Merge branch 'master' of github.com:highfidelity/hifi into 22007-hifiQtBuildv2

This commit is contained in:
NissimHadar 2019-04-25 14:26:29 -07:00
commit a4d5aa8eac
141 changed files with 3364 additions and 1597 deletions

View file

@ -107,6 +107,10 @@ BakeVersion currentBakeVersionForAssetType(BakedAssetType type) {
}
}
QString getBakeMapping(const AssetUtils::AssetHash& hash, const QString& relativeFilePath) {
return AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath;
}
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
void AssetServer::bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath) {
@ -141,26 +145,27 @@ std::pair<AssetUtils::BakingStatus, QString> AssetServer::getAssetStatus(const A
return { AssetUtils::Baked, "" };
}
auto dotIndex = path.lastIndexOf(".");
if (dotIndex == -1) {
BakedAssetType type = assetTypeForFilename(path);
if (type == BakedAssetType::Undefined) {
return { AssetUtils::Irrelevant, "" };
}
auto extension = path.mid(dotIndex + 1);
bool loaded;
AssetMeta meta;
std::tie(loaded, meta) = readMetaFile(hash);
QString bakedFilename;
if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) {
bakedFilename = BAKED_MODEL_SIMPLE_NAME;
} else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit()) && hasMetaFile(hash)) {
bakedFilename = BAKED_TEXTURE_SIMPLE_NAME;
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) {
bakedFilename = BAKED_SCRIPT_SIMPLE_NAME;
} else {
// We create a meta file for Skyboxes at runtime when they get requested
// Otherwise, textures don't get baked by themselves.
if (type == BakedAssetType::Texture && !loaded) {
return { AssetUtils::Irrelevant, "" };
}
auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + bakedFilename;
QString bakedFilename = bakedFilenameForAssetType(type);
auto bakedPath = getBakeMapping(hash, bakedFilename);
if (loaded && !meta.redirectTarget.isEmpty()) {
bakedPath = meta.redirectTarget;
}
auto jt = _fileMappings.find(bakedPath);
if (jt != _fileMappings.end()) {
if (jt->second == hash) {
@ -168,14 +173,8 @@ std::pair<AssetUtils::BakingStatus, QString> AssetServer::getAssetStatus(const A
} else {
return { AssetUtils::Baked, "" };
}
} else {
bool loaded;
AssetMeta meta;
std::tie(loaded, meta) = readMetaFile(hash);
if (loaded && meta.failedLastBake) {
return { AssetUtils::Error, meta.lastBakeErrors };
}
} else if (loaded && meta.failedLastBake) {
return { AssetUtils::Error, meta.lastBakeErrors };
}
return { AssetUtils::Pending, "" };
@ -227,8 +226,16 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU
return false;
}
bool loaded;
AssetMeta meta;
std::tie(loaded, meta) = readMetaFile(assetHash);
QString bakedFilename = bakedFilenameForAssetType(type);
auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename;
auto bakedPath = getBakeMapping(assetHash, bakedFilename);
if (loaded && !meta.redirectTarget.isEmpty()) {
bakedPath = meta.redirectTarget;
}
auto mappingIt = _fileMappings.find(bakedPath);
bool bakedMappingExists = mappingIt != _fileMappings.end();
@ -238,10 +245,8 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU
return false;
}
bool loaded;
AssetMeta meta;
std::tie(loaded, meta) = readMetaFile(assetHash);
// We create a meta file for Skyboxes at runtime when they get requested
// Otherwise, textures don't get baked by themselves.
if (type == BakedAssetType::Texture && !loaded) {
return false;
}
@ -633,36 +638,33 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketLi
if (it != _fileMappings.end()) {
// check if we should re-direct to a baked asset
// first, figure out from the mapping extension what type of file this is
auto assetPathExtension = assetPath.mid(assetPath.lastIndexOf('.') + 1).toLower();
auto type = assetTypeForFilename(assetPath);
QString bakedRootFile = bakedFilenameForAssetType(type);
auto originalAssetHash = it->second;
QString redirectedAssetHash;
QString bakedAssetPath;
quint8 wasRedirected = false;
bool bakingDisabled = false;
if (!bakedRootFile.isEmpty()) {
// we ran into an asset for which we could have a baked version, let's check if it's ready
bakedAssetPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + bakedRootFile;
auto bakedIt = _fileMappings.find(bakedAssetPath);
bool loaded;
AssetMeta meta;
std::tie(loaded, meta) = readMetaFile(originalAssetHash);
if (bakedIt != _fileMappings.end()) {
if (bakedIt->second != originalAssetHash) {
qDebug() << "Did find baked version for: " << originalAssetHash << assetPath;
// we found a baked version of the requested asset to serve, redirect to that
redirectedAssetHash = bakedIt->second;
wasRedirected = true;
} else {
qDebug() << "Did not find baked version for: " << originalAssetHash << assetPath << " (disabled)";
bakingDisabled = true;
}
auto type = assetTypeForFilename(assetPath);
QString bakedRootFile = bakedFilenameForAssetType(type);
QString bakedAssetPath = getBakeMapping(originalAssetHash, bakedRootFile);
if (loaded && !meta.redirectTarget.isEmpty()) {
bakedAssetPath = meta.redirectTarget;
}
auto bakedIt = _fileMappings.find(bakedAssetPath);
if (bakedIt != _fileMappings.end()) {
if (bakedIt->second != originalAssetHash) {
qDebug() << "Did find baked version for: " << originalAssetHash << assetPath;
// we found a baked version of the requested asset to serve, redirect to that
redirectedAssetHash = bakedIt->second;
wasRedirected = true;
} else {
qDebug() << "Did not find baked version for: " << originalAssetHash << assetPath;
qDebug() << "Did not find baked version for: " << originalAssetHash << assetPath << " (disabled)";
bakingDisabled = true;
}
}
@ -684,20 +686,13 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketLi
auto query = QUrlQuery(url.query());
bool isSkybox = query.hasQueryItem("skybox");
if (isSkybox) {
bool loaded;
AssetMeta meta;
std::tie(loaded, meta) = readMetaFile(originalAssetHash);
if (!loaded) {
AssetMeta needsBakingMeta;
needsBakingMeta.bakeVersion = NEEDS_BAKING_BAKE_VERSION;
writeMetaFile(originalAssetHash, needsBakingMeta);
if (!bakingDisabled) {
maybeBake(assetPath, originalAssetHash);
}
if (isSkybox && !loaded) {
AssetMeta needsBakingMeta;
needsBakingMeta.bakeVersion = NEEDS_BAKING_BAKE_VERSION;
writeMetaFile(originalAssetHash, needsBakingMeta);
if (!bakingDisabled) {
maybeBake(assetPath, originalAssetHash);
}
}
}
@ -1297,14 +1292,6 @@ bool AssetServer::renameMapping(AssetUtils::AssetPath oldPath, AssetUtils::Asset
}
}
static const QString BAKED_ASSET_SIMPLE_FBX_NAME = "asset.fbx";
static const QString BAKED_ASSET_SIMPLE_TEXTURE_NAME = "texture.ktx";
static const QString BAKED_ASSET_SIMPLE_JS_NAME = "asset.js";
QString getBakeMapping(const AssetUtils::AssetHash& hash, const QString& relativeFilePath) {
return AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath;
}
void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath, QString errors) {
qDebug() << "Failed to bake: " << originalAssetHash << assetPath << "(" << errors << ")";
@ -1326,12 +1313,78 @@ void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath,
}
void AssetServer::handleCompletedBake(QString originalAssetHash, QString originalAssetPath,
QString bakedTempOutputDir, QVector<QString> bakedFilePaths) {
QString bakedTempOutputDir) {
auto reportCompletion = [this, originalAssetPath, originalAssetHash](bool errorCompletingBake,
QString errorReason,
QString redirectTarget) {
auto type = assetTypeForFilename(originalAssetPath);
auto currentTypeVersion = currentBakeVersionForAssetType(type);
AssetMeta meta;
meta.bakeVersion = currentTypeVersion;
meta.failedLastBake = errorCompletingBake;
meta.redirectTarget = redirectTarget;
if (errorCompletingBake) {
qWarning() << "Could not complete bake for" << originalAssetHash;
meta.lastBakeErrors = errorReason;
}
writeMetaFile(originalAssetHash, meta);
_pendingBakes.remove(originalAssetHash);
};
bool errorCompletingBake { false };
QString errorReason;
QString redirectTarget;
qDebug() << "Completing bake for " << originalAssetHash;
// Find the directory containing the baked content
QDir outputDir(bakedTempOutputDir);
QString outputDirName = outputDir.dirName();
auto directories = outputDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
QString bakedDirectoryPath;
for (const auto& dirName : directories) {
outputDir.cd(dirName);
if (outputDir.exists("baked") && outputDir.exists("original")) {
bakedDirectoryPath = outputDir.filePath("baked");
break;
}
outputDir.cdUp();
}
if (bakedDirectoryPath.isEmpty()) {
errorCompletingBake = true;
errorReason = "Failed to find baking output";
// Cleanup temporary output directory
PathUtils::deleteMyTemporaryDir(outputDirName);
reportCompletion(errorCompletingBake, errorReason, redirectTarget);
return;
}
// Compile list of all the baked files
QDirIterator it(bakedDirectoryPath, QDirIterator::Subdirectories);
QVector<QString> bakedFilePaths;
while (it.hasNext()) {
it.next();
if (it.fileInfo().isFile()) {
bakedFilePaths.push_back(it.filePath());
}
}
if (bakedFilePaths.isEmpty()) {
errorCompletingBake = true;
errorReason = "Baking output has no files";
// Cleanup temporary output directory
PathUtils::deleteMyTemporaryDir(outputDirName);
reportCompletion(errorCompletingBake, errorReason, redirectTarget);
return;
}
QDir bakedDirectory(bakedDirectoryPath);
for (auto& filePath : bakedFilePaths) {
// figure out the hash for the contents of this file
QFile file(filePath);
@ -1340,89 +1393,72 @@ void AssetServer::handleCompletedBake(QString originalAssetHash, QString origina
AssetUtils::AssetHash bakedFileHash;
if (file.open(QIODevice::ReadOnly)) {
QCryptographicHash hasher(QCryptographicHash::Sha256);
if (hasher.addData(&file)) {
bakedFileHash = hasher.result().toHex();
} else {
// stop handling this bake, couldn't hash the contents of the file
errorCompletingBake = true;
errorReason = "Failed to finalize bake";
break;
}
// first check that we don't already have this bake file in our list
auto bakeFileDestination = _filesDirectory.absoluteFilePath(bakedFileHash);
if (!QFile::exists(bakeFileDestination)) {
// copy each to our files folder (with the hash as their filename)
if (!file.copy(_filesDirectory.absoluteFilePath(bakedFileHash))) {
// stop handling this bake, couldn't copy the bake file into our files directory
errorCompletingBake = true;
errorReason = "Failed to copy baked assets to asset server";
break;
}
}
// setup the mapping for this bake file
auto relativeFilePath = QUrl(filePath).fileName();
qDebug() << "Relative file path is: " << relativeFilePath;
if (relativeFilePath.endsWith(".fbx", Qt::CaseInsensitive)) {
// for an FBX file, we replace the filename with the simple name
// (to handle the case where two mapped assets have the same hash but different names)
relativeFilePath = BAKED_ASSET_SIMPLE_FBX_NAME;
} else if (relativeFilePath.endsWith(".js", Qt::CaseInsensitive)) {
relativeFilePath = BAKED_ASSET_SIMPLE_JS_NAME;
} else if (!originalAssetPath.endsWith(".fbx", Qt::CaseInsensitive)) {
relativeFilePath = BAKED_ASSET_SIMPLE_TEXTURE_NAME;
}
QString bakeMapping = getBakeMapping(originalAssetHash, relativeFilePath);
// add a mapping (under the hidden baked folder) for this file resulting from the bake
if (setMapping(bakeMapping, bakedFileHash)) {
qDebug() << "Added" << bakeMapping << "for bake file" << bakedFileHash << "from bake of" << originalAssetHash;
} else {
qDebug() << "Failed to set mapping";
// stop handling this bake, couldn't add a mapping for this bake file
errorCompletingBake = true;
errorReason = "Failed to finalize bake";
break;
}
} else {
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "Failed to open baked file: " << filePath;
// stop handling this bake, we couldn't open one of the files for reading
errorCompletingBake = true;
errorReason = "Failed to finalize bake";
errorReason = "Could not open baked file " + file.fileName();
break;
}
}
for (auto& filePath : bakedFilePaths) {
QFile file(filePath);
if (!file.remove()) {
qWarning() << "Failed to remove temporary file:" << filePath;
QCryptographicHash hasher(QCryptographicHash::Sha256);
if (!hasher.addData(&file)) {
// stop handling this bake, couldn't hash the contents of the file
errorCompletingBake = true;
errorReason = "Could not hash data for " + file.fileName();
break;
}
}
if (!QDir(bakedTempOutputDir).rmdir(".")) {
qWarning() << "Failed to remove temporary directory:" << bakedTempOutputDir;
bakedFileHash = hasher.result().toHex();
// first check that we don't already have this bake file in our list
auto bakeFileDestination = _filesDirectory.absoluteFilePath(bakedFileHash);
if (!QFile::exists(bakeFileDestination)) {
// copy each to our files folder (with the hash as their filename)
if (!file.copy(_filesDirectory.absoluteFilePath(bakedFileHash))) {
// stop handling this bake, couldn't copy the bake file into our files directory
errorCompletingBake = true;
errorReason = "Failed to copy baked assets to asset server";
break;
}
}
// setup the mapping for this bake file
auto relativeFilePath = bakedDirectory.relativeFilePath(filePath);
QString bakeMapping = getBakeMapping(originalAssetHash, relativeFilePath);
// Check if this is the file we should redirect to when someone asks for the original asset
if ((relativeFilePath.endsWith(".baked.fst", Qt::CaseInsensitive) && originalAssetPath.endsWith(".fbx")) ||
(relativeFilePath.endsWith(".texmeta.json", Qt::CaseInsensitive) && !originalAssetPath.endsWith(".fbx"))) {
if (!redirectTarget.isEmpty()) {
qWarning() << "Found multiple baked redirect target for" << originalAssetPath;
}
redirectTarget = bakeMapping;
}
// add a mapping (under the hidden baked folder) for this file resulting from the bake
if (!setMapping(bakeMapping, bakedFileHash)) {
qDebug() << "Failed to set mapping";
// stop handling this bake, couldn't add a mapping for this bake file
errorCompletingBake = true;
errorReason = "Failed to set mapping for baked file " + file.fileName();
break;
}
qDebug() << "Added" << bakeMapping << "for bake file" << bakedFileHash << "from bake of" << originalAssetHash;
}
auto type = assetTypeForFilename(originalAssetPath);
auto currentTypeVersion = currentBakeVersionForAssetType(type);
AssetMeta meta;
meta.bakeVersion = currentTypeVersion;
meta.failedLastBake = errorCompletingBake;
if (errorCompletingBake) {
qWarning() << "Could not complete bake for" << originalAssetHash;
meta.lastBakeErrors = errorReason;
if (redirectTarget.isEmpty()) {
errorCompletingBake = true;
errorReason = "Could not find root file for baked output";
}
writeMetaFile(originalAssetHash, meta);
_pendingBakes.remove(originalAssetHash);
// Cleanup temporary output directory
PathUtils::deleteMyTemporaryDir(outputDirName);
reportCompletion(errorCompletingBake, errorReason, redirectTarget);
}
void AssetServer::handleAbortedBake(QString originalAssetHash, QString assetPath) {
@ -1435,6 +1471,7 @@ void AssetServer::handleAbortedBake(QString originalAssetHash, QString assetPath
static const QString BAKE_VERSION_KEY = "bake_version";
static const QString FAILED_LAST_BAKE_KEY = "failed_last_bake";
static const QString LAST_BAKE_ERRORS_KEY = "last_bake_errors";
static const QString REDIRECT_TARGET_KEY = "redirect_target";
std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetUtils::AssetHash hash) {
auto metaFilePath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + "meta.json";
@ -1461,6 +1498,7 @@ std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetUtils::AssetHash hash)
auto bakeVersion = root[BAKE_VERSION_KEY];
auto failedLastBake = root[FAILED_LAST_BAKE_KEY];
auto lastBakeErrors = root[LAST_BAKE_ERRORS_KEY];
auto redirectTarget = root[REDIRECT_TARGET_KEY];
if (bakeVersion.isDouble()
&& failedLastBake.isBool()
@ -1470,6 +1508,7 @@ std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetUtils::AssetHash hash)
meta.bakeVersion = bakeVersion.toInt();
meta.failedLastBake = failedLastBake.toBool();
meta.lastBakeErrors = lastBakeErrors.toString();
meta.redirectTarget = redirectTarget.toString();
return { true, meta };
} else {
@ -1488,6 +1527,7 @@ bool AssetServer::writeMetaFile(AssetUtils::AssetHash originalAssetHash, const A
metaFileObject[BAKE_VERSION_KEY] = (int)meta.bakeVersion;
metaFileObject[FAILED_LAST_BAKE_KEY] = meta.failedLastBake;
metaFileObject[LAST_BAKE_ERRORS_KEY] = meta.lastBakeErrors;
metaFileObject[REDIRECT_TARGET_KEY] = meta.redirectTarget;
QJsonDocument metaFileDoc;
metaFileDoc.setObject(metaFileObject);
@ -1521,10 +1561,18 @@ bool AssetServer::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool
if (type == BakedAssetType::Undefined) {
continue;
}
QString bakedFilename = bakedFilenameForAssetType(type);
auto hash = it->second;
bool loaded;
AssetMeta meta;
std::tie(loaded, meta) = readMetaFile(hash);
QString bakedFilename = bakedFilenameForAssetType(type);
auto bakedMapping = getBakeMapping(hash, bakedFilename);
if (loaded && !meta.redirectTarget.isEmpty()) {
bakedMapping = meta.redirectTarget;
}
auto it = _fileMappings.find(bakedMapping);
bool currentlyDisabled = (it != _fileMappings.end() && it->second == hash);

View file

@ -62,12 +62,10 @@ enum class ScriptBakeVersion : BakeVersion {
};
struct AssetMeta {
AssetMeta() {
}
BakeVersion bakeVersion { INITIAL_BAKE_VERSION };
bool failedLastBake { false };
QString lastBakeErrors;
QString redirectTarget;
};
class BakeAssetTask;
@ -139,8 +137,7 @@ private:
void bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath);
/// Move baked content for asset to baked directory and update baked status
void handleCompletedBake(QString originalAssetHash, QString assetPath, QString bakedTempOutputDir,
QVector<QString> bakedFilePaths);
void handleCompletedBake(QString originalAssetHash, QString assetPath, QString bakedTempOutputDir);
void handleFailedBake(QString originalAssetHash, QString assetPath, QString errors);
void handleAbortedBake(QString originalAssetHash, QString assetPath);

View file

@ -36,33 +36,38 @@ BakeAssetTask::BakeAssetTask(const AssetUtils::AssetHash& assetHash, const Asset
});
}
void cleanupTempFiles(QString tempOutputDir, std::vector<QString> files) {
for (const auto& filename : files) {
QFile f { filename };
if (!f.remove()) {
qDebug() << "Failed to remove:" << filename;
}
}
if (!tempOutputDir.isEmpty()) {
QDir dir { tempOutputDir };
if (!dir.rmdir(".")) {
qDebug() << "Failed to remove temporary directory:" << tempOutputDir;
}
}
};
void BakeAssetTask::run() {
if (_isBaking.exchange(true)) {
qWarning() << "Tried to start bake asset task while already baking";
return;
}
// Make a new temporary directory for the Oven to work in
QString tempOutputDir = PathUtils::generateTemporaryDir();
QString tempOutputDirName = QDir(tempOutputDir).dirName();
if (tempOutputDir.isEmpty()) {
QString errors = "Could not create temporary working directory";
emit bakeFailed(_assetHash, _assetPath, errors);
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
return;
}
// Copy file to bake the temporary dir and give a name the oven can work with
auto assetName = _assetPath.split("/").last();
auto tempAssetPath = tempOutputDir + "/" + assetName;
auto success = QFile::copy(_filePath, tempAssetPath);
if (!success) {
QString errors = "Couldn't copy file to bake to temporary directory";
emit bakeFailed(_assetHash, _assetPath, errors);
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
return;
}
auto base = QFileInfo(QCoreApplication::applicationFilePath()).absoluteDir();
QString path = base.absolutePath() + "/oven";
QString extension = _assetPath.mid(_assetPath.lastIndexOf('.') + 1);
QStringList args {
"-i", _filePath,
"-i", tempAssetPath,
"-o", tempOutputDir,
"-t", extension,
};
@ -72,10 +77,11 @@ void BakeAssetTask::run() {
QEventLoop loop;
connect(_ovenProcess.get(), static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
this, [&loop, this, tempOutputDir](int exitCode, QProcess::ExitStatus exitStatus) {
this, [&loop, this, tempOutputDir, tempAssetPath, tempOutputDirName](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug() << "Baking process finished: " << exitCode << exitStatus;
if (exitStatus == QProcess::CrashExit) {
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
if (_wasAborted) {
emit bakeAborted(_assetHash, _assetPath);
} else {
@ -83,16 +89,10 @@ void BakeAssetTask::run() {
emit bakeFailed(_assetHash, _assetPath, errors);
}
} else if (exitCode == OVEN_STATUS_CODE_SUCCESS) {
QDir outputDir = tempOutputDir;
auto files = outputDir.entryInfoList(QDir::Files);
QVector<QString> outputFiles;
for (auto& file : files) {
outputFiles.push_back(file.absoluteFilePath());
}
emit bakeComplete(_assetHash, _assetPath, tempOutputDir, outputFiles);
emit bakeComplete(_assetHash, _assetPath, tempOutputDir);
} else if (exitStatus == QProcess::NormalExit && exitCode == OVEN_STATUS_CODE_ABORT) {
_wasAborted.store(true);
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
emit bakeAborted(_assetHash, _assetPath);
} else {
QString errors;
@ -107,6 +107,7 @@ void BakeAssetTask::run() {
errors = "Unknown error occurred while baking";
}
}
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
emit bakeFailed(_assetHash, _assetPath, errors);
}
@ -115,7 +116,10 @@ void BakeAssetTask::run() {
qDebug() << "Starting oven for " << _assetPath;
_ovenProcess->start(path, args, QIODevice::ReadOnly);
if (!_ovenProcess->waitForStarted(-1)) {
qDebug() << "Running:" << path << args;
if (!_ovenProcess->waitForStarted()) {
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
QString errors = "Oven process failed to start";
emit bakeFailed(_assetHash, _assetPath, errors);
return;

View file

@ -37,7 +37,7 @@ public slots:
void abort();
signals:
void bakeComplete(QString assetHash, QString assetPath, QString tempOutputDir, QVector<QString> outputFiles);
void bakeComplete(QString assetHash, QString assetPath, QString tempOutputDir);
void bakeFailed(QString assetHash, QString assetPath, QString errors);
void bakeAborted(QString assetHash, QString assetPath);

View file

@ -17,8 +17,8 @@ if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://public.highfidelity.com/dependencies/ovr_sdk_win_1.26.0_public.zip
URL_MD5 06804ff9727b910dcd04a37c800053b5
URL https://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.35.0.zip
URL_MD5 1e3e8b2101387af07ff9c841d0ea285e
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/LibOVRCMakeLists.txt" <SOURCE_DIR>/CMakeLists.txt
LOG_DOWNLOAD 1

View file

@ -23,15 +23,15 @@ Rectangle {
property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled;
function updateOpacity() {
if (ignoreRadiusEnabled) {
bubbleRect.opacity = 1.0;
} else {
bubbleRect.opacity = 0.7;
}
var rectOpacity = ignoreRadiusEnabled ? 1.0 : (mouseArea.containsMouse ? 1.0 : 0.7);
bubbleRect.opacity = rectOpacity;
}
Component.onCompleted: {
updateOpacity();
AvatarInputs.ignoreRadiusEnabledChanged.connect(function() {
ignoreRadiusEnabled = AvatarInputs.ignoreRadiusEnabled;
});
}
onIgnoreRadiusEnabledChanged: {
@ -74,10 +74,10 @@ Rectangle {
}
drag.target: dragTarget;
onContainsMouseChanged: {
var rectOpacity = (ignoreRadiusEnabled && containsMouse) ? 1.0 : (containsMouse ? 1.0 : 0.7);
if (containsMouse) {
Tablet.playSound(TabletEnums.ButtonHover);
}
var rectOpacity = ignoreRadiusEnabled ? 1.0 : (mouseArea.containsMouse ? 1.0 : 0.7);
bubbleRect.opacity = rectOpacity;
}
}

View file

@ -398,7 +398,7 @@ Item {
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
onLinkActivated: loginDialog.openUrl(link);
onLinkActivated: Window.openUrl(link);
Component.onCompleted: {
if (termsTextMetrics.width > root.bannerWidth) {

View file

@ -363,7 +363,7 @@ Item {
linkColor: hifi.colors.blueAccent
onLinkActivated: {
Tablet.playSound(TabletEnums.ButtonClick);
loginDialog.openUrl(link);
Window.openUrl(link);
lightboxPopup.titleText = "Can't Access Account";
lightboxPopup.bodyText = lightboxPopup.cantAccessBodyText;
lightboxPopup.button2text = "CLOSE";

View file

@ -411,7 +411,7 @@ Item {
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
onLinkActivated: loginDialog.openUrl(link);
onLinkActivated: Window.openUrl(link);
Component.onCompleted: {
if (termsTextMetrics.width > root.bannerWidth) {

View file

@ -234,7 +234,7 @@ Item {
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
onLinkActivated: loginDialog.openUrl(link);
onLinkActivated: Window.openUrl(link);
Component.onCompleted: {
if (termsTextMetrics.width > root.bannerWidth) {

View file

@ -952,7 +952,7 @@ Rectangle {
text: "LOG IN"
onClicked: {
sendToScript({method: 'needsLogIn_loginClicked'});
sendToScript({method: 'marketplace_loginClicked'});
}
}

View file

@ -137,7 +137,7 @@ Item {
width: parent.width/2 - anchors.leftMargin*2;
text: "Cancel"
onClicked: {
sendToScript({method: 'needsLogIn_cancelClicked'});
sendToScript({method: 'passphrasePopup_cancelClicked'});
}
}
@ -155,7 +155,7 @@ Item {
width: parent.width/2 - anchors.rightMargin*2;
text: "Log In"
onClicked: {
sendToScript({method: 'needsLogIn_loginClicked'});
sendToScript({method: 'marketplace_loginClicked'});
}
}
}

View file

@ -1861,12 +1861,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) {
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_MIN); // So that camera doesn't auto-switch to third person.
} else if (Menu::getInstance()->isOptionChecked(MenuOption::IndependentMode)) {
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true);
cameraMenuChanged();
} else if (Menu::getInstance()->isOptionChecked(MenuOption::CameraEntityMode)) {
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true);
cameraMenuChanged();
}
{
@ -2337,7 +2331,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
DependencyManager::get<PickManager>()->setPrecisionPicking(rayPickID, value);
});
EntityItem::setBillboardRotationOperator([this](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, const glm::vec3& frustumPos) {
EntityItem::setBillboardRotationOperator([](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, const glm::vec3& frustumPos) {
if (billboardMode == BillboardMode::YAW) {
//rotate about vertical to face the camera
glm::vec3 dPosition = frustumPos - position;
@ -2365,7 +2359,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
DependencyManager::get<UsersScriptingInterface>()->setKickConfirmationOperator([this] (const QUuid& nodeID) { userKickConfirmation(nodeID); });
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([=](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
bool isTablet = url == TabletScriptingInterface::QML;
if (htmlContent) {
webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(render::entities::WebEntityRenderer::QML);
@ -2405,7 +2399,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
const uint8_t TABLET_FPS = 90;
webSurface->setMaxFps(isTablet ? TABLET_FPS : DEFAULT_MAX_FPS);
});
render::entities::WebEntityRenderer::setReleaseWebSurfaceOperator([this](QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface, std::vector<QMetaObject::Connection>& connections) {
render::entities::WebEntityRenderer::setReleaseWebSurfaceOperator([=](QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface, std::vector<QMetaObject::Connection>& connections) {
QQuickItem* rootItem = webSurface->getRootItem();
// Fix for crash in QtWebEngineCore when rapidly switching domains
@ -2443,6 +2437,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
DependencyManager::get<TabletScriptingInterface>()->preloadSounds();
DependencyManager::get<Keyboard>()->createKeyboard();
FileDialogHelper::setOpenDirectoryOperator([this](const QString& path) { openDirectory(path); });
QDesktopServices::setUrlHandler("file", this, "showUrlHandler");
QDesktopServices::setUrlHandler("", this, "showUrlHandler");
auto drives = QDir::drives();
for (auto drive : drives) {
QDesktopServices::setUrlHandler(QUrl(drive.absolutePath()).scheme(), this, "showUrlHandler");
}
_pendingIdleEvent = false;
_graphicsEngine.startup();
@ -5757,9 +5759,6 @@ void Application::cycleCamera() {
menu->setIsOptionChecked(MenuOption::ThirdPerson, false);
menu->setIsOptionChecked(MenuOption::FullscreenMirror, true);
} else if (menu->isOptionChecked(MenuOption::IndependentMode) || menu->isOptionChecked(MenuOption::CameraEntityMode)) {
// do nothing if in independent or camera entity modes
return;
}
cameraMenuChanged(); // handle the menu change
}
@ -5775,12 +5774,6 @@ void Application::cameraModeChanged() {
case CAMERA_MODE_MIRROR:
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, true);
break;
case CAMERA_MODE_INDEPENDENT:
Menu::getInstance()->setIsOptionChecked(MenuOption::IndependentMode, true);
break;
case CAMERA_MODE_ENTITY:
Menu::getInstance()->setIsOptionChecked(MenuOption::CameraEntityMode, true);
break;
default:
break;
}
@ -5824,14 +5817,6 @@ void Application::cameraMenuChanged() {
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT);
}
}
} else if (menu->isOptionChecked(MenuOption::IndependentMode)) {
if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) {
_myCamera.setMode(CAMERA_MODE_INDEPENDENT);
}
} else if (menu->isOptionChecked(MenuOption::CameraEntityMode)) {
if (_myCamera.getMode() != CAMERA_MODE_ENTITY) {
_myCamera.setMode(CAMERA_MODE_ENTITY);
}
}
}
@ -6746,6 +6731,11 @@ void Application::updateRenderArgs(float deltaTime) {
}
}
appRenderArgs._renderArgs._stencilMaskMode = getActiveDisplayPlugin()->getStencilMaskMode();
if (appRenderArgs._renderArgs._stencilMaskMode == StencilMaskMode::MESH) {
appRenderArgs._renderArgs._stencilMaskOperator = getActiveDisplayPlugin()->getStencilMaskMeshOperator();
}
{
QMutexLocker viewLocker(&_viewMutex);
_myCamera.loadViewFrustum(_displayViewFrustum);
@ -8280,19 +8270,6 @@ void Application::packageModel() {
ModelPackager::package();
}
void Application::openUrl(const QUrl& url) const {
if (!url.isEmpty()) {
if (url.scheme() == URL_SCHEME_HIFI) {
DependencyManager::get<AddressManager>()->handleLookupString(url.toString());
} else if (url.scheme() == URL_SCHEME_HIFIAPP) {
DependencyManager::get<QmlCommerce>()->openSystemApp(url.path());
} else {
// address manager did not handle - ask QDesktopServices to handle
QDesktopServices::openUrl(url);
}
}
}
void Application::loadDialog() {
ModalDialogListener* dlg = OffscreenUi::getOpenFileNameAsync(_glWidget, tr("Open Script"),
getPreviousScriptLocation(),
@ -8433,11 +8410,23 @@ void Application::loadAvatarBrowser() const {
DependencyManager::get<HMDScriptingInterface>()->openTablet();
}
void Application::addSnapshotOperator(const SnapshotOperator& snapshotOperator) {
std::lock_guard<std::mutex> lock(_snapshotMutex);
_snapshotOperators.push(snapshotOperator);
_hasPrimarySnapshot = _hasPrimarySnapshot || std::get<2>(snapshotOperator);
}
bool Application::takeSnapshotOperators(std::queue<SnapshotOperator>& snapshotOperators) {
std::lock_guard<std::mutex> lock(_snapshotMutex);
bool hasPrimarySnapshot = _hasPrimarySnapshot;
_hasPrimarySnapshot = false;
_snapshotOperators.swap(snapshotOperators);
return hasPrimarySnapshot;
}
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) {
postLambdaEvent([notify, includeAnimated, aspectRatio, filename, this] {
// Get a screenshot and save it
QString path = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename,
TestScriptingInterface::getInstance()->getTestResultsLocation());
addSnapshotOperator(std::make_tuple([notify, includeAnimated, aspectRatio, filename](const QImage& snapshot) {
QString path = DependencyManager::get<Snapshot>()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation());
// If we're not doing an animated snapshot as well...
if (!includeAnimated) {
@ -8446,19 +8435,20 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
}
} else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) {
// Get an animated GIF snapshot and save it
SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>());
qApp->postLambdaEvent([path, aspectRatio] {
// Get an animated GIF snapshot and save it
SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, DependencyManager::get<WindowScriptingInterface>());
});
}
});
}, aspectRatio, true));
}
void Application::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) {
postLambdaEvent([notify, filename, this] {
QString snapshotPath = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename,
TestScriptingInterface::getInstance()->getTestResultsLocation());
addSnapshotOperator(std::make_tuple([notify, filename](const QImage& snapshot) {
QString snapshotPath = DependencyManager::get<Snapshot>()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation());
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, notify);
});
}, 0.0f, false));
}
void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) {
@ -9161,7 +9151,7 @@ void Application::readArgumentsFromLocalSocket() const {
// If we received a message, try to open it as a URL
if (message.length() > 0) {
qApp->openUrl(QString::fromUtf8(message));
DependencyManager::get<WindowScriptingInterface>()->openUrl(QString::fromUtf8(message));
}
}
@ -9278,6 +9268,44 @@ QString Application::getGraphicsCardType() {
return GPUIdent::getInstance()->getName();
}
void Application::openDirectory(const QString& path) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "openDirectory", Q_ARG(const QString&, path));
return;
}
QString dirPath = path;
const QString FILE_SCHEME = "file:///";
if (dirPath.startsWith(FILE_SCHEME)) {
dirPath.remove(0, FILE_SCHEME.length());
}
QFileInfo fileInfo(dirPath);
if (fileInfo.isDir()) {
auto scheme = QUrl(path).scheme();
QDesktopServices::unsetUrlHandler(scheme);
QDesktopServices::openUrl(path);
QDesktopServices::setUrlHandler(scheme, this, "showUrlHandler");
}
}
void Application::showUrlHandler(const QUrl& url) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "showUrlHandler", Q_ARG(const QUrl&, url));
return;
}
ModalDialogListener* dlg = OffscreenUi::asyncQuestion("Confirm openUrl", "Do you recognize this path or code and want to open or execute it: " + url.toDisplayString());
QObject::connect(dlg, &ModalDialogListener::response, this, [=](QVariant answer) {
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
if (QMessageBox::Yes == static_cast<QMessageBox::StandardButton>(answer.toInt())) {
// Unset the handler, open the URL, and the reset the handler
QDesktopServices::unsetUrlHandler(url.scheme());
QDesktopServices::openUrl(url);
QDesktopServices::setUrlHandler(url.scheme(), this, "showUrlHandler");
}
});
}
#if defined(Q_OS_ANDROID)
void Application::beforeEnterBackground() {
auto nodeList = DependencyManager::get<NodeList>();

View file

@ -345,6 +345,12 @@ public:
void toggleAwayMode();
#endif
using SnapshotOperator = std::tuple<std::function<void(const QImage&)>, float, bool>;
void addSnapshotOperator(const SnapshotOperator& snapshotOperator);
bool takeSnapshotOperators(std::queue<SnapshotOperator>& snapshotOperators);
void openDirectory(const QString& path);
signals:
void svoImportRequested(const QString& url);
@ -404,8 +410,6 @@ public slots:
static void packageModel();
void openUrl(const QUrl& url) const;
void resetSensors(bool andReload = false);
void setActiveFaceTracker() const;
@ -472,6 +476,8 @@ public slots:
QString getGraphicsCardType();
void showUrlHandler(const QUrl& url);
private slots:
void onDesktopRootItemCreated(QQuickItem* qmlContext);
void onDesktopRootContextCreated(QQmlContext* qmlContext);
@ -789,6 +795,9 @@ private:
AudioInjectorPointer _snapshotSoundInjector;
SharedSoundPointer _snapshotSound;
SharedSoundPointer _sampleSound;
std::mutex _snapshotMutex;
std::queue<SnapshotOperator> _snapshotOperators;
bool _hasPrimarySnapshot { false };
DisplayPluginPointer _autoSwitchDisplayModeSupportedHMDPlugin;
QString _autoSwitchDisplayModeSupportedHMDPluginName;

View file

@ -194,20 +194,6 @@ Menu::Menu() {
viewMirrorAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
// View > Independent
auto viewIndependentAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
MenuOption::IndependentMode, 0,
false, qApp, SLOT(cameraMenuChanged())));
viewIndependentAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
// View > Entity Camera
auto viewEntityCameraAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
MenuOption::CameraEntityMode, 0,
false, qApp, SLOT(cameraMenuChanged())));
viewEntityCameraAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
viewMenu->addSeparator();
// View > Center Player In View

View file

@ -53,7 +53,6 @@ namespace MenuOption {
const QString BookmarkAvatarEntities = "Bookmark Avatar Entities";
const QString BookmarkLocation = "Bookmark Location";
const QString CalibrateCamera = "Calibrate Camera";
const QString CameraEntityMode = "Entity Mode";
const QString CenterPlayerInView = "Center Player In View";
const QString Chat = "Chat...";
const QString ClearDiskCache = "Clear Disk Cache";
@ -120,7 +119,6 @@ namespace MenuOption {
const QString Help = "Help...";
const QString HomeLocation = "Home ";
const QString IncreaseAvatarSize = "Increase Avatar Size";
const QString IndependentMode = "Independent Mode";
const QString ActionMotorControl = "Enable Default Motor Control";
const QString LastLocation = "Last Location";
const QString LoadScript = "Open and Run Script File...";

View file

@ -18,9 +18,6 @@
#include <QPushButton>
#include <QStandardPaths>
static const QString AVATAR_HEAD_AND_BODY_STRING = "Avatar Body with Head";
static const QString ENTITY_MODEL_STRING = "Entity Model";
ModelSelector::ModelSelector() {
QFormLayout* form = new QFormLayout(this);

View file

@ -152,10 +152,12 @@ public:
_cachedArgsPointer->_viewport = args->_viewport;
_cachedArgsPointer->_displayMode = args->_displayMode;
_cachedArgsPointer->_renderMode = args->_renderMode;
_cachedArgsPointer->_stencilMaskMode = args->_stencilMaskMode;
args->_blitFramebuffer = destFramebuffer;
args->_viewport = glm::ivec4(0, 0, destFramebuffer->getWidth(), destFramebuffer->getHeight());
args->_displayMode = RenderArgs::MONO;
args->_renderMode = RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE;
args->_stencilMaskMode = StencilMaskMode::NONE;
gpu::doInBatch("SecondaryCameraJob::run", args->_context, [&](gpu::Batch& batch) {
batch.disableContextStereo();
@ -255,10 +257,11 @@ public:
void run(const render::RenderContextPointer& renderContext, const RenderArgsPointer& cachedArgs) {
auto args = renderContext->args;
if (cachedArgs) {
args->_blitFramebuffer = cachedArgs->_blitFramebuffer;
args->_viewport = cachedArgs->_viewport;
args->_displayMode = cachedArgs->_displayMode;
args->_renderMode = cachedArgs->_renderMode;
args->_blitFramebuffer = cachedArgs->_blitFramebuffer;
args->_viewport = cachedArgs->_viewport;
args->_displayMode = cachedArgs->_displayMode;
args->_renderMode = cachedArgs->_renderMode;
args->_stencilMaskMode = cachedArgs->_stencilMaskMode;
}
args->popViewFrustum();

View file

@ -727,7 +727,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
boxHit._distance = FLT_MAX;
for (size_t i = 0; i < hit._boundJoints.size(); i++) {
assert(hit._boundJoints[i] < multiSpheres.size());
assert(hit._boundJoints[i] < (int)multiSpheres.size());
auto &mSphere = multiSpheres[hit._boundJoints[i]];
if (mSphere.isValid()) {
float boundDistance = FLT_MAX;

View file

@ -1630,7 +1630,9 @@ void MyAvatar::handleChangedAvatarEntityData() {
if (!skip) {
sanitizeAvatarEntityProperties(properties);
entityTree->withWriteLock([&] {
entityTree->updateEntity(id, properties);
if (entityTree->updateEntity(id, properties)) {
packetSender->queueEditAvatarEntityMessage(entityTree, id);
}
});
}
}
@ -5811,12 +5813,19 @@ void MyAvatar::releaseGrab(const QUuid& grabID) {
}
void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr<Avatar>& otherAvatar) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "addAvatarHandsToFlow",
Q_ARG(const std::shared_ptr<Avatar>&, otherAvatar));
return;
}
auto &flow = _skeletonModel->getRig().getFlow();
for (auto &handJointName : HAND_COLLISION_JOINTS) {
int jointIndex = otherAvatar->getJointIndex(handJointName);
if (jointIndex != -1) {
glm::vec3 position = otherAvatar->getJointPosition(jointIndex);
flow.setOthersCollision(otherAvatar->getID(), jointIndex, position);
if (otherAvatar != nullptr && flow.getActive()) {
for (auto &handJointName : HAND_COLLISION_JOINTS) {
int jointIndex = otherAvatar->getJointIndex(handJointName);
if (jointIndex != -1) {
glm::vec3 position = otherAvatar->getJointPosition(jointIndex);
flow.setOthersCollision(otherAvatar->getID(), jointIndex, position);
}
}
}
}

View file

@ -41,326 +41,246 @@
#include "ui/SecurityImageProvider.h"
#include "scripting/HMDScriptingInterface.h"
static const char* KEY_FILE = "hifikey";
static const char* INSTRUCTIONS_FILE = "backup_instructions.html";
static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n";
static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n";
namespace {
const char* KEY_FILE = "hifikey";
const char* INSTRUCTIONS_FILE = "backup_instructions.html";
const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n";
const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n";
void initialize() {
static bool initialized = false;
if (!initialized) {
SSL_load_error_strings();
SSL_library_init();
OpenSSL_add_all_algorithms();
initialized = true;
}
}
QString keyFilePath() {
auto accountManager = DependencyManager::get<AccountManager>();
return PathUtils::getAppDataFilePath(QString("%1.%2").arg(accountManager->getAccountInfo().getUsername(), KEY_FILE));
}
bool Wallet::copyKeyFileFrom(const QString& pathname) {
QString existing = getKeyFilePath();
qCDebug(commerce) << "Old keyfile" << existing;
if (!existing.isEmpty()) {
QString backup = QString(existing).insert(existing.indexOf(KEY_FILE) - 1,
QDateTime::currentDateTime().toString(Qt::ISODate).replace(":", ""));
qCDebug(commerce) << "Renaming old keyfile to" << backup;
if (!QFile::rename(existing, backup)) {
qCCritical(commerce) << "Unable to backup" << existing << "to" << backup;
return false;
void initialize() {
static bool initialized = false;
if (!initialized) {
SSL_load_error_strings();
SSL_library_init();
OpenSSL_add_all_algorithms();
initialized = true;
}
}
QString destination = keyFilePath();
bool result = QFile::copy(pathname, destination);
qCDebug(commerce) << "copy" << pathname << "to" << destination << "=>" << result;
return result;
}
// use the cached _passphrase if it exists, otherwise we need to prompt
int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
// just return a hardcoded pwd for now
auto wallet = DependencyManager::get<Wallet>();
auto passphrase = wallet->getPassphrase();
if (passphrase && !passphrase->isEmpty()) {
QString saltedPassphrase(*passphrase);
saltedPassphrase.append(wallet->getSalt());
strcpy(password, saltedPassphrase.toUtf8().constData());
return static_cast<int>(passphrase->size());
} else {
// this shouldn't happen - so lets log it to tell us we have
// a problem with the flow...
qCCritical(commerce) << "no cached passphrase while decrypting!";
return 0;
QString keyFilePath() {
auto accountManager = DependencyManager::get<AccountManager>();
return PathUtils::getAppDataFilePath(QString("%1.%2").arg(accountManager->getAccountInfo().getUsername(), KEY_FILE));
}
}
EC_KEY* readKeys(QString filename) {
QFile file(filename);
EC_KEY* key = NULL;
if (file.open(QFile::ReadOnly)) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
// use the cached _passphrase if it exists, otherwise we need to prompt
int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
// just return a hardcoded pwd for now
auto wallet = DependencyManager::get<Wallet>();
auto passphrase = wallet->getPassphrase();
if (passphrase && !passphrase->isEmpty()) {
QString saltedPassphrase(*passphrase);
saltedPassphrase.append(wallet->getSalt());
strcpy(password, saltedPassphrase.toUtf8().constData());
return static_cast<int>(passphrase->size());
} else {
// this shouldn't happen - so lets log it to tell us we have
// a problem with the flow...
qCCritical(commerce) << "no cached passphrase while decrypting!";
return 0;
}
}
QByteArray pemKeyBytes = file.readAll();
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
if ((key = PEM_read_bio_EC_PUBKEY(bufio, NULL, NULL, NULL))) {
// now read private key
EC_KEY* readKeys(QString filename) {
QFile file(filename);
EC_KEY* key = NULL;
if (file.open(QFile::ReadOnly)) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
qCDebug(commerce) << "read public key";
QByteArray pemKeyBytes = file.readAll();
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
if ((key = PEM_read_bio_EC_PUBKEY(bufio, NULL, NULL, NULL))) {
// now read private key
if ((key = PEM_read_bio_ECPrivateKey(bufio, &key, passwordCallback, NULL))) {
qCDebug(commerce) << "read private key";
BIO_free(bufio);
file.close();
qCDebug(commerce) << "read public key";
if ((key = PEM_read_bio_ECPrivateKey(bufio, &key, passwordCallback, NULL))) {
qCDebug(commerce) << "read private key";
} else {
qCDebug(commerce) << "failed to read private key";
}
} else {
qCDebug(commerce) << "failed to read private key";
qCDebug(commerce) << "failed to read public key";
}
} else {
qCDebug(commerce) << "failed to read public key";
}
BIO_free(bufio);
file.close();
} else {
qCDebug(commerce) << "failed to open key file" << filename;
}
return key;
}
bool Wallet::writeBackupInstructions() {
QString inputFilename(PathUtils::resourcesPath() + "html/commerce/backup_instructions.html");
QString outputFilename = PathUtils::getAppDataFilePath(INSTRUCTIONS_FILE);
QFile inputFile(inputFilename);
QFile outputFile(outputFilename);
bool retval = false;
if (getKeyFilePath().isEmpty()) {
return false;
}
if (QFile::exists(inputFilename) && inputFile.open(QIODevice::ReadOnly)) {
if (outputFile.open(QIODevice::ReadWrite)) {
// Read the data from the original file, then close it
QByteArray fileData = inputFile.readAll();
inputFile.close();
// Translate the data from the original file into a QString
QString text(fileData);
// Replace the necessary string
text.replace(QString("HIFIKEY_PATH_REPLACEME"), keyFilePath());
// Write the new text back to the file
outputFile.write(text.toUtf8());
// Close the output file
outputFile.close();
retval = true;
qCDebug(commerce) << "wrote html file successfully";
} else {
qCDebug(commerce) << "failed to open output html file" << outputFilename;
}
} else {
qCDebug(commerce) << "failed to open input html file" << inputFilename;
}
return retval;
}
bool writeKeys(QString filename, EC_KEY* keys) {
BIO* bio = BIO_new(BIO_s_mem());
bool retval = false;
if (!PEM_write_bio_EC_PUBKEY(bio, keys)) {
BIO_free(bio);
qCCritical(commerce) << "failed to write public key";
return retval;
}
if (!PEM_write_bio_ECPrivateKey(bio, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
BIO_free(bio);
qCCritical(commerce) << "failed to write private key";
return retval;
}
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) {
const char* bio_data;
long bio_size = BIO_get_mem_data(bio, &bio_data);
QByteArray keyBytes(bio_data, bio_size);
file.write(keyBytes);
retval = true;
qCDebug(commerce) << "wrote keys successfully";
file.close();
} else {
qCDebug(commerce) << "failed to open key file" << filename;
}
BIO_free(bio);
return retval;
}
bool Wallet::setWallet(const QByteArray& wallet) {
QFile file(keyFilePath());
if (!file.open(QIODevice::WriteOnly)) {
qCCritical(commerce) << "Unable to open wallet for write in" << keyFilePath();
return false;
}
if (file.write(wallet) != wallet.count()) {
qCCritical(commerce) << "Unable to write wallet in" << keyFilePath();
return false;
}
file.close();
return true;
}
QByteArray Wallet::getWallet() {
QFile file(keyFilePath());
if (!file.open(QIODevice::ReadOnly)) {
qCInfo(commerce) << "No existing wallet in" << keyFilePath();
return QByteArray();
}
QByteArray wallet = file.readAll();
file.close();
return wallet;
}
QPair<QByteArray*, QByteArray*> generateECKeypair() {
EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1);
QPair<QByteArray*, QByteArray*> retval{};
EC_KEY_set_asn1_flag(keyPair, OPENSSL_EC_NAMED_CURVE);
if (!EC_KEY_generate_key(keyPair)) {
qCDebug(commerce) << "Error generating EC Keypair -" << ERR_get_error();
return retval;
}
// grab the public key and private key from the file
unsigned char* publicKeyDER = NULL;
int publicKeyLength = i2d_EC_PUBKEY(keyPair, &publicKeyDER);
unsigned char* privateKeyDER = NULL;
int privateKeyLength = i2d_ECPrivateKey(keyPair, &privateKeyDER);
if (publicKeyLength <= 0 || privateKeyLength <= 0) {
qCDebug(commerce) << "Error getting DER public or private key from EC struct -" << ERR_get_error();
// cleanup the EC struct
EC_KEY_free(keyPair);
// cleanup the public and private key DER data, if required
if (publicKeyLength > 0) {
OPENSSL_free(publicKeyDER);
}
if (privateKeyLength > 0) {
OPENSSL_free(privateKeyDER);
}
return retval;
}
if (!writeKeys(keyFilePath(), keyPair)) {
qCDebug(commerce) << "couldn't save keys!";
return retval;
}
EC_KEY_free(keyPair);
// prepare the return values. TODO: Fix this - we probably don't really even want the
// private key at all (better to read it when we need it?). Or maybe we do, when we have
// multiple keys?
retval.first = new QByteArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength);
retval.second = new QByteArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength);
// cleanup the publicKeyDER and publicKeyDER data
OPENSSL_free(publicKeyDER);
OPENSSL_free(privateKeyDER);
return retval;
}
// END copied code (which will soon change)
// the public key can just go into a byte array
QByteArray readPublicKey(QString filename) {
QByteArray retval;
QFile file(filename);
if (file.open(QIODevice::ReadOnly)) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
QByteArray pemKeyBytes = file.readAll();
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
EC_KEY* key = PEM_read_bio_EC_PUBKEY(bufio, NULL, NULL, NULL);
if (key) {
// file read successfully
unsigned char* publicKeyDER = NULL;
int publicKeyLength = i2d_EC_PUBKEY(key, &publicKeyDER);
// TODO: check for 0 length?
// cleanup
EC_KEY_free(key);
qCDebug(commerce) << "parsed public key file successfully";
QByteArray retval((char*)publicKeyDER, publicKeyLength);
OPENSSL_free(publicKeyDER);
BIO_free(bufio);
file.close();
} else {
qCDebug(commerce) << "failed to open key file" << filename;
}
return key;
}
bool writeKeys(QString filename, EC_KEY* keys) {
BIO* bio = BIO_new(BIO_s_mem());
bool retval = false;
if (!PEM_write_bio_EC_PUBKEY(bio, keys)) {
BIO_free(bio);
qCCritical(commerce) << "failed to write public key";
return retval;
} else {
qCDebug(commerce) << "couldn't parse" << filename;
}
BIO_free(bufio);
file.close();
} else {
qCDebug(commerce) << "couldn't open" << filename;
}
return QByteArray();
}
// the private key should be read/copied into heap memory. For now, we need the EC_KEY struct
// so I'll return that.
EC_KEY* readPrivateKey(QString filename) {
QFile file(filename);
EC_KEY* key = NULL;
if (file.open(QIODevice::ReadOnly)) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
QByteArray pemKeyBytes = file.readAll();
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
if ((key = PEM_read_bio_ECPrivateKey(bufio, &key, passwordCallback, NULL))) {
qCDebug(commerce) << "parsed private key file successfully";
} else {
qCDebug(commerce) << "couldn't parse" << filename;
// if the passphrase is wrong, then let's not cache it
DependencyManager::get<Wallet>()->setPassphrase("");
if (!PEM_write_bio_ECPrivateKey(bio, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
BIO_free(bio);
qCCritical(commerce) << "failed to write private key";
return retval;
}
BIO_free(bufio);
file.close();
} else {
qCDebug(commerce) << "couldn't open" << filename;
}
return key;
}
// QT's QByteArray will convert to Base64 without any embedded newlines. This just
// writes it with embedded newlines, which is more readable.
void outputBase64WithNewlines(QFile& file, const QByteArray& b64Array) {
for (int i = 0; i < b64Array.size(); i += 64) {
file.write(b64Array.mid(i, 64));
file.write("\n");
}
}
QFile file(filename);
if (file.open(QIODevice::WriteOnly)) {
const char* bio_data;
long bio_size = BIO_get_mem_data(bio, &bio_data);
void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) {
// use the ones in the wallet
auto wallet = DependencyManager::get<Wallet>();
memcpy(ivec, wallet->getIv(), 16);
memcpy(ckey, wallet->getCKey(), 32);
}
QByteArray keyBytes(bio_data, bio_size);
file.write(keyBytes);
retval = true;
qCDebug(commerce) << "wrote keys successfully";
file.close();
} else {
qCDebug(commerce) << "failed to open key file" << filename;
}
BIO_free(bio);
return retval;
}
QPair<QByteArray*, QByteArray*> generateECKeypair() {
EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1);
QPair<QByteArray*, QByteArray*> retval {};
EC_KEY_set_asn1_flag(keyPair, OPENSSL_EC_NAMED_CURVE);
if (!EC_KEY_generate_key(keyPair)) {
qCDebug(commerce) << "Error generating EC Keypair -" << ERR_get_error();
return retval;
}
// grab the public key and private key from the file
unsigned char* publicKeyDER = NULL;
int publicKeyLength = i2d_EC_PUBKEY(keyPair, &publicKeyDER);
unsigned char* privateKeyDER = NULL;
int privateKeyLength = i2d_ECPrivateKey(keyPair, &privateKeyDER);
if (publicKeyLength <= 0 || privateKeyLength <= 0) {
qCDebug(commerce) << "Error getting DER public or private key from EC struct -" << ERR_get_error();
// cleanup the EC struct
EC_KEY_free(keyPair);
// cleanup the public and private key DER data, if required
if (publicKeyLength > 0) {
OPENSSL_free(publicKeyDER);
}
if (privateKeyLength > 0) {
OPENSSL_free(privateKeyDER);
}
return retval;
}
if (!writeKeys(keyFilePath(), keyPair)) {
qCDebug(commerce) << "couldn't save keys!";
return retval;
}
EC_KEY_free(keyPair);
// prepare the return values. TODO: Fix this - we probably don't really even want the
// private key at all (better to read it when we need it?). Or maybe we do, when we have
// multiple keys?
retval.first = new QByteArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength);
retval.second = new QByteArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength);
// cleanup the publicKeyDER and publicKeyDER data
OPENSSL_free(publicKeyDER);
OPENSSL_free(privateKeyDER);
return retval;
}
// END copied code (which will soon change)
// the public key can just go into a byte array
QByteArray readPublicKey(QString filename) {
QByteArray retval;
QFile file(filename);
if (file.open(QIODevice::ReadOnly)) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
QByteArray pemKeyBytes = file.readAll();
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
EC_KEY* key = PEM_read_bio_EC_PUBKEY(bufio, NULL, NULL, NULL);
if (key) {
// file read successfully
unsigned char* publicKeyDER = NULL;
int publicKeyLength = i2d_EC_PUBKEY(key, &publicKeyDER);
// TODO: check for 0 length?
// cleanup
EC_KEY_free(key);
qCDebug(commerce) << "parsed public key file successfully";
QByteArray retval((char*)publicKeyDER, publicKeyLength);
OPENSSL_free(publicKeyDER);
BIO_free(bufio);
file.close();
return retval;
} else {
qCDebug(commerce) << "couldn't parse" << filename;
}
BIO_free(bufio);
file.close();
} else {
qCDebug(commerce) << "couldn't open" << filename;
}
return QByteArray();
}
// the private key should be read/copied into heap memory. For now, we need the EC_KEY struct
// so I'll return that.
EC_KEY* readPrivateKey(QString filename) {
QFile file(filename);
EC_KEY* key = NULL;
if (file.open(QIODevice::ReadOnly)) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
QByteArray pemKeyBytes = file.readAll();
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
if ((key = PEM_read_bio_ECPrivateKey(bufio, &key, passwordCallback, NULL))) {
qCDebug(commerce) << "parsed private key file successfully";
} else {
qCDebug(commerce) << "couldn't parse" << filename;
// if the passphrase is wrong, then let's not cache it
DependencyManager::get<Wallet>()->setPassphrase("");
}
BIO_free(bufio);
file.close();
} else {
qCDebug(commerce) << "couldn't open" << filename;
}
return key;
}
// QT's QByteArray will convert to Base64 without any embedded newlines. This just
// writes it with embedded newlines, which is more readable.
void outputBase64WithNewlines(QFile& file, const QByteArray& b64Array) {
for (int i = 0; i < b64Array.size(); i += 64) {
file.write(b64Array.mid(i, 64));
file.write("\n");
}
}
void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) {
// use the ones in the wallet
auto wallet = DependencyManager::get<Wallet>();
memcpy(ivec, wallet->getIv(), 16);
memcpy(ckey, wallet->getCKey(), 32);
}
} // close unnamed namespace
Wallet::Wallet() {
auto nodeList = DependencyManager::get<NodeList>();
@ -423,6 +343,88 @@ Wallet::~Wallet() {
}
}
bool Wallet::setWallet(const QByteArray& wallet) {
QFile file(keyFilePath());
if (!file.open(QIODevice::WriteOnly)) {
qCCritical(commerce) << "Unable to open wallet for write in" << keyFilePath();
return false;
}
if (file.write(wallet) != wallet.count()) {
qCCritical(commerce) << "Unable to write wallet in" << keyFilePath();
return false;
}
file.close();
return true;
}
QByteArray Wallet::getWallet() {
QFile file(keyFilePath());
if (!file.open(QIODevice::ReadOnly)) {
qCInfo(commerce) << "No existing wallet in" << keyFilePath();
return QByteArray();
}
QByteArray wallet = file.readAll();
file.close();
return wallet;
}
bool Wallet::copyKeyFileFrom(const QString& pathname) {
QString existing = getKeyFilePath();
qCDebug(commerce) << "Old keyfile" << existing;
if (!existing.isEmpty()) {
QString backup = QString(existing).insert(existing.indexOf(KEY_FILE) - 1,
QDateTime::currentDateTime().toString(Qt::ISODate).replace(":", ""));
qCDebug(commerce) << "Renaming old keyfile to" << backup;
if (!QFile::rename(existing, backup)) {
qCCritical(commerce) << "Unable to backup" << existing << "to" << backup;
return false;
}
}
QString destination = keyFilePath();
bool result = QFile::copy(pathname, destination);
qCDebug(commerce) << "copy" << pathname << "to" << destination << "=>" << result;
return result;
}
bool Wallet::writeBackupInstructions() {
QString inputFilename(PathUtils::resourcesPath() + "html/commerce/backup_instructions.html");
QString outputFilename = PathUtils::getAppDataFilePath(INSTRUCTIONS_FILE);
QFile inputFile(inputFilename);
QFile outputFile(outputFilename);
bool retval = false;
if (getKeyFilePath().isEmpty()) {
return false;
}
if (QFile::exists(inputFilename) && inputFile.open(QIODevice::ReadOnly)) {
if (outputFile.open(QIODevice::ReadWrite)) {
// Read the data from the original file, then close it
QByteArray fileData = inputFile.readAll();
inputFile.close();
// Translate the data from the original file into a QString
QString text(fileData);
// Replace the necessary string
text.replace(QString("HIFIKEY_PATH_REPLACEME"), keyFilePath());
// Write the new text back to the file
outputFile.write(text.toUtf8());
// Close the output file
outputFile.close();
retval = true;
qCDebug(commerce) << "wrote html file successfully";
} else {
qCDebug(commerce) << "failed to open output html file" << outputFilename;
}
} else {
qCDebug(commerce) << "failed to open input html file" << inputFilename;
}
return retval;
}
bool Wallet::setPassphrase(const QString& passphrase) {
if (_passphrase) {
delete _passphrase;

View file

@ -244,6 +244,7 @@ void GraphicsEngine::render_performFrame() {
finalFramebuffer = framebufferCache->getFramebuffer();
}
std::queue<Application::SnapshotOperator> snapshotOperators;
if (!_programsCompiled.load()) {
gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {
batch.setFramebuffer(finalFramebuffer);
@ -271,6 +272,7 @@ void GraphicsEngine::render_performFrame() {
PROFILE_RANGE(render, "/runRenderFrame");
renderArgs._hudOperator = displayPlugin->getHUDOperator();
renderArgs._hudTexture = qApp->getApplicationOverlay().getOverlayTexture();
renderArgs._takingSnapshot = qApp->takeSnapshotOperators(snapshotOperators);
renderArgs._blitFramebuffer = finalFramebuffer;
render_runRenderFrame(&renderArgs);
}
@ -285,6 +287,7 @@ void GraphicsEngine::render_performFrame() {
frameBufferCache->releaseFramebuffer(framebuffer);
}
};
frame->snapshotOperators = snapshotOperators;
// deliver final scene rendering commands to the display plugin
{
PROFILE_RANGE(render, "/pluginOutput");

View file

@ -174,14 +174,10 @@ void Audio::setPTTDesktop(bool enabled) {
_pttDesktop = enabled;
}
});
if (!enabled) {
// Set to default behavior (unmuted for Desktop) on Push-To-Talk disable.
setMutedDesktop(true);
} else {
// Should be muted when not pushing to talk while PTT is enabled.
if (enabled || _settingsLoaded) {
// Set to default behavior (muted for Desktop) on Push-To-Talk disable or when enabled. Settings also need to be loaded.
setMutedDesktop(true);
}
if (changed) {
emit pushToTalkChanged(enabled);
emit pushToTalkDesktopChanged(enabled);
@ -202,12 +198,9 @@ void Audio::setPTTHMD(bool enabled) {
_pttHMD = enabled;
}
});
if (!enabled) {
// Set to default behavior (unmuted for HMD) on Push-To-Talk disable.
setMutedHMD(false);
} else {
// Should be muted when not pushing to talk while PTT is enabled.
setMutedHMD(true);
if (enabled || _settingsLoaded) {
// Set to default behavior (unmuted for HMD) on Push-To-Talk disable or muted for when PTT is enabled.
setMutedHMD(enabled);
}
if (changed) {
@ -231,6 +224,7 @@ void Audio::loadData() {
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted()), Q_ARG(bool, false));
_settingsLoaded = true;
}
bool Audio::getPTTHMD() const {
@ -357,10 +351,12 @@ void Audio::onContextChanged() {
changed = true;
}
});
if (isHMD) {
setMuted(getMutedHMD());
} else {
setMuted(getMutedDesktop());
if (_settingsLoaded) {
bool isMuted = isHMD ? getMutedHMD() : getMutedDesktop();
setMuted(isMuted);
// always set audio client muted state on context changed - sometimes setMuted does not catch it.
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false));
}
if (changed) {
emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP);

View file

@ -409,6 +409,7 @@ protected:
private:
bool _settingsLoaded { false };
float _inputVolume { 1.0f };
float _inputLevel { 0.0f };
float _localInjectorGain { 0.0f }; // in dB

View file

@ -27,6 +27,7 @@
#include "MainWindow.h"
#include "Menu.h"
#include "OffscreenUi.h"
#include "commerce/QmlCommerce.h"
static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
static const QString LAST_BROWSE_LOCATION_SETTING = "LastBrowseLocation";
@ -134,15 +135,17 @@ void WindowScriptingInterface::disconnectedFromDomain() {
void WindowScriptingInterface::openUrl(const QUrl& url) {
if (!url.isEmpty()) {
if (url.scheme() == URL_SCHEME_HIFI) {
auto scheme = url.scheme();
if (scheme == URL_SCHEME_HIFI) {
DependencyManager::get<AddressManager>()->handleLookupString(url.toString());
} else if (scheme == URL_SCHEME_HIFIAPP) {
DependencyManager::get<QmlCommerce>()->openSystemApp(url.path());
} else {
#if defined(Q_OS_ANDROID)
QMap<QString, QString> args;
args["url"] = url.toString();
AndroidHelper::instance().requestActivity("WebView", true, args);
#else
// address manager did not handle - ask QDesktopServices to handle
QDesktopServices::openUrl(url);
#endif
}

View file

@ -535,9 +535,10 @@ public slots:
int openMessageBox(QString title, QString text, int buttons, int defaultButton);
/**jsdoc
* Open a URL in the Interface window or other application, depending on the URL's scheme. If the URL starts with
* <code>hifi://</code> then that URL is navigated to in Interface, otherwise the URL is opened in the application the OS
* associates with the URL's scheme (e.g., a Web browser for <code>http://</code>).
* Open a URL in the Interface window or other application, depending on the URL's scheme. The following schemes are supported:
* <code>hifi</code> (navigate to the URL in Interface), <code>hifiapp<code> (open a system app in Interface). Other schemes will either be handled by the OS
* (e.g. <code>http</code>, <code>https</code>, <code>mailto</code>) or will create a confirmation dialog asking the user to confirm that they want to try to open
* the URL.
* @function Window.openUrl
* @param {string} url - The URL to open.
*/

View file

@ -138,7 +138,7 @@ void LoginDialog::login(const QString& username, const QString& password) const
void LoginDialog::loginThroughOculus() {
qDebug() << "Attempting to login through Oculus";
if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) {
oculusPlatformPlugin->requestNonceAndUserID([] (QString nonce, QString oculusID) {
DependencyManager::get<AccountManager>()->requestAccessTokenWithOculus(nonce, oculusID);
});
}
@ -279,10 +279,6 @@ void LoginDialog::createAccountFromSteam(QString username) {
}
}
void LoginDialog::openUrl(const QString& url) const {
QDesktopServices::openUrl(QUrl(url));
}
void LoginDialog::linkCompleted(QNetworkReply* reply) {
emit handleLinkCompleted();
}

View file

@ -80,8 +80,6 @@ protected slots:
Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password);
Q_INVOKABLE void openUrl(const QString& url) const;
Q_INVOKABLE bool getLoginDialogPoppedUp() const;
};

View file

@ -159,47 +159,57 @@ void Snapshot::save360Snapshot(const glm::vec3& cameraPosition,
secondaryCameraRenderConfig->setOrientation(CAMERA_ORIENTATION_DOWN);
_snapshotIndex = 0;
_taking360Snapshot = true;
_snapshotTimer.start(SNAPSHOT_360_TIMER_INTERVAL);
}
void Snapshot::takeNextSnapshot() {
SecondaryCameraJobConfig* config =
static_cast<SecondaryCameraJobConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
if (_taking360Snapshot) {
if (!_waitingOnSnapshot) {
_waitingOnSnapshot = true;
qApp->addSnapshotOperator(std::make_tuple([this](const QImage& snapshot) {
// Order is:
// 0. Down
// 1. Front
// 2. Left
// 3. Back
// 4. Right
// 5. Up
if (_snapshotIndex < 6) {
_imageArray[_snapshotIndex] = snapshot;
}
// Order is:
// 0. Down
// 1. Front
// 2. Left
// 3. Back
// 4. Right
// 5. Up
if (_snapshotIndex < 6) {
_imageArray[_snapshotIndex] = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
}
SecondaryCameraJobConfig* config = static_cast<SecondaryCameraJobConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
if (_snapshotIndex == 0) {
// Setup for Front Image capture
config->setOrientation(CAMERA_ORIENTATION_FRONT);
} else if (_snapshotIndex == 1) {
// Setup for Left Image capture
config->setOrientation(CAMERA_ORIENTATION_LEFT);
} else if (_snapshotIndex == 2) {
// Setup for Back Image capture
config->setOrientation(CAMERA_ORIENTATION_BACK);
} else if (_snapshotIndex == 3) {
// Setup for Right Image capture
config->setOrientation(CAMERA_ORIENTATION_RIGHT);
} else if (_snapshotIndex == 4) {
// Setup for Up Image capture
config->setOrientation(CAMERA_ORIENTATION_UP);
} else if (_snapshotIndex == 5) {
_taking360Snapshot = false;
}
if (_snapshotIndex == 0) {
// Setup for Front Image capture
config->setOrientation(CAMERA_ORIENTATION_FRONT);
} else if (_snapshotIndex == 1) {
// Setup for Left Image capture
config->setOrientation(CAMERA_ORIENTATION_LEFT);
} else if (_snapshotIndex == 2) {
// Setup for Back Image capture
config->setOrientation(CAMERA_ORIENTATION_BACK);
} else if (_snapshotIndex == 3) {
// Setup for Right Image capture
config->setOrientation(CAMERA_ORIENTATION_RIGHT);
} else if (_snapshotIndex == 4) {
// Setup for Up Image capture
config->setOrientation(CAMERA_ORIENTATION_UP);
} else if (_snapshotIndex > 5) {
_waitingOnSnapshot = false;
_snapshotIndex++;
}, 0.0f, false));
}
} else {
_snapshotTimer.stop();
// Reset secondary camera render config
static_cast<ToneMappingConfig*>(
qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))
->setCurve(1);
SecondaryCameraJobConfig* config = static_cast<SecondaryCameraJobConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
static_cast<ToneMappingConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(1);
config->resetSizeSpectatorCamera(qApp->getWindow()->geometry().width(), qApp->getWindow()->geometry().height());
config->setProperty("attachedEntityId", _oldAttachedEntityId);
config->setProperty("vFoV", _oldvFoV);
@ -217,8 +227,6 @@ void Snapshot::takeNextSnapshot() {
QtConcurrent::run([this]() { convertToEquirectangular(); });
}
}
_snapshotIndex++;
}
void Snapshot::convertToCubemap() {

View file

@ -97,6 +97,8 @@ private:
bool _cubemapOutputFormat;
QTimer _snapshotTimer;
qint16 _snapshotIndex;
bool _waitingOnSnapshot { false };
bool _taking360Snapshot { false };
bool _oldEnabled;
QVariant _oldAttachedEntityId;
QVariant _oldOrientation;

View file

@ -27,7 +27,6 @@ QString SnapshotAnimated::snapshotAnimatedPath;
QString SnapshotAnimated::snapshotStillPath;
QVector<QImage> SnapshotAnimated::snapshotAnimatedFrameVector;
QVector<qint64> SnapshotAnimated::snapshotAnimatedFrameDelayVector;
Application* SnapshotAnimated::app;
float SnapshotAnimated::aspectRatio;
QSharedPointer<WindowScriptingInterface> SnapshotAnimated::snapshotAnimatedDM;
GifWriter SnapshotAnimated::snapshotAnimatedGifWriter;
@ -36,12 +35,11 @@ GifWriter SnapshotAnimated::snapshotAnimatedGifWriter;
Setting::Handle<bool> SnapshotAnimated::alsoTakeAnimatedSnapshot("alsoTakeAnimatedSnapshot", true);
Setting::Handle<float> SnapshotAnimated::snapshotAnimatedDuration("snapshotAnimatedDuration", SNAPSNOT_ANIMATED_DURATION_SECS);
void SnapshotAnimated::saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer<WindowScriptingInterface> dm) {
void SnapshotAnimated::saveSnapshotAnimated(QString pathStill, float aspectRatio, QSharedPointer<WindowScriptingInterface> dm) {
// If we're not in the middle of capturing an animated snapshot...
if (SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp == 0) {
SnapshotAnimated::snapshotAnimatedTimer = new QTimer();
SnapshotAnimated::aspectRatio = aspectRatio;
SnapshotAnimated::app = app;
SnapshotAnimated::snapshotAnimatedDM = dm;
// Define the output location of the still and animated snapshots.
SnapshotAnimated::snapshotStillPath = pathStill;
@ -62,44 +60,45 @@ void SnapshotAnimated::saveSnapshotAnimated(QString pathStill, float aspectRatio
void SnapshotAnimated::captureFrames() {
if (SnapshotAnimated::snapshotAnimatedTimerRunning) {
// Get a screenshot from the display, then scale the screenshot down,
// then convert it to the image format the GIF library needs,
// then save all that to the QImage named "frame"
QImage frame(SnapshotAnimated::app->getActiveDisplayPlugin()->getScreenshot(SnapshotAnimated::aspectRatio));
frame = frame.scaledToWidth(SNAPSNOT_ANIMATED_WIDTH);
SnapshotAnimated::snapshotAnimatedFrameVector.append(frame);
qApp->addSnapshotOperator(std::make_tuple([](const QImage& snapshot) {
// Get a screenshot from the display, then scale the screenshot down,
// then convert it to the image format the GIF library needs,
// then save all that to the QImage named "frame"
QImage frame = snapshot.scaledToWidth(SNAPSNOT_ANIMATED_WIDTH);
SnapshotAnimated::snapshotAnimatedFrameVector.append(frame);
// If that was the first frame...
if (SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp == 0) {
// Record the current frame timestamp
SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
// Record the first frame timestamp
SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = SnapshotAnimated::snapshotAnimatedTimestamp;
SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10);
// If this is an intermediate or the final frame...
} else {
// Push the current frame delay onto the vector
SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(round(((float)(QDateTime::currentMSecsSinceEpoch() - SnapshotAnimated::snapshotAnimatedTimestamp)) / 10));
// Record the current frame timestamp
SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
// If that was the first frame...
if (SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp == 0) {
// Record the current frame timestamp
SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
// Record the first frame timestamp
SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = SnapshotAnimated::snapshotAnimatedTimestamp;
SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10);
// If this is an intermediate or the final frame...
} else {
// Push the current frame delay onto the vector
SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(round(((float)(QDateTime::currentMSecsSinceEpoch() - SnapshotAnimated::snapshotAnimatedTimestamp)) / 10));
// Record the current frame timestamp
SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
// If that was the last frame...
if ((SnapshotAnimated::snapshotAnimatedTimestamp - SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp) >= (SnapshotAnimated::snapshotAnimatedDuration.get() * MSECS_PER_SECOND)) {
SnapshotAnimated::snapshotAnimatedTimerRunning = false;
// Notify the user that we're processing the snapshot
// This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called.
emit SnapshotAnimated::snapshotAnimatedDM->processingGifStarted(SnapshotAnimated::snapshotStillPath);
// Kick off the thread that'll pack the frames into the GIF
QtConcurrent::run(processFrames);
// Stop the snapshot QTimer. This action by itself DOES NOT GUARANTEE
// that the slot will not be called again in the future.
// See: http://lists.qt-project.org/pipermail/qt-interest-old/2009-October/013926.html
SnapshotAnimated::snapshotAnimatedTimer->stop();
delete SnapshotAnimated::snapshotAnimatedTimer;
// If that was the last frame...
if ((SnapshotAnimated::snapshotAnimatedTimestamp - SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp) >= (SnapshotAnimated::snapshotAnimatedDuration.get() * MSECS_PER_SECOND)) {
SnapshotAnimated::snapshotAnimatedTimerRunning = false;
}
}
}
}, SnapshotAnimated::aspectRatio, true));
} else {
// Notify the user that we're processing the snapshot
// This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called.
emit SnapshotAnimated::snapshotAnimatedDM->processingGifStarted(SnapshotAnimated::snapshotStillPath);
// Kick off the thread that'll pack the frames into the GIF
QtConcurrent::run(processFrames);
// Stop the snapshot QTimer. This action by itself DOES NOT GUARANTEE
// that the slot will not be called again in the future.
// See: http://lists.qt-project.org/pipermail/qt-interest-old/2009-October/013926.html
SnapshotAnimated::snapshotAnimatedTimer->stop();
delete SnapshotAnimated::snapshotAnimatedTimer;
}
}

View file

@ -42,7 +42,6 @@ private:
static QVector<QImage> snapshotAnimatedFrameVector;
static QVector<qint64> snapshotAnimatedFrameDelayVector;
static QSharedPointer<WindowScriptingInterface> snapshotAnimatedDM;
static Application* app;
static float aspectRatio;
static GifWriter snapshotAnimatedGifWriter;
@ -51,7 +50,7 @@ private:
static void processFrames();
static void clearTempVariables();
public:
static void saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer<WindowScriptingInterface> dm);
static void saveSnapshotAnimated(QString pathStill, float aspectRatio, QSharedPointer<WindowScriptingInterface> dm);
static bool isAlreadyTakingSnapshotAnimated() { return snapshotAnimatedFirstFrameTimestamp != 0; };
static Setting::Handle<bool> alsoTakeAnimatedSnapshot;
static Setting::Handle<float> snapshotAnimatedDuration;

View file

@ -261,7 +261,7 @@ public:
qCDebug(animation) << " " << pair.first << "=" << pair.second.getString();
break;
default:
assert(("invalid AnimVariant::Type", false));
assert(false);
}
}
}

View file

@ -142,6 +142,27 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector<hfm::Mesh>& meshes, const
} else if (object->name == "Texture" || object->name == "Video") {
// this is an embedded texture, we need to remove it from the FBX
object = rootChild.children.erase(object);
} else if (object->name == "Material") {
for (FBXNode& materialChild : object->children) {
if (materialChild.name == "Properties60" || materialChild.name == "Properties70") {
// This is a properties node
// Remove the material texture scale because that is now included in the material JSON
// Texture nodes are removed, so their texture scale is effectively gone already
static const QVariant MAYA_UV_SCALE = hifi::ByteArray("Maya|uv_scale");
static const QVariant MAYA_UV_OFFSET = hifi::ByteArray("Maya|uv_offset");
for (int i = 0; i < materialChild.children.size(); i++) {
const auto& prop = materialChild.children[i];
const auto& propertyName = prop.properties.at(0);
if (propertyName == MAYA_UV_SCALE ||
propertyName == MAYA_UV_OFFSET) {
materialChild.children.removeAt(i);
--i;
}
}
}
}
object++;
} else {
object++;
}

View file

@ -276,4 +276,8 @@ void MaterialBaker::setMaterials(const QHash<QString, hfm::Material>& materials,
addTexture(material.name, image::TextureUsage::SCATTERING_TEXTURE, material.scatteringTexture);
addTexture(material.name, image::TextureUsage::LIGHTMAP_TEXTURE, material.lightmapTexture);
}
}
void MaterialBaker::setMaterials(const NetworkMaterialResourcePointer& materialResource) {
_materialResource = materialResource;
}

View file

@ -31,6 +31,9 @@ public:
QString getBakedMaterialData() const { return _bakedMaterialData; }
void setMaterials(const QHash<QString, hfm::Material>& materials, const QString& baseURL);
void setMaterials(const NetworkMaterialResourcePointer& materialResource);
NetworkMaterialResourcePointer getNetworkMaterialResource() const { return _materialResource; }
static void setNextOvenWorkerThreadOperator(std::function<QThread*()> getNextOvenWorkerThreadOperator) { _getNextOvenWorkerThreadOperator = getNextOvenWorkerThreadOperator; }

View file

@ -241,14 +241,12 @@ void ModelBaker::bakeSourceCopy() {
config->getJobConfig("BuildDracoMesh")->setEnabled(true);
// Do not permit potentially lossy modification of joint data meant for runtime
((PrepareJointsConfig*)config->getJobConfig("PrepareJoints"))->passthrough = true;
// The resources parsed from this job will not be used for now
// TODO: Proper full baking of all materials for a model
config->getJobConfig("ParseMaterialMapping")->setEnabled(false);
// Begin hfm baking
baker.run();
_hfmModel = baker.getHFMModel();
_materialMapping = baker.getMaterialMapping();
dracoMeshes = baker.getDracoMeshes();
dracoMaterialLists = baker.getDracoMaterialLists();
}
@ -260,7 +258,7 @@ void ModelBaker::bakeSourceCopy() {
return;
}
if (_hfmModel->materials.size() > 0) {
if (!_hfmModel->materials.isEmpty()) {
_materialBaker = QSharedPointer<MaterialBaker>(
new MaterialBaker(_modelURL.fileName(), true, _bakedOutputDir),
&MaterialBaker::deleteLater
@ -269,7 +267,7 @@ void ModelBaker::bakeSourceCopy() {
connect(_materialBaker.data(), &MaterialBaker::finished, this, &ModelBaker::handleFinishedMaterialBaker);
_materialBaker->bake();
} else {
outputBakedFST();
bakeMaterialMap();
}
}
@ -285,26 +283,14 @@ void ModelBaker::handleFinishedMaterialBaker() {
auto baseName = relativeBakedMaterialURL.left(relativeBakedMaterialURL.lastIndexOf('.'));
relativeBakedMaterialURL = baseName + BAKED_MATERIAL_EXTENSION;
// First we add the materials in the model
QJsonArray materialMapping;
for (auto material : _hfmModel->materials) {
QJsonObject json;
json["mat::" + material.name] = relativeBakedMaterialURL + "#" + material.name;
materialMapping.push_back(json);
}
// The we add any existing mappings from the mapping
if (_mapping.contains(MATERIAL_MAPPING_FIELD)) {
QByteArray materialMapValue = _mapping[MATERIAL_MAPPING_FIELD].toByteArray();
QJsonObject oldMaterialMapping = QJsonDocument::fromJson(materialMapValue).object();
for (auto key : oldMaterialMapping.keys()) {
auto materialResource = baker->getNetworkMaterialResource();
if (materialResource) {
for (auto materialName : materialResource->parsedMaterials.names) {
QJsonObject json;
json[key] = oldMaterialMapping[key];
materialMapping.push_back(json);
json[QString("mat::" + QString(materialName.c_str()))] = relativeBakedMaterialURL + "#" + materialName.c_str();
_materialMappingJSON.push_back(json);
}
}
_mapping[MATERIAL_MAPPING_FIELD] = QJsonDocument(materialMapping).toJson(QJsonDocument::Compact);
} else {
// this material failed to bake - this doesn't fail the entire bake but we need to add the errors from
// the material to our warnings
@ -314,7 +300,62 @@ void ModelBaker::handleFinishedMaterialBaker() {
handleWarning("Failed to bake the materials for model with URL " + _modelURL.toString());
}
outputBakedFST();
bakeMaterialMap();
}
void ModelBaker::bakeMaterialMap() {
if (!_materialMapping.empty()) {
// TODO: The existing material map must be baked in order, so we do it all on this thread to preserve the order.
// It could be spread over multiple threads if we had a good way of preserving the order once all of the bakers are done
_materialBaker = QSharedPointer<MaterialBaker>(
new MaterialBaker("materialMap" + QString::number(_materialMapIndex++), true, _bakedOutputDir),
&MaterialBaker::deleteLater
);
_materialBaker->setMaterials(_materialMapping.front().second);
connect(_materialBaker.data(), &MaterialBaker::finished, this, &ModelBaker::handleFinishedMaterialMapBaker);
_materialBaker->bake();
} else {
outputBakedFST();
}
}
void ModelBaker::handleFinishedMaterialMapBaker() {
auto baker = qobject_cast<MaterialBaker*>(sender());
if (baker) {
if (!baker->hasErrors()) {
// this MaterialBaker is done and everything went according to plan
qCDebug(model_baking) << "Adding baked material to FST mapping " << baker->getBakedMaterialData();
QString materialName;
{
auto materialResource = baker->getNetworkMaterialResource();
if (materialResource) {
auto url = materialResource->getURL();
if (!url.isEmpty()) {
QString urlString = url.toDisplayString();
auto index = urlString.lastIndexOf("#");
if (index != -1) {
materialName = urlString.right(urlString.length() - index);
}
}
}
}
QJsonObject json;
json[QString(_materialMapping.front().first.c_str())] = baker->getMaterialData() + BAKED_MATERIAL_EXTENSION + materialName;
_materialMappingJSON.push_back(json);
} else {
// this material failed to bake - this doesn't fail the entire bake but we need to add the errors from
// the material to our warnings
_warningList << baker->getWarnings();
}
} else {
handleWarning("Failed to bake the materialMap for model with URL " + _modelURL.toString() + " and mapping target " + _materialMapping.front().first.c_str());
}
_materialMapping.erase(_materialMapping.begin());
bakeMaterialMap();
}
void ModelBaker::outputUnbakedFST() {
@ -363,6 +404,9 @@ void ModelBaker::outputBakedFST() {
outputMapping[FILENAME_FIELD] = _bakedModelURL.fileName();
outputMapping.remove(TEXDIR_FIELD);
outputMapping.remove(COMMENT_FIELD);
if (!_materialMappingJSON.isEmpty()) {
outputMapping[MATERIAL_MAPPING_FIELD] = QJsonDocument(_materialMappingJSON).toJson(QJsonDocument::Compact);
}
hifi::ByteArray fstOut = FSTReader::writeMapping(outputMapping);
QFile fstOutputFile { outputFSTURL };

View file

@ -16,6 +16,7 @@
#include <QtCore/QDir>
#include <QtCore/QUrl>
#include <QtNetwork/QNetworkReply>
#include <QJsonArray>
#include "Baker.h"
#include "MaterialBaker.h"
@ -80,14 +81,19 @@ protected slots:
void handleModelNetworkReply();
virtual void bakeSourceCopy();
void handleFinishedMaterialBaker();
void handleFinishedMaterialMapBaker();
private:
void outputUnbakedFST();
void outputBakedFST();
void bakeMaterialMap();
bool _hasBeenBaked { false };
hfm::Model::Pointer _hfmModel;
MaterialMapping _materialMapping;
int _materialMapIndex { 0 };
QJsonArray _materialMappingJSON;
QSharedPointer<MaterialBaker> _materialBaker;
};

View file

@ -25,12 +25,4 @@ void NullDisplayPlugin::submitFrame(const gpu::FramePointer& frame) {
if (frame) {
_gpuContext->consumeFrameUpdates(frame);
}
}
QImage NullDisplayPlugin::getScreenshot(float aspectRatio) const {
return QImage();
}
QImage NullDisplayPlugin::getSecondaryCameraScreenshot() const {
return QImage();
}
}

View file

@ -17,8 +17,6 @@ public:
glm::uvec2 getRecommendedRenderSize() const override;
void submitFrame(const gpu::FramePointer& newFrame) override;
QImage getScreenshot(float aspectRatio = 0.0f) const override;
QImage getSecondaryCameraScreenshot() const override;
void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {};
void pluginUpdate() override {};
private:

View file

@ -721,6 +721,19 @@ void OpenGLDisplayPlugin::present() {
compositeLayers();
}
{ // If we have any snapshots this frame, handle them
PROFILE_RANGE_EX(render, "snapshotOperators", 0xffff00ff, frameId)
while (!_currentFrame->snapshotOperators.empty()) {
auto& snapshotOperator = _currentFrame->snapshotOperators.front();
if (std::get<2>(snapshotOperator)) {
std::get<0>(snapshotOperator)(getScreenshot(std::get<1>(snapshotOperator)));
} else {
std::get<0>(snapshotOperator)(getSecondaryCameraScreenshot());
}
_currentFrame->snapshotOperators.pop();
}
}
// Take the composite framebuffer and send it to the output device
{
PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, frameId)
@ -785,7 +798,7 @@ bool OpenGLDisplayPlugin::setDisplayTexture(const QString& name) {
return !!_displayTexture;
}
QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) {
auto size = _compositeFramebuffer->getSize();
if (isHmd()) {
size.x /= 2;
@ -801,24 +814,18 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
corner.x = round((size.x - bestSize.x) / 2.0f);
corner.y = round((size.y - bestSize.y) / 2.0f);
}
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32);
withOtherThreadContext([&] {
glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot);
});
getGLBackend()->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot);
return screenshot.mirrored(false, true);
}
QImage OpenGLDisplayPlugin::getSecondaryCameraScreenshot() const {
QImage OpenGLDisplayPlugin::getSecondaryCameraScreenshot() {
auto textureCache = DependencyManager::get<TextureCache>();
auto secondaryCameraFramebuffer = textureCache->getSpectatorCameraFramebuffer();
gpu::Vec4i region(0, 0, secondaryCameraFramebuffer->getWidth(), secondaryCameraFramebuffer->getHeight());
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
QImage screenshot(region.z, region.w, QImage::Format_ARGB32);
withOtherThreadContext([&] {
glBackend->downloadFramebuffer(secondaryCameraFramebuffer, region, screenshot);
});
getGLBackend()->downloadFramebuffer(secondaryCameraFramebuffer, region, screenshot);
return screenshot.mirrored(false, true);
}

View file

@ -60,8 +60,6 @@ public:
virtual bool setDisplayTexture(const QString& name) override;
virtual bool onDisplayTextureReset() { return false; };
QImage getScreenshot(float aspectRatio = 0.0f) const override;
QImage getSecondaryCameraScreenshot() const override;
float presentRate() const override;
@ -185,5 +183,8 @@ protected:
// be serialized through this mutex
mutable Mutex _presentMutex;
float _hudAlpha{ 1.0f };
QImage getScreenshot(float aspectRatio);
QImage getSecondaryCameraScreenshot();
};

View file

@ -199,7 +199,7 @@ void HmdDisplayPlugin::internalPresent() {
float newWidth = sourceSize.x - shiftLeftBy;
// Experimentally adjusted the region presented in preview to avoid seeing the masked pixels and recenter the center...
static float SCALE_WIDTH = 0.9f;
static float SCALE_WIDTH = 0.8f;
static float SCALE_OFFSET = 2.0f;
newWidth *= SCALE_WIDTH;
shiftLeftBy *= SCALE_OFFSET;

View file

@ -48,6 +48,8 @@ public:
void pluginUpdate() override {};
virtual StencilMaskMode getStencilMaskMode() const override { return StencilMaskMode::PAINT; }
signals:
void hmdMountedChanged();
void hmdVisibleChanged(bool visible);

View file

@ -313,11 +313,9 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) {
batch.setModelTransform(renderTransform);
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
drawMaterial->setTextureTransforms(textureTransform, MaterialMappingMode::UV, true);
// bind the material
RenderPipelines::bindMaterial(drawMaterial, batch, args->_enableTexturing);
drawMaterial->setTextureTransforms(textureTransform, MaterialMappingMode::UV, true);
// bind the material
if (RenderPipelines::bindMaterial(drawMaterial, batch, args->_renderMode, args->_enableTexturing)) {
args->_details._materialSwitches++;
}

View file

@ -291,8 +291,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline);
}
} else {
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
RenderPipelines::bindMaterials(materials, batch, args->_enableTexturing);
if (RenderPipelines::bindMaterials(materials, batch, args->_renderMode, args->_enableTexturing)) {
args->_details._materialSwitches++;
}

View file

@ -791,7 +791,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
bool otherOverwrites = overwriteLocalData && !weOwnSimulation;
// calculate hasGrab once outside the lambda rather than calling it every time inside
bool hasGrab = stillHasGrabAction();
auto shouldUpdate = [this, lastEdited, otherOverwrites, filterRejection, hasGrab](quint64 updatedTimestamp, bool valueChanged) {
auto shouldUpdate = [lastEdited, otherOverwrites, filterRejection, hasGrab](quint64 updatedTimestamp, bool valueChanged) {
if (hasGrab) {
return false;
}

View file

@ -909,7 +909,6 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) {
auto& node = _file.nodes[nodeIndex];
if (node.defined["mesh"]) {
qCDebug(modelformat) << "node_transforms" << node.transforms;
foreach(auto &primitive, _file.meshes[node.mesh].primitives) {
hfmModel.meshes.append(HFMMesh());
HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1];
@ -1276,7 +1275,6 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) {
QString fname = hifi::URL(url).fileName();
hifi::URL textureUrl = _url.resolved(url);
qCDebug(modelformat) << "fname: " << fname;
fbxtex.name = fname;
fbxtex.filename = textureUrl.toEncoded();
@ -1370,10 +1368,7 @@ bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int c
blobstream.setByteOrder(QDataStream::LittleEndian);
blobstream.setVersion(QDataStream::Qt_5_9);
blobstream.setFloatingPointPrecision(QDataStream::FloatingPointPrecision::SinglePrecision);
qCDebug(modelformat) << "size1: " << count;
int dataskipped = blobstream.skipRawData(byteOffset);
qCDebug(modelformat) << "dataskipped: " << dataskipped;
blobstream.skipRawData(byteOffset);
int bufferCount = 0;
switch (accessorType) {
@ -1467,6 +1462,38 @@ void GLTFSerializer::retriangulate(const QVector<int>& inIndices, const QVector<
}
}
void GLTFSerializer::glTFDebugDump() {
qCDebug(modelformat) << "---------------- Nodes ----------------";
for (GLTFNode node : _file.nodes) {
if (node.defined["mesh"]) {
qCDebug(modelformat) << "\n";
qCDebug(modelformat) << " node_transforms" << node.transforms;
qCDebug(modelformat) << "\n";
}
}
qCDebug(modelformat) << "---------------- Accessors ----------------";
for (GLTFAccessor accessor : _file.accessors) {
qCDebug(modelformat) << "\n";
qCDebug(modelformat) << "count: " << accessor.count;
qCDebug(modelformat) << "byteOffset: " << accessor.byteOffset;
qCDebug(modelformat) << "\n";
}
qCDebug(modelformat) << "---------------- Textures ----------------";
for (GLTFTexture texture : _file.textures) {
if (texture.defined["source"]) {
qCDebug(modelformat) << "\n";
QString url = _file.images[texture.source].uri;
QString fname = hifi::URL(url).fileName();
qCDebug(modelformat) << "fname: " << fname;
qCDebug(modelformat) << "\n";
}
}
qCDebug(modelformat) << "\n";
}
void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) {
qCDebug(modelformat) << "---------------- hfmModel ----------------";
qCDebug(modelformat) << " hasSkeletonJoints =" << hfmModel.hasSkeletonJoints;
@ -1607,5 +1634,8 @@ void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) {
qCDebug(modelformat) << "\n";
}
qCDebug(modelformat) << "---------------- GLTF Model ----------------";
glTFDebugDump();
qCDebug(modelformat) << "\n";
}

View file

@ -784,6 +784,7 @@ private:
void setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& material);
HFMTexture getHFMTexture(const GLTFTexture& texture);
void glTFDebugDump();
void hfmDebugDump(const HFMModel& hfmModel);
};

View file

@ -9,6 +9,7 @@
#define hifi_gpu_Frame_h
#include <functional>
#include <queue>
#include "Forward.h"
#include "Batch.h"
@ -41,6 +42,8 @@ namespace gpu {
/// How to process the framebuffer when the frame dies. MUST BE THREAD SAFE
FramebufferRecycler framebufferRecycler;
std::queue<std::tuple<std::function<void(const QImage&)>, float, bool>> snapshotOperators;
protected:
friend class Deserializer;

View file

@ -59,8 +59,8 @@ namespace scriptable {
* @property {string} occlusionMap
* @property {string} lightmapMap
* @property {string} scatteringMap
* @property {string} texCoordTransform0
* @property {string} texCoordTransform1
* @property {Mat4|string} texCoordTransform0
* @property {Mat4|string} texCoordTransform1
* @property {string} lightmapParams
* @property {string} materialParams
* @property {boolean} defaultFallthrough
@ -93,6 +93,7 @@ namespace scriptable {
QString occlusionMap;
QString lightmapMap;
QString scatteringMap;
std::array<glm::mat4, graphics::Material::NUM_TEXCOORD_TRANSFORMS> texCoordTransforms;
bool defaultFallthrough;
std::unordered_map<uint, bool> propertyFallthroughs; // not actually exposed to script

View file

@ -470,9 +470,13 @@ namespace scriptable {
// These need to be implemented, but set the fallthrough for now
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM0)) {
obj.setProperty("texCoordTransform0", FALLTHROUGH);
} else if (material.texCoordTransforms[0] != mat4()) {
obj.setProperty("texCoordTransform0", mat4toScriptValue(engine, material.texCoordTransforms[0]));
}
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM1)) {
obj.setProperty("texCoordTransform1", FALLTHROUGH);
} else if (material.texCoordTransforms[1] != mat4()) {
obj.setProperty("texCoordTransform1", mat4toScriptValue(engine, material.texCoordTransforms[1]));
}
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::Material::LIGHTMAP_PARAMS)) {
obj.setProperty("lightmapParams", FALLTHROUGH);

View file

@ -119,6 +119,10 @@ scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPoint
if (map && map->getTextureSource()) {
scatteringMap = map->getTextureSource()->getUrl().toString();
}
for (int i = 0; i < graphics::Material::NUM_TEXCOORD_TRANSFORMS; i++) {
texCoordTransforms[i] = material->getTexCoordTransform(i);
}
}
}

View file

@ -73,6 +73,22 @@ bool Light::getCastShadows() const {
return _castShadows;
}
void Light::setShadowsMaxDistance(const float maxDistance) {
_shadowsMaxDistance = std::max(0.0f, maxDistance);
}
float Light::getShadowsMaxDistance() const {
return _shadowsMaxDistance;
}
void Light::setShadowsBiasScale(const float scale) {
_shadowsBiasScale = std::max(0.0f, scale);
}
float Light::getShadowsBiasScale() const {
return _shadowsBiasScale;
}
void Light::setColor(const Color& color) {
_lightSchemaBuffer.edit().irradiance.color = color;
updateLightRadius();

View file

@ -106,6 +106,12 @@ public:
void setCastShadows(const bool castShadows);
bool getCastShadows() const;
void setShadowsMaxDistance(const float maxDistance);
float getShadowsMaxDistance() const;
void setShadowsBiasScale(const float scale);
float getShadowsBiasScale() const;
void setOrientation(const Quat& orientation);
const glm::quat& getOrientation() const { return _transform.getRotation(); }
@ -192,10 +198,11 @@ protected:
Type _type { SUN };
float _spotCos { -1.0f }; // stored here to be able to reset the spot angle when turning the type spot on/off
void updateLightRadius();
float _shadowsMaxDistance{ 40.0f };
float _shadowsBiasScale{ 1.0f };
bool _castShadows{ false };
void updateLightRadius();
};
typedef std::shared_ptr< Light > LightPointer;

View file

@ -324,6 +324,7 @@ public:
void setModel(const std::string& model) { _model = model; }
glm::mat4 getTexCoordTransform(uint i) const { return _texcoordTransforms[i]; }
void setTexCoordTransform(uint i, const glm::mat4& mat4) { _texcoordTransforms[i] = mat4; }
glm::vec2 getLightmapParams() const { return _lightmapParams; }
glm::vec2 getMaterialParams() const { return _materialParams; }

View file

@ -149,7 +149,6 @@ float fetchScatteringMap(vec2 uv) {
<@endfunc@>
<@func fetchMaterialTexturesCoord0(matKey, texcoord0, albedo, roughness, normal, metallic, emissive, scattering)@>
if (getTexMapArray()._materialParams.y != 1.0 && clamp(<$texcoord0$>, vec2(0.0), vec2(1.0)) != <$texcoord0$>) {
discard;

View file

@ -177,6 +177,8 @@ std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource
material->setModel(modelString);
}
std::array<glm::mat4, graphics::Material::NUM_TEXCOORD_TRANSFORMS> texcoordTransforms;
if (modelString == HIFI_PBR) {
const QString FALLTHROUGH("fallthrough");
for (auto& key : materialJSON.keys()) {
@ -372,8 +374,11 @@ std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::TEXCOORDTRANSFORM0);
}
} else if (value.isObject()) {
auto valueVariant = value.toVariant();
glm::mat4 transform = mat4FromVariant(valueVariant);
texcoordTransforms[0] = transform;
}
// TODO: implement texCoordTransform0
} else if (key == "texCoordTransform1") {
auto value = materialJSON.value(key);
if (value.isString()) {
@ -381,8 +386,11 @@ std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::TEXCOORDTRANSFORM1);
}
} else if (value.isObject()) {
auto valueVariant = value.toVariant();
glm::mat4 transform = mat4FromVariant(valueVariant);
texcoordTransforms[1] = transform;
}
// TODO: implement texCoordTransform1
} else if (key == "lightmapParams") {
auto value = materialJSON.value(key);
if (value.isString()) {
@ -409,6 +417,15 @@ std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource
}
}
}
// Do this after the texture maps are defined, so it overrides the default transforms
for (int i = 0; i < graphics::Material::NUM_TEXCOORD_TRANSFORMS; i++) {
mat4 newTransform = texcoordTransforms[i];
if (newTransform != mat4() || newTransform != material->getTexCoordTransform(i)) {
material->setTexCoordTransform(i, newTransform);
}
}
return std::pair<std::string, std::shared_ptr<NetworkMaterial>>(name, material);
}

View file

@ -635,11 +635,9 @@ void NetworkTexture::makeLocalRequest() {
}
bool NetworkTexture::handleFailedRequest(ResourceRequest::Result result) {
if (_currentlyLoadingResourceType != ResourceType::KTX
&& result == ResourceRequest::Result::RedirectFail) {
if (_shouldFailOnRedirect && result == ResourceRequest::Result::RedirectFail) {
auto newPath = _request->getRelativePathUrl();
if (newPath.fileName().endsWith(".ktx")) {
if (newPath.fileName().toLower().endsWith(".ktx")) {
_currentlyLoadingResourceType = ResourceType::KTX;
_activeUrl = newPath;
_shouldFailOnRedirect = false;

View file

@ -52,7 +52,7 @@ std::vector<hifi::ByteArray> createMaterialList(const hfm::Mesh& mesh) {
}
std::unique_ptr<draco::Mesh> createDracoMesh(const hfm::Mesh& mesh, const std::vector<glm::vec3>& normals, const std::vector<glm::vec3>& tangents, const std::vector<hifi::ByteArray>& materialList) {
Q_ASSERT(normals.size() == 0 || normals.size() == mesh.vertices.size());
Q_ASSERT(normals.size() == 0 || (int)normals.size() == mesh.vertices.size());
Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size());
Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size());

View file

@ -31,8 +31,6 @@
Q_LOGGING_CATEGORY(trace_resource_parse_geometry, "trace.resource.parse.geometry")
class GeometryReader;
class GeometryExtra {
public:
const GeometryMappingPair& mapping;
@ -87,113 +85,6 @@ namespace std {
};
}
QUrl resolveTextureBaseUrl(const QUrl& url, const QUrl& textureBaseUrl) {
return textureBaseUrl.isValid() ? textureBaseUrl : url;
}
class GeometryMappingResource : public GeometryResource {
Q_OBJECT
public:
GeometryMappingResource(const QUrl& url) : GeometryResource(url) {};
QString getType() const override { return "GeometryMapping"; }
virtual void downloadFinished(const QByteArray& data) override;
private slots:
void onGeometryMappingLoaded(bool success);
private:
GeometryResource::Pointer _geometryResource;
QMetaObject::Connection _connection;
};
void GeometryMappingResource::downloadFinished(const QByteArray& data) {
PROFILE_ASYNC_BEGIN(resource_parse_geometry, "GeometryMappingResource::downloadFinished", _url.toString(),
{ { "url", _url.toString() } });
// store parsed contents of FST file
_mapping = FSTReader::readMapping(data);
QString filename = _mapping.value("filename").toString();
if (filename.isNull()) {
finishedLoading(false);
} else {
const QString baseURL = _mapping.value("baseURL").toString();
const QUrl base = _effectiveBaseURL.resolved(baseURL);
QUrl url = base.resolved(filename);
QString texdir = _mapping.value(TEXDIR_FIELD).toString();
if (!texdir.isNull()) {
if (!texdir.endsWith('/')) {
texdir += '/';
}
_textureBaseUrl = resolveTextureBaseUrl(url, base.resolved(texdir));
} else {
_textureBaseUrl = url.resolved(QUrl("."));
}
auto scripts = FSTReader::getScripts(base, _mapping);
if (scripts.size() > 0) {
_mapping.remove(SCRIPT_FIELD);
for (auto &scriptPath : scripts) {
_mapping.insertMulti(SCRIPT_FIELD, scriptPath);
}
}
auto animGraphVariant = _mapping.value("animGraphUrl");
if (animGraphVariant.isValid()) {
QUrl fstUrl(animGraphVariant.toString());
if (fstUrl.isValid()) {
_animGraphOverrideUrl = base.resolved(fstUrl);
} else {
_animGraphOverrideUrl = QUrl();
}
} else {
_animGraphOverrideUrl = QUrl();
}
auto modelCache = DependencyManager::get<ModelCache>();
GeometryExtra extra { GeometryMappingPair(base, _mapping), _textureBaseUrl, false };
// Get the raw GeometryResource
_geometryResource = modelCache->getResource(url, QUrl(), &extra, std::hash<GeometryExtra>()(extra)).staticCast<GeometryResource>();
// Avoid caching nested resources - their references will be held by the parent
_geometryResource->_isCacheable = false;
if (_geometryResource->isLoaded()) {
onGeometryMappingLoaded(!_geometryResource->getURL().isEmpty());
} else {
if (_connection) {
disconnect(_connection);
}
_connection = connect(_geometryResource.data(), &Resource::finished,
this, &GeometryMappingResource::onGeometryMappingLoaded);
}
}
}
void GeometryMappingResource::onGeometryMappingLoaded(bool success) {
if (success && _geometryResource) {
_hfmModel = _geometryResource->_hfmModel;
_materialMapping = _geometryResource->_materialMapping;
_meshParts = _geometryResource->_meshParts;
_meshes = _geometryResource->_meshes;
_materials = _geometryResource->_materials;
// Avoid holding onto extra references
_geometryResource.reset();
// Make sure connection will not trigger again
disconnect(_connection); // FIXME Should not have to do this
}
PROFILE_ASYNC_END(resource_parse_geometry, "GeometryMappingResource::downloadFinished", _url.toString());
finishedLoading(success);
}
class GeometryReader : public QRunnable {
public:
GeometryReader(const ModelLoader& modelLoader, QWeakPointer<Resource>& resource, const QUrl& url, const GeometryMappingPair& mapping,
@ -308,47 +199,124 @@ void GeometryReader::run() {
}
}
class GeometryDefinitionResource : public GeometryResource {
Q_OBJECT
public:
GeometryDefinitionResource(const ModelLoader& modelLoader, const QUrl& url) : GeometryResource(url), _modelLoader(modelLoader) {}
GeometryDefinitionResource(const GeometryDefinitionResource& other) :
GeometryResource(other),
_modelLoader(other._modelLoader),
_mapping(other._mapping),
_combineParts(other._combineParts) {}
QUrl resolveTextureBaseUrl(const QUrl& url, const QUrl& textureBaseUrl) {
return textureBaseUrl.isValid() ? textureBaseUrl : url;
}
QString getType() const override { return "GeometryDefinition"; }
GeometryResource::GeometryResource(const GeometryResource& other) :
Resource(other),
Geometry(other),
_modelLoader(other._modelLoader),
_mappingPair(other._mappingPair),
_textureBaseURL(other._textureBaseURL),
_combineParts(other._combineParts),
_isCacheable(other._isCacheable)
{
if (other._geometryResource) {
_startedLoading = false;
}
}
virtual void downloadFinished(const QByteArray& data) override;
void GeometryResource::downloadFinished(const QByteArray& data) {
if (_effectiveBaseURL.fileName().toLower().endsWith(".fst")) {
PROFILE_ASYNC_BEGIN(resource_parse_geometry, "GeometryResource::downloadFinished", _url.toString(), { { "url", _url.toString() } });
void setExtra(void* extra) override;
// store parsed contents of FST file
_mapping = FSTReader::readMapping(data);
protected:
Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel, const MaterialMapping& materialMapping);
QString filename = _mapping.value("filename").toString();
private:
ModelLoader _modelLoader;
GeometryMappingPair _mapping;
bool _combineParts;
};
if (filename.isNull()) {
finishedLoading(false);
} else {
const QString baseURL = _mapping.value("baseURL").toString();
const QUrl base = _effectiveBaseURL.resolved(baseURL);
QUrl url = base.resolved(filename);
void GeometryDefinitionResource::setExtra(void* extra) {
QString texdir = _mapping.value(TEXDIR_FIELD).toString();
if (!texdir.isNull()) {
if (!texdir.endsWith('/')) {
texdir += '/';
}
_textureBaseURL = resolveTextureBaseUrl(url, base.resolved(texdir));
} else {
_textureBaseURL = url.resolved(QUrl("."));
}
auto scripts = FSTReader::getScripts(base, _mapping);
if (scripts.size() > 0) {
_mapping.remove(SCRIPT_FIELD);
for (auto &scriptPath : scripts) {
_mapping.insertMulti(SCRIPT_FIELD, scriptPath);
}
}
auto animGraphVariant = _mapping.value("animGraphUrl");
if (animGraphVariant.isValid()) {
QUrl fstUrl(animGraphVariant.toString());
if (fstUrl.isValid()) {
_animGraphOverrideUrl = base.resolved(fstUrl);
} else {
_animGraphOverrideUrl = QUrl();
}
} else {
_animGraphOverrideUrl = QUrl();
}
auto modelCache = DependencyManager::get<ModelCache>();
GeometryExtra extra { GeometryMappingPair(base, _mapping), _textureBaseURL, false };
// Get the raw GeometryResource
_geometryResource = modelCache->getResource(url, QUrl(), &extra, std::hash<GeometryExtra>()(extra)).staticCast<GeometryResource>();
// Avoid caching nested resources - their references will be held by the parent
_geometryResource->_isCacheable = false;
if (_geometryResource->isLoaded()) {
onGeometryMappingLoaded(!_geometryResource->getURL().isEmpty());
} else {
if (_connection) {
disconnect(_connection);
}
_connection = connect(_geometryResource.data(), &Resource::finished, this, &GeometryResource::onGeometryMappingLoaded);
}
}
} else {
if (_url != _effectiveBaseURL) {
_url = _effectiveBaseURL;
_textureBaseURL = _effectiveBaseURL;
}
QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, _self, _effectiveBaseURL, _mappingPair, data, _combineParts, _request->getWebMediaType()));
}
}
void GeometryResource::onGeometryMappingLoaded(bool success) {
if (success && _geometryResource) {
_hfmModel = _geometryResource->_hfmModel;
_materialMapping = _geometryResource->_materialMapping;
_meshParts = _geometryResource->_meshParts;
_meshes = _geometryResource->_meshes;
_materials = _geometryResource->_materials;
// Avoid holding onto extra references
_geometryResource.reset();
// Make sure connection will not trigger again
disconnect(_connection); // FIXME Should not have to do this
}
PROFILE_ASYNC_END(resource_parse_geometry, "GeometryResource::downloadFinished", _url.toString());
finishedLoading(success);
}
void GeometryResource::setExtra(void* extra) {
const GeometryExtra* geometryExtra = static_cast<const GeometryExtra*>(extra);
_mapping = geometryExtra ? geometryExtra->mapping : GeometryMappingPair(QUrl(), QVariantHash());
_textureBaseUrl = geometryExtra ? resolveTextureBaseUrl(_url, geometryExtra->textureBaseUrl) : QUrl();
_mappingPair = geometryExtra ? geometryExtra->mapping : GeometryMappingPair(QUrl(), QVariantHash());
_textureBaseURL = geometryExtra ? resolveTextureBaseUrl(_url, geometryExtra->textureBaseUrl) : QUrl();
_combineParts = geometryExtra ? geometryExtra->combineParts : true;
}
void GeometryDefinitionResource::downloadFinished(const QByteArray& data) {
if (_url != _effectiveBaseURL) {
_url = _effectiveBaseURL;
_textureBaseUrl = _effectiveBaseURL;
}
QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, _self, _effectiveBaseURL, _mapping, data, _combineParts, _request->getWebMediaType()));
}
void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel, const MaterialMapping& materialMapping) {
void GeometryResource::setGeometryDefinition(HFMModel::Pointer hfmModel, const MaterialMapping& materialMapping) {
// Assume ownership of the processed HFMModel
_hfmModel = hfmModel;
_materialMapping = materialMapping;
@ -357,7 +325,7 @@ void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmMode
QHash<QString, size_t> materialIDAtlas;
for (const HFMMaterial& material : _hfmModel->materials) {
materialIDAtlas[material.materialID] = _materials.size();
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl));
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseURL));
}
std::shared_ptr<GeometryMeshes> meshes = std::make_shared<GeometryMeshes>();
@ -380,6 +348,23 @@ void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmMode
finishedLoading(true);
}
void GeometryResource::deleter() {
resetTextures();
Resource::deleter();
}
void GeometryResource::setTextures() {
if (_hfmModel) {
for (const HFMMaterial& material : _hfmModel->materials) {
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseURL));
}
}
}
void GeometryResource::resetTextures() {
_materials.clear();
}
ModelCache::ModelCache() {
const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE;
setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE);
@ -392,26 +377,14 @@ ModelCache::ModelCache() {
}
QSharedPointer<Resource> ModelCache::createResource(const QUrl& url) {
Resource* resource = nullptr;
if (url.path().toLower().endsWith(".fst")) {
resource = new GeometryMappingResource(url);
} else {
resource = new GeometryDefinitionResource(_modelLoader, url);
}
return QSharedPointer<Resource>(resource, &Resource::deleter);
return QSharedPointer<Resource>(new GeometryResource(url, _modelLoader), &GeometryResource::deleter);
}
QSharedPointer<Resource> ModelCache::createResourceCopy(const QSharedPointer<Resource>& resource) {
if (resource->getURL().path().toLower().endsWith(".fst")) {
return QSharedPointer<Resource>(new GeometryMappingResource(*resource.staticCast<GeometryMappingResource>()), &Resource::deleter);
} else {
return QSharedPointer<Resource>(new GeometryDefinitionResource(*resource.staticCast<GeometryDefinitionResource>()), &Resource::deleter);
}
return QSharedPointer<Resource>(new GeometryResource(*resource.staticCast<GeometryResource>()), &GeometryResource::deleter);
}
GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url,
const GeometryMappingPair& mapping, const QUrl& textureBaseUrl) {
GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, const GeometryMappingPair& mapping, const QUrl& textureBaseUrl) {
bool combineParts = true;
GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts };
GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash<GeometryExtra>()(geometryExtra)).staticCast<GeometryResource>();
@ -531,23 +504,6 @@ const std::shared_ptr<NetworkMaterial> Geometry::getShapeMaterial(int partID) co
return nullptr;
}
void GeometryResource::deleter() {
resetTextures();
Resource::deleter();
}
void GeometryResource::setTextures() {
if (_hfmModel) {
for (const HFMMaterial& material : _hfmModel->materials) {
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl));
}
}
}
void GeometryResource::resetTextures() {
_materials.clear();
}
void GeometryResourceWatcher::startWatching() {
connect(_resource.data(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished);
connect(_resource.data(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed);

View file

@ -24,8 +24,6 @@
class MeshPart;
class GeometryMappingResource;
using GeometryMappingPair = std::pair<QUrl, QVariantHash>;
Q_DECLARE_METATYPE(GeometryMappingPair)
@ -60,8 +58,6 @@ public:
const QVariantHash& getMapping() const { return _mapping; }
protected:
friend class GeometryMappingResource;
// Shared across all geometries, constant throughout lifetime
std::shared_ptr<const HFMModel> _hfmModel;
MaterialMapping _materialMapping;
@ -80,23 +76,29 @@ private:
/// A geometry loaded from the network.
class GeometryResource : public Resource, public Geometry {
Q_OBJECT
public:
using Pointer = QSharedPointer<GeometryResource>;
GeometryResource(const QUrl& url) : Resource(url) {}
GeometryResource(const GeometryResource& other) :
Resource(other),
Geometry(other),
_textureBaseUrl(other._textureBaseUrl),
_isCacheable(other._isCacheable) {}
GeometryResource(const QUrl& url, const ModelLoader& modelLoader) : Resource(url), _modelLoader(modelLoader) {}
GeometryResource(const GeometryResource& other);
virtual bool areTexturesLoaded() const override { return isLoaded() && Geometry::areTexturesLoaded(); }
QString getType() const override { return "Geometry"; }
virtual void deleter() override;
virtual void downloadFinished(const QByteArray& data) override;
void setExtra(void* extra) override;
virtual bool areTexturesLoaded() const override { return isLoaded() && Geometry::areTexturesLoaded(); }
private slots:
void onGeometryMappingLoaded(bool success);
protected:
friend class ModelCache;
friend class GeometryMappingResource;
Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel, const MaterialMapping& materialMapping);
// Geometries may not hold onto textures while cached - that is for the texture cache
// Instead, these methods clear and reset textures from the geometry when caching/loading
@ -104,10 +106,18 @@ protected:
void setTextures();
void resetTextures();
QUrl _textureBaseUrl;
virtual bool isCacheable() const override { return _loaded && _isCacheable; }
bool _isCacheable { true };
private:
ModelLoader _modelLoader;
GeometryMappingPair _mappingPair;
QUrl _textureBaseURL;
bool _combineParts;
GeometryResource::Pointer _geometryResource;
QMetaObject::Connection _connection;
bool _isCacheable{ true };
};
class GeometryResourceWatcher : public QObject {
@ -158,7 +168,7 @@ public:
const QUrl& textureBaseUrl = QUrl());
protected:
friend class GeometryMappingResource;
friend class GeometryResource;
virtual QSharedPointer<Resource> createResource(const QUrl& url) override;
QSharedPointer<Resource> createResourceCopy(const QSharedPointer<Resource>& resource) override;

View file

@ -576,6 +576,7 @@ Resource::Resource(const Resource& other) :
Resource::Resource(const QUrl& url) :
_url(url),
_effectiveBaseURL(url),
_activeUrl(url),
_requestID(++requestID) {
init();

View file

@ -28,7 +28,7 @@
static AAssetManager* ASSET_MANAGER = nullptr;
#define USE_BLIT_PRESENT 0
#define USE_BLIT_PRESENT 1
#if !USE_BLIT_PRESENT

View file

@ -27,6 +27,7 @@
#include <SimpleMovingAverage.h>
#include <gpu/Forward.h>
#include "Plugin.h"
#include "StencilMaskMode.h"
class QOpenGLFramebufferObject;
@ -172,10 +173,6 @@ public:
return QRect(0, 0, recommendedSize.x, recommendedSize.y);
}
// Fetch the most recently displayed image as a QImage
virtual QImage getScreenshot(float aspectRatio = 0.0f) const = 0;
virtual QImage getSecondaryCameraScreenshot() const = 0;
// will query the underlying hmd api to compute the most recent head pose
virtual bool beginFrameRender(uint32_t frameIndex) { return true; }
@ -221,6 +218,10 @@ public:
// for updating plugin-related commands. Mimics the input plugin.
virtual void pluginUpdate() = 0;
virtual StencilMaskMode getStencilMaskMode() const { return StencilMaskMode::NONE; }
using StencilMaskMeshOperator = std::function<void(gpu::Batch&)>;
virtual StencilMaskMeshOperator getStencilMaskMeshOperator() { return nullptr; }
signals:
void recommendedFramebufferSizeChanged(const QSize& size);
void resetSensorsRequested();

View file

@ -120,7 +120,7 @@ float LightStage::Shadow::Cascade::computeFarDistance(const ViewFrustum& viewFru
return far;
}
LightStage::Shadow::Shadow(graphics::LightPointer light, float maxDistance, unsigned int cascadeCount) :
LightStage::Shadow::Shadow(graphics::LightPointer light, unsigned int cascadeCount) :
_light{ light } {
cascadeCount = std::min(cascadeCount, (unsigned int)SHADOW_CASCADE_MAX_COUNT);
Schema schema;
@ -149,70 +149,79 @@ LightStage::Shadow::Shadow(graphics::LightPointer light, float maxDistance, unsi
cascade.framebuffer->setDepthBuffer(map, depthFormat, cascadeIndex);
}
setMaxDistance(maxDistance);
if (light) {
setMaxDistance(light->getShadowsMaxDistance());
}
}
void LightStage::Shadow::setLight(graphics::LightPointer light) {
_light = light;
if (light) {
setMaxDistance(light->getShadowsMaxDistance());
}
}
void LightStage::Shadow::setMaxDistance(float value) {
// This overlaping factor isn't really used directly for blending of shadow cascades. It
// just there to be sure the cascades do overlap. The blending width used is relative
// to the UV space and is set in the Schema with invCascadeBlendWidth.
static const auto OVERLAP_FACTOR = 1.0f / 5.0f;
static const auto MINIMUM_MAXDISTANCE = 1e-3f;
_maxDistance = std::max(0.0f, value);
value = std::max(MINIMUM_MAXDISTANCE, value);
if (value != _maxDistance) {
// This overlaping factor isn't really used directly for blending of shadow cascades. It's
// just there to be sure the cascades do overlap. The blending width used is relative
// to the UV space and is set in the Schema with invCascadeBlendWidth.
static const auto OVERLAP_FACTOR = 1.0f / 5.0f;
if (_cascades.size() == 1) {
_cascades.front().setMinDistance(0.0f);
_cascades.front().setMaxDistance(_maxDistance);
} else {
// Distribute the cascades along that distance
// TODO : these parameters should be exposed to the user as part of the light entity parameters, no?
static const auto LOW_MAX_DISTANCE = 2.0f;
static const auto MAX_RESOLUTION_LOSS = 0.6f; // Between 0 and 1, 0 giving tighter distributions
_maxDistance = value;
// The max cascade distance is computed by multiplying the previous cascade's max distance by a certain
// factor. There is a "user" factor that is computed from a desired max resolution loss in the shadow
// and an optimal one based on the global min and max shadow distance, all cascades considered. The final
// distance is a gradual blend between the two
const auto userDistanceScale = 1.0f / (1.0f - MAX_RESOLUTION_LOSS);
const auto optimalDistanceScale = powf(_maxDistance / LOW_MAX_DISTANCE, 1.0f / (_cascades.size() - 1));
if (_cascades.size() == 1) {
_cascades.front().setMinDistance(0.0f);
_cascades.front().setMaxDistance(_maxDistance);
} else {
// Distribute the cascades along that distance
// TODO : these parameters should be exposed to the user as part of the light entity parameters, no?
static const auto LOW_MAX_DISTANCE = 2.0f;
static const auto MAX_RESOLUTION_LOSS = 0.6f; // Between 0 and 1, 0 giving tighter distributions
float maxCascadeUserDistance = LOW_MAX_DISTANCE;
float maxCascadeOptimalDistance = LOW_MAX_DISTANCE;
float minCascadeDistance = 0.0f;
// The max cascade distance is computed by multiplying the previous cascade's max distance by a certain
// factor. There is a "user" factor that is computed from a desired max resolution loss in the shadow
// and an optimal one based on the global min and max shadow distance, all cascades considered. The final
// distance is a gradual blend between the two
const auto userDistanceScale = 1.0f / (1.0f - MAX_RESOLUTION_LOSS);
const auto optimalDistanceScale = powf(_maxDistance / LOW_MAX_DISTANCE, 1.0f / (_cascades.size() - 1));
for (size_t cascadeIndex = 0; cascadeIndex < _cascades.size(); ++cascadeIndex) {
float blendFactor = cascadeIndex / float(_cascades.size() - 1);
float maxCascadeDistance;
float maxCascadeUserDistance = LOW_MAX_DISTANCE;
float maxCascadeOptimalDistance = LOW_MAX_DISTANCE;
float minCascadeDistance = 0.0f;
if (cascadeIndex == size_t(_cascades.size() - 1)) {
maxCascadeDistance = _maxDistance;
} else {
maxCascadeDistance = maxCascadeUserDistance + (maxCascadeOptimalDistance - maxCascadeUserDistance)*blendFactor*blendFactor;
for (size_t cascadeIndex = 0; cascadeIndex < _cascades.size(); ++cascadeIndex) {
float blendFactor = cascadeIndex / float(_cascades.size() - 1);
float maxCascadeDistance;
if (cascadeIndex == size_t(_cascades.size() - 1)) {
maxCascadeDistance = _maxDistance;
} else {
maxCascadeDistance = maxCascadeUserDistance + (maxCascadeOptimalDistance - maxCascadeUserDistance)*blendFactor*blendFactor;
}
float shadowOverlapDistance = maxCascadeDistance * OVERLAP_FACTOR;
_cascades[cascadeIndex].setMinDistance(minCascadeDistance);
_cascades[cascadeIndex].setMaxDistance(maxCascadeDistance + shadowOverlapDistance);
// Compute distances for next cascade
minCascadeDistance = maxCascadeDistance;
maxCascadeUserDistance = maxCascadeUserDistance * userDistanceScale;
maxCascadeOptimalDistance = maxCascadeOptimalDistance * optimalDistanceScale;
maxCascadeUserDistance = std::min(maxCascadeUserDistance, _maxDistance);
}
float shadowOverlapDistance = maxCascadeDistance * OVERLAP_FACTOR;
_cascades[cascadeIndex].setMinDistance(minCascadeDistance);
_cascades[cascadeIndex].setMaxDistance(maxCascadeDistance + shadowOverlapDistance);
// Compute distances for next cascade
minCascadeDistance = maxCascadeDistance;
maxCascadeUserDistance = maxCascadeUserDistance * userDistanceScale;
maxCascadeOptimalDistance = maxCascadeOptimalDistance * optimalDistanceScale;
maxCascadeUserDistance = std::min(maxCascadeUserDistance, _maxDistance);
}
}
// Update the buffer
const auto& lastCascade = _cascades.back();
auto& schema = _schemaBuffer.edit<Schema>();
schema.maxDistance = _maxDistance;
schema.invFalloffDistance = 1.0f / (OVERLAP_FACTOR*lastCascade.getMaxDistance());
// Update the buffer
const auto& lastCascade = _cascades.back();
auto& schema = _schemaBuffer.edit<Schema>();
schema.maxDistance = _maxDistance;
schema.invFalloffDistance = 1.0f / (OVERLAP_FACTOR*lastCascade.getMaxDistance());
}
}
void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum,

View file

@ -74,7 +74,7 @@ public:
float left, float right, float bottom, float top, float viewMaxShadowDistance) const;
};
Shadow(graphics::LightPointer light, float maxDistance, unsigned int cascadeCount = 1);
Shadow(graphics::LightPointer light, unsigned int cascadeCount = 1);
void setLight(graphics::LightPointer light);
@ -104,16 +104,14 @@ public:
};
protected:
using Cascades = std::vector<Cascade>;
static const glm::mat4 _biasMatrix;
graphics::LightPointer _light;
float _maxDistance;
float _maxDistance{ 0.0f };
Cascades _cascades;
UniformBufferView _schemaBuffer = nullptr;
};

View file

@ -154,8 +154,7 @@ void MeshPartPayload::render(RenderArgs* args) {
bindMesh(batch);
// apply material properties
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
RenderPipelines::bindMaterials(_drawMaterials, batch, args->_enableTexturing);
if (RenderPipelines::bindMaterials(_drawMaterials, batch, args->_renderMode, args->_enableTexturing)) {
args->_details._materialSwitches++;
}
@ -434,8 +433,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) {
}
// apply material properties
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
RenderPipelines::bindMaterials(_drawMaterials, batch, args->_enableTexturing);
if (RenderPipelines::bindMaterials(_drawMaterials, batch, args->_renderMode, args->_enableTexturing)) {
args->_details._materialSwitches++;
}

View file

@ -364,7 +364,7 @@ void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input
sprintf(jobName, "DrawShadowFrustum%d", i);
task.addJob<DrawFrustum>(jobName, shadowFrustum, glm::vec3(0.0f, tint, 1.0f));
if (!renderShadowTaskOut.isNull()) {
const auto& shadowCascadeSceneBBoxes = renderShadowTaskOut;
const auto& shadowCascadeSceneBBoxes = renderShadowTaskOut.get<RenderShadowTask::CascadeBoxes>();
const auto shadowBBox = shadowCascadeSceneBBoxes[ExtractFrustums::SHADOW_CASCADE0_FRUSTUM + i];
sprintf(jobName, "DrawShadowBBox%d", i);
task.addJob<DrawAABox>(jobName, shadowBBox, glm::vec3(1.0f, tint, 0.0f));

View file

@ -373,10 +373,10 @@ void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state, con
gpu::Shader::createProgram(deformed_model_shadow_fade_dq), state, extraBatchSetter, itemSetter);
}
void RenderPipelines::bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures) {
bool RenderPipelines::bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, render::Args::RenderMode renderMode, bool enableTextures) {
graphics::MultiMaterial multiMaterial;
multiMaterial.push(graphics::MaterialLayer(material, 0));
bindMaterials(multiMaterial, batch, enableTextures);
return bindMaterials(multiMaterial, batch, renderMode, enableTextures);
}
void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial) {
@ -730,7 +730,7 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial
multiMaterial.setInitialized();
}
void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, bool enableTextures) {
bool RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, render::Args::RenderMode renderMode, bool enableTextures) {
if (multiMaterial.shouldUpdate()) {
updateMultiMaterial(multiMaterial);
}
@ -738,8 +738,13 @@ void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu:
auto textureCache = DependencyManager::get<TextureCache>();
static gpu::TextureTablePointer defaultMaterialTextures = std::make_shared<gpu::TextureTable>();
static gpu::BufferView defaultMaterialSchema;
static std::once_flag once;
std::call_once(once, [textureCache] {
graphics::MultiMaterial::Schema schema;
defaultMaterialSchema = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(schema), (const gpu::Byte*) &schema, sizeof(schema)));
defaultMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, textureCache->getWhiteTexture());
defaultMaterialTextures->setTexture(gr::Texture::MaterialMetallic, textureCache->getBlackTexture());
defaultMaterialTextures->setTexture(gr::Texture::MaterialRoughness, textureCache->getWhiteTexture());
@ -749,17 +754,29 @@ void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu:
// MaterialEmissiveLightmap has to be set later
});
auto& schemaBuffer = multiMaterial.getSchemaBuffer();
batch.setUniformBuffer(gr::Buffer::Material, schemaBuffer);
if (enableTextures) {
batch.setResourceTextureTable(multiMaterial.getTextureTable());
} else {
auto key = multiMaterial.getMaterialKey();
if (key.isLightmapMap()) {
defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getBlackTexture());
} else if (key.isEmissiveMap()) {
defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getGrayTexture());
// For shadows, we only need opacity mask information
auto key = multiMaterial.getMaterialKey();
if (renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE || key.isOpacityMaskMap()) {
auto& schemaBuffer = multiMaterial.getSchemaBuffer();
batch.setUniformBuffer(gr::Buffer::Material, schemaBuffer);
if (enableTextures) {
batch.setResourceTextureTable(multiMaterial.getTextureTable());
} else {
if (renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
if (key.isLightmapMap()) {
defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getBlackTexture());
} else if (key.isEmissiveMap()) {
defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getGrayTexture());
}
}
batch.setResourceTextureTable(defaultMaterialTextures);
}
return true;
} else {
batch.setResourceTextureTable(defaultMaterialTextures);
batch.setUniformBuffer(gr::Buffer::Material, defaultMaterialSchema);
return false;
}
}

View file

@ -12,12 +12,13 @@
#define hifi_RenderPipelines_h
#include <graphics/Material.h>
#include <render/Args.h>
class RenderPipelines {
public:
static void bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures);
static void updateMultiMaterial(graphics::MultiMaterial& multiMaterial);
static void bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, bool enableTextures);
static bool bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, render::Args::RenderMode renderMode, bool enableTextures);
static bool bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, render::Args::RenderMode renderMode, bool enableTextures);
};

View file

@ -34,7 +34,6 @@
#define SHADOW_FRUSTUM_NEAR 1.0f
#define SHADOW_FRUSTUM_FAR 500.0f
static const unsigned int SHADOW_CASCADE_COUNT{ 4 };
static const float SHADOW_MAX_DISTANCE{ 40.0f };
using namespace render;
@ -90,7 +89,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende
#endif
};
render::VaryingArray<AABox,4> cascadeSceneBBoxes;
CascadeBoxes cascadeSceneBBoxes;
for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) {
char jobName[64];
@ -336,7 +335,7 @@ void RenderShadowSetup::setConstantBias(int cascadeIndex, float value) {
}
void RenderShadowSetup::setSlopeBias(int cascadeIndex, float value) {
_bias[cascadeIndex]._slope = value * value * value * 0.01f;
_bias[cascadeIndex]._slope = value * value * value * 0.001f;
}
void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, const Input& input, Output& output) {
@ -367,7 +366,7 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, c
output.edit2() = _cameraFrustum;
if (!_globalShadowObject) {
_globalShadowObject = std::make_shared<LightStage::Shadow>(graphics::LightPointer(), SHADOW_MAX_DISTANCE, SHADOW_CASCADE_COUNT);
_globalShadowObject = std::make_shared<LightStage::Shadow>(currentKeyLight, SHADOW_CASCADE_COUNT);
}
_globalShadowObject->setLight(currentKeyLight);
@ -378,11 +377,12 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, c
unsigned int cascadeIndex;
// Adjust each cascade frustum
const auto biasScale = currentKeyLight->getShadowsBiasScale();
for (cascadeIndex = 0; cascadeIndex < _globalShadowObject->getCascadeCount(); ++cascadeIndex) {
auto& bias = _bias[cascadeIndex];
_globalShadowObject->setKeylightCascadeFrustum(cascadeIndex, args->getViewFrustum(),
SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR,
bias._constant, bias._slope);
SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR,
bias._constant, bias._slope * biasScale);
}
_shadowFrameCache->pushShadow(_globalShadowObject);

View file

@ -52,8 +52,9 @@ class RenderShadowTask {
public:
// There is one AABox per shadow cascade
using CascadeBoxes = render::VaryingArray<AABox, SHADOW_CASCADE_MAX_COUNT>;
using Input = render::VaryingSet2<LightStage::FramePointer, LightingModelPointer>;
using Output = render::VaryingSet2<render::VaryingArray<AABox, SHADOW_CASCADE_MAX_COUNT>, LightStage::ShadowFramePointer>;
using Output = render::VaryingSet2<CascadeBoxes, LightStage::ShadowFramePointer>;
using Config = RenderShadowTaskConfig;
using JobModel = render::Task::ModelIO<RenderShadowTask, Input, Output, Config>;
@ -92,10 +93,10 @@ public:
float constantBias1{ 0.15f };
float constantBias2{ 0.175f };
float constantBias3{ 0.2f };
float slopeBias0{ 0.6f };
float slopeBias1{ 0.6f };
float slopeBias2{ 0.7f };
float slopeBias3{ 0.82f };
float slopeBias0{ 0.4f };
float slopeBias1{ 0.45f };
float slopeBias2{ 0.65f };
float slopeBias3{ 0.7f };
signals:
void dirty();

View file

@ -90,8 +90,8 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve
return shadowAttenuation;
}
float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float oneMinusNdotL) {
float bias = getShadowFixedBias(cascadeIndex) + getShadowSlopeBias(cascadeIndex) * oneMinusNdotL;
float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float slopeNdotL) {
float bias = getShadowFixedBias(cascadeIndex) + getShadowSlopeBias(cascadeIndex) * slopeNdotL;
return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias);
}
@ -104,7 +104,8 @@ float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDe
vec3 cascadeMix;
bvec4 isPixelOnCascade;
int cascadeIndex;
float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0.0, 1.0);
float NdotL = clamp(dot(worldLightDir, worldNormal), 0.0, 1.0);
float slopeNdotL = min(2.0, sqrt(1.0-NdotL*NdotL) / NdotL);
for (cascadeIndex=0 ; cascadeIndex<getShadowCascadeCount() ; cascadeIndex++) {
cascadeShadowCoords[cascadeIndex] = evalShadowTexcoord(cascadeIndex, worldPosition);
@ -115,10 +116,10 @@ float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDe
isPixelOnCascade.z = isShadowCascadeProjectedOnPixel(cascadeShadowCoords[2]);
isPixelOnCascade.w = isShadowCascadeProjectedOnPixel(cascadeShadowCoords[3]);
cascadeAttenuations.x = mix(1.0, evalShadowCascadeAttenuation(0, offsets, cascadeShadowCoords[0], oneMinusNdotL), float(isPixelOnCascade.x));
cascadeAttenuations.y = mix(1.0, evalShadowCascadeAttenuation(1, offsets, cascadeShadowCoords[1], oneMinusNdotL), float(isPixelOnCascade.y));
cascadeAttenuations.z = mix(1.0, evalShadowCascadeAttenuation(2, offsets, cascadeShadowCoords[2], oneMinusNdotL), float(isPixelOnCascade.z));
cascadeAttenuations.w = mix(1.0, evalShadowCascadeAttenuation(3, offsets, cascadeShadowCoords[3], oneMinusNdotL), float(isPixelOnCascade.w));
cascadeAttenuations.x = mix(1.0, evalShadowCascadeAttenuation(0, offsets, cascadeShadowCoords[0], slopeNdotL), float(isPixelOnCascade.x));
cascadeAttenuations.y = mix(1.0, evalShadowCascadeAttenuation(1, offsets, cascadeShadowCoords[1], slopeNdotL), float(isPixelOnCascade.y));
cascadeAttenuations.z = mix(1.0, evalShadowCascadeAttenuation(2, offsets, cascadeShadowCoords[2], slopeNdotL), float(isPixelOnCascade.z));
cascadeAttenuations.w = mix(1.0, evalShadowCascadeAttenuation(3, offsets, cascadeShadowCoords[3], slopeNdotL), float(isPixelOnCascade.w));
cascadeWeights.x = evalShadowCascadeWeight(cascadeShadowCoords[0]);
cascadeWeights.y = evalShadowCascadeWeight(cascadeShadowCoords[1]);

View file

@ -19,7 +19,6 @@ using namespace render;
void PrepareStencil::configure(const Config& config) {
_maskMode = config.maskMode;
_forceDraw = config.forceDraw;
}
graphics::MeshPointer PrepareStencil::getMesh() {
@ -43,6 +42,7 @@ gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() {
auto state = std::make_shared<gpu::State>();
drawMask(*state);
state->setColorWriteMask(gpu::State::WRITE_NONE);
state->setCullMode(gpu::State::CullMode::CULL_NONE);
_meshStencilPipeline = gpu::Pipeline::create(program, state);
}
@ -64,8 +64,28 @@ gpu::PipelinePointer PrepareStencil::getPaintStencilPipeline() {
void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) {
RenderArgs* args = renderContext->args;
// Only draw the stencil mask if in HMD mode or not forced.
if (!_forceDraw && (args->_displayMode != RenderArgs::STEREO_HMD)) {
if (args->_takingSnapshot) {
return;
}
StencilMaskMode maskMode = _maskMode;
std::function<void(gpu::Batch&)> maskOperator = [this](gpu::Batch& batch) {
auto mesh = getMesh();
batch.setIndexBuffer(mesh->getIndexBuffer());
batch.setInputFormat((mesh->getVertexFormat()));
batch.setInputStream(0, mesh->getVertexStream());
// Draw
auto part = mesh->getPartBuffer().get<graphics::Mesh::Part>(0);
batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex);
};
if (maskMode == StencilMaskMode::NONE) {
maskMode = args->_stencilMaskMode;
maskOperator = args->_stencilMaskOperator;
}
if (maskMode == StencilMaskMode::NONE || (maskMode == StencilMaskMode::MESH && !maskOperator)) {
return;
}
@ -74,20 +94,12 @@ void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::F
batch.setViewportTransform(args->_viewport);
if (_maskMode < 0) {
batch.setPipeline(getMeshStencilPipeline());
auto mesh = getMesh();
batch.setIndexBuffer(mesh->getIndexBuffer());
batch.setInputFormat((mesh->getVertexFormat()));
batch.setInputStream(0, mesh->getVertexStream());
// Draw
auto part = mesh->getPartBuffer().get<graphics::Mesh::Part>(0);
batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex);
} else {
if (maskMode == StencilMaskMode::PAINT) {
batch.setPipeline(getPaintStencilPipeline());
batch.draw(gpu::TRIANGLE_STRIP, 4);
} else if (maskMode == StencilMaskMode::MESH) {
batch.setPipeline(getMeshStencilPipeline());
maskOperator(batch);
}
});
}

View file

@ -15,17 +15,19 @@
#include <render/Engine.h>
#include <gpu/Pipeline.h>
#include <graphics/Geometry.h>
#include <StencilMaskMode.h>
class PrepareStencilConfig : public render::Job::Config {
Q_OBJECT
Q_PROPERTY(int maskMode MEMBER maskMode NOTIFY dirty)
Q_PROPERTY(bool forceDraw MEMBER forceDraw NOTIFY dirty)
Q_PROPERTY(StencilMaskMode maskMode MEMBER maskMode NOTIFY dirty)
public:
PrepareStencilConfig(bool enabled = true) : JobConfig(enabled) {}
int maskMode { 0 };
bool forceDraw { false };
// -1 -> don't force drawing (fallback to render args mode)
// 0 -> force draw without mesh
// 1 -> force draw with mesh
StencilMaskMode maskMode { StencilMaskMode::NONE };
signals:
void dirty();
@ -66,8 +68,7 @@ private:
graphics::MeshPointer _mesh;
graphics::MeshPointer getMesh();
int _maskMode { 0 };
bool _forceDraw { false };
StencilMaskMode _maskMode { StencilMaskMode::NONE };
};

View file

@ -18,9 +18,15 @@
<$declareMeshDeformer(_SCRIBE_NULL, _SCRIBE_NULL, 1, _SCRIBE_NULL, 1)$>
<$declareMeshDeformerActivation(1, 1)$>
<@include graphics/Material.slh@>
<@include graphics/MaterialTextures.slh@>
<$declareMaterialTexMapArrayBuffer()$>
<@include render-utils/ShaderConstants.h@>
layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS;
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01;
void main(void) {
vec4 deformedPosition = vec4(0.0, 0.0, 0.0, 0.0);
@ -33,5 +39,14 @@ void main(void) {
TransformObject obj = getTransformObject();
<$transformModelToClipPos(cam, obj, deformedPosition, gl_Position)$>
<$transformModelToWorldPos(obj, deformedPosition, _positionWS)$>
Material mat = getMaterial();
BITFIELD matKey = getMaterialKey(mat);
_texCoord01 = vec4(0.0, 0.0, 0.0, 0.0);
// If we have an opacity mask than we need the first tex coord
if ((matKey & OPACITY_MASK_MAP_BIT) != 0) {
TexMapArray texMapArray = getTexMapArray();
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _positionWS, _texCoord01.xy)$>
}
}

View file

@ -18,9 +18,15 @@
<$declareMeshDeformer(_SCRIBE_NULL, _SCRIBE_NULL, 1, 1, 1)$>
<$declareMeshDeformerActivation(1, 1)$>
<@include graphics/Material.slh@>
<@include graphics/MaterialTextures.slh@>
<$declareMaterialTexMapArrayBuffer()$>
<@include render-utils/ShaderConstants.h@>
layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS;
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01;
void main(void) {
vec4 deformedPosition = vec4(0.0, 0.0, 0.0, 0.0);
@ -33,4 +39,13 @@ void main(void) {
TransformObject obj = getTransformObject();
<$transformModelToClipPos(cam, obj, deformedPosition, gl_Position)$>
<$transformModelToWorldPos(obj, deformedPosition, _positionWS)$>
Material mat = getMaterial();
BITFIELD matKey = getMaterialKey(mat);
_texCoord01 = vec4(0.0, 0.0, 0.0, 0.0);
// If we have an opacity mask than we need the first tex coord
if ((matKey & OPACITY_MASK_MAP_BIT) != 0) {
TexMapArray texMapArray = getTexMapArray();
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _positionWS, _texCoord01.xy)$>
}
}

View file

@ -32,7 +32,7 @@ void main(void) {
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);

View file

@ -9,10 +9,26 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include graphics/Material.slh@>
<@include graphics/MaterialTextures.slh@>
<@include render-utils/ShaderConstants.h@>
<$declareMaterialTextures(ALBEDO, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$>
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
#define _texCoord0 _texCoord01.xy
layout(location=0) out vec4 _fragColor;
void main(void) {
Material mat = getMaterial();
BITFIELD matKey = getMaterialKey(mat);
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
<$discardTransparent(opacity)$>;
// pass-through to set z-buffer
_fragColor = vec4(1.0, 1.0, 1.0, 0.0);
}

View file

@ -13,12 +13,16 @@
<@include gpu/Inputs.slh@>
<@include gpu/Transform.slh@>
<@include graphics/Material.slh@>
<@include graphics/MaterialTextures.slh@>
<$declareStandardTransform()$>
<$declareMaterialTexMapArrayBuffer()$>
<@include render-utils/ShaderConstants.h@>
layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS;
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01;
void main(void) {
// standard transform
@ -26,4 +30,13 @@ void main(void) {
TransformObject obj = getTransformObject();
<$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
<$transformModelToWorldPos(obj, inPosition, _positionWS)$>
Material mat = getMaterial();
BITFIELD matKey = getMaterialKey(mat);
_texCoord01 = vec4(0.0, 0.0, 0.0, 0.0);
// If we have an opacity mask than we need the first tex coord
if ((matKey & OPACITY_MASK_MAP_BIT) != 0) {
TexMapArray texMapArray = getTexMapArray();
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _positionWS, _texCoord01.xy)$>
}
}

View file

@ -9,13 +9,18 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include graphics/Material.slh@>
<@include graphics/MaterialTextures.slh@>
<@include render-utils/ShaderConstants.h@>
<$declareMaterialTextures(ALBEDO, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$>
<@include Fade.slh@>
<$declareFadeFragment()$>
layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS;
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
#define _texCoord0 _texCoord01.xy
layout(location=0) out vec4 _fragColor;
@ -24,6 +29,14 @@ void main(void) {
<$fetchFadeObjectParams(fadeParams)$>
applyFadeClip(fadeParams, _positionWS.xyz);
Material mat = getMaterial();
BITFIELD matKey = getMaterialKey(mat);
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
<$discardTransparent(opacity)$>;
// pass-through to set z-buffer
_fragColor = vec4(1.0, 1.0, 1.0, 0.0);
}

View file

@ -16,6 +16,7 @@
#include <GLMHelpers.h>
#include <ViewFrustum.h>
#include <StencilMaskMode.h>
#include <gpu/Forward.h>
#include "Forward.h"
@ -133,6 +134,10 @@ namespace render {
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> _hudOperator;
gpu::TexturePointer _hudTexture;
bool _takingSnapshot { false };
StencilMaskMode _stencilMaskMode { StencilMaskMode::NONE };
std::function<void(gpu::Batch&)> _stencilMaskOperator;
};
}

View file

@ -185,6 +185,30 @@ QString PathUtils::generateTemporaryDir() {
return "";
}
bool PathUtils::deleteMyTemporaryDir(QString dirName) {
QDir rootTempDir = QDir::tempPath();
QString appName = qApp->applicationName();
QRegularExpression re { "^" + QRegularExpression::escape(appName) + "\\-(?<pid>\\d+)\\-(?<timestamp>\\d+)$" };
auto match = re.match(dirName);
auto pid = match.capturedRef("pid").toLongLong();
if (match.hasMatch() && rootTempDir.exists(dirName) && pid == qApp->applicationPid()) {
auto absoluteDirPath = QDir(rootTempDir.absoluteFilePath(dirName));
bool success = absoluteDirPath.removeRecursively();
if (success) {
qDebug() << " Removing temporary directory: " << absoluteDirPath.absolutePath();
} else {
qDebug() << " Failed to remove temporary directory: " << absoluteDirPath.absolutePath();
}
return success;
}
return false;
}
// Delete all temporary directories for an application
int PathUtils::removeTemporaryApplicationDirs(QString appName) {
if (appName.isNull()) {

View file

@ -56,6 +56,7 @@ public:
static QString getAppLocalDataFilePath(const QString& filename);
static QString generateTemporaryDir();
static bool deleteMyTemporaryDir(QString dirName);
static int removeTemporaryApplicationDirs(QString appName = QString::null);

View file

@ -40,6 +40,7 @@ int qMapURLStringMetaTypeId = qRegisterMetaType<QMap<QUrl,QString>>();
int socketErrorMetaTypeId = qRegisterMetaType<QAbstractSocket::SocketError>();
int voidLambdaType = qRegisterMetaType<std::function<void()>>();
int variantLambdaType = qRegisterMetaType<std::function<QVariant()>>();
int stencilModeMetaTypeId = qRegisterMetaType<StencilMaskMode>();
void registerMetaTypes(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, vec2ToScriptValue, vec2FromScriptValue);
@ -64,6 +65,8 @@ void registerMetaTypes(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, collisionToScriptValue, collisionFromScriptValue);
qScriptRegisterMetaType(engine, quuidToScriptValue, quuidFromScriptValue);
qScriptRegisterMetaType(engine, aaCubeToScriptValue, aaCubeFromScriptValue);
qScriptRegisterMetaType(engine, stencilMaskModeToScriptValue, stencilMaskModeFromScriptValue);
}
QScriptValue vec2ToScriptValue(QScriptEngine* engine, const glm::vec2& vec2) {
@ -636,6 +639,81 @@ void mat4FromScriptValue(const QScriptValue& object, glm::mat4& mat4) {
mat4[3][3] = object.property("r3c3").toVariant().toFloat();
}
QVariant mat4ToVariant(const glm::mat4& mat4) {
if (mat4 != mat4) {
// NaN
return QVariant();
}
QVariantMap object;
object["r0c0"] = mat4[0][0];
object["r1c0"] = mat4[0][1];
object["r2c0"] = mat4[0][2];
object["r3c0"] = mat4[0][3];
object["r0c1"] = mat4[1][0];
object["r1c1"] = mat4[1][1];
object["r2c1"] = mat4[1][2];
object["r3c1"] = mat4[1][3];
object["r0c2"] = mat4[2][0];
object["r1c2"] = mat4[2][1];
object["r2c2"] = mat4[2][2];
object["r3c2"] = mat4[2][3];
object["r0c3"] = mat4[3][0];
object["r1c3"] = mat4[3][1];
object["r2c3"] = mat4[3][2];
object["r3c3"] = mat4[3][3];
return object;
}
glm::mat4 mat4FromVariant(const QVariant& object, bool& valid) {
glm::mat4 mat4;
valid = false;
if (!object.isValid() || object.isNull()) {
return mat4;
} else {
const static auto getElement = [](const QVariantMap& map, const char * key, float& value, bool& everyConversionValid) {
auto variantValue = map[key];
if (variantValue.canConvert<float>()) {
value = variantValue.toFloat();
} else {
everyConversionValid = false;
}
};
auto map = object.toMap();
bool everyConversionValid = true;
getElement(map, "r0c0", mat4[0][0], everyConversionValid);
getElement(map, "r1c0", mat4[0][1], everyConversionValid);
getElement(map, "r2c0", mat4[0][2], everyConversionValid);
getElement(map, "r3c0", mat4[0][3], everyConversionValid);
getElement(map, "r0c1", mat4[1][0], everyConversionValid);
getElement(map, "r1c1", mat4[1][1], everyConversionValid);
getElement(map, "r2c1", mat4[1][2], everyConversionValid);
getElement(map, "r3c1", mat4[1][3], everyConversionValid);
getElement(map, "r0c2", mat4[2][0], everyConversionValid);
getElement(map, "r1c2", mat4[2][1], everyConversionValid);
getElement(map, "r2c2", mat4[2][2], everyConversionValid);
getElement(map, "r3c2", mat4[2][3], everyConversionValid);
getElement(map, "r0c3", mat4[3][0], everyConversionValid);
getElement(map, "r1c3", mat4[3][1], everyConversionValid);
getElement(map, "r2c3", mat4[3][2], everyConversionValid);
getElement(map, "r3c3", mat4[3][3], everyConversionValid);
if (everyConversionValid) {
valid = true;
}
return mat4;
}
}
glm::mat4 mat4FromVariant(const QVariant& object) {
bool valid = false;
return mat4FromVariant(object, valid);
}
QScriptValue qVectorVec3ColorToScriptValue(QScriptEngine* engine, const QVector<glm::vec3>& vector) {
QScriptValue array = engine->newArray();
for (int i = 0; i < vector.size(); i++) {
@ -1283,4 +1361,12 @@ QVariantMap parseTexturesToMap(QString newTextures, const QVariantMap& defaultTe
}
return toReturn;
}
QScriptValue stencilMaskModeToScriptValue(QScriptEngine* engine, const StencilMaskMode& stencilMode) {
return engine->newVariant((int)stencilMode);
}
void stencilMaskModeFromScriptValue(const QScriptValue& object, StencilMaskMode& stencilMode) {
stencilMode = StencilMaskMode(object.toVariant().toInt());
}

View file

@ -25,6 +25,7 @@
#include "shared/Bilateral.h"
#include "Transform.h"
#include "PhysicsCollisionGroups.h"
#include "StencilMaskMode.h"
class QColor;
class QUrl;
@ -67,6 +68,10 @@ void registerMetaTypes(QScriptEngine* engine);
QScriptValue mat4toScriptValue(QScriptEngine* engine, const glm::mat4& mat4);
void mat4FromScriptValue(const QScriptValue& object, glm::mat4& mat4);
QVariant mat4ToVariant(const glm::mat4& mat4);
glm::mat4 mat4FromVariant(const QVariant& object, bool& valid);
glm::mat4 mat4FromVariant(const QVariant& object);
/**jsdoc
* A 2-dimensional vector.
*
@ -729,5 +734,8 @@ void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector<MeshFace>
QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures);
Q_DECLARE_METATYPE(StencilMaskMode)
QScriptValue stencilMaskModeToScriptValue(QScriptEngine* engine, const StencilMaskMode& stencilMode);
void stencilMaskModeFromScriptValue(const QScriptValue& object, StencilMaskMode& stencilMode);
#endif // hifi_RegisteredMetaTypes_h

View file

@ -0,0 +1,18 @@
//
// Created by Sam Gondelman on 3/26/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_StencilMaskMode_h
#define hifi_StencilMaskMode_h
enum class StencilMaskMode {
NONE = -1, // for legacy reasons, this is -1
PAINT = 0,
MESH = 1
};
#endif // hifi_StencilMaskMode_h

View file

@ -44,7 +44,7 @@ QScriptValue variantToScriptValue(QVariant& qValue, QScriptEngine& scriptEngine)
if (qValue.canConvert<float>()) {
return qValue.toFloat();
}
qCDebug(shared) << "unhandled QScript type" << qValue.type();
//qCDebug(shared) << "unhandled QScript type" << qValue.type();
break;
}

View file

@ -152,6 +152,7 @@ public:
template <class... A>
static std::shared_ptr<Model> create(const std::string& name, const Varying& input, A&&... args) {
assert(input.canCast<I>());
return std::make_shared<Model>(name, input, std::make_shared<C>(), std::forward<A>(args)...);
}

View file

@ -17,6 +17,7 @@
#include <QtCore/QRegularExpression>
#include <QDesktopServices>
std::function<void(const QString&)> FileDialogHelper::_openDirectoryOperator = nullptr;
QUrl FileDialogHelper::home() {
return pathToUrl(QStandardPaths::standardLocations(QStandardPaths::HomeLocation)[0]);
@ -111,7 +112,9 @@ QStringList FileDialogHelper::drives() {
}
void FileDialogHelper::openDirectory(const QString& path) {
QDesktopServices::openUrl(path);
if (_openDirectoryOperator) {
_openDirectoryOperator(path);
}
}
QList<QUrl> FileDialogHelper::urlToList(const QUrl& url) {

View file

@ -16,6 +16,7 @@
#include <QtCore/QStringList>
#include <QtCore/QUrl>
#include <functional>
class FileDialogHelper : public QObject {
Q_OBJECT
@ -62,6 +63,7 @@ public:
Q_INVOKABLE QUrl saveHelper(const QString& saveText, const QUrl& currentFolder, const QStringList& selectionFilters);
Q_INVOKABLE QList<QUrl> urlToList(const QUrl& url);
static void setOpenDirectoryOperator(std::function<void(const QString&)> openDirectoryOperator) { _openDirectoryOperator = openDirectoryOperator; }
Q_INVOKABLE void openDirectory(const QString& path);
Q_INVOKABLE void monitorDirectory(const QString& path);
@ -72,6 +74,7 @@ signals:
private:
QFileSystemWatcher _fsWatcher;
QString _fsWatcherPath;
static std::function<void(const QString&)> _openDirectoryOperator;
};

View file

@ -18,7 +18,7 @@ if (WIN32 AND (NOT USE_GLES))
link_hifi_libraries(
shared task gl shaders gpu ${PLATFORM_GL_BACKEND} controllers ui qml
plugins ui-plugins display-plugins input-plugins
audio-client networking render-utils
audio-client networking render-utils graphics
${PLATFORM_GL_BACKEND}
)
include_hifi_library_headers(octree)

View file

@ -227,3 +227,66 @@ QVector<glm::vec3> OculusBaseDisplayPlugin::getSensorPositions() {
return result;
}
DisplayPlugin::StencilMaskMeshOperator OculusBaseDisplayPlugin::getStencilMaskMeshOperator() {
if (_session) {
if (!_stencilMeshesInitialized) {
_stencilMeshesInitialized = true;
ovr::for_each_eye([&](ovrEyeType eye) {
ovrFovStencilDesc stencilDesc = {
ovrFovStencil_HiddenArea, 0, eye,
_eyeRenderDescs[eye].Fov, _eyeRenderDescs[eye].HmdToEyePose.Orientation
};
// First we get the size of the buffer we need
ovrFovStencilMeshBuffer buffer = { 0, 0, nullptr, 0, 0, nullptr };
ovrResult result = ovr_GetFovStencil(_session, &stencilDesc, &buffer);
if (!OVR_SUCCESS(result)) {
_stencilMeshesInitialized = false;
return;
}
std::vector<ovrVector2f> ovrVertices(buffer.UsedVertexCount);
std::vector<uint16_t> ovrIndices(buffer.UsedIndexCount);
// Now we populate the actual buffer
buffer = { (int)ovrVertices.size(), 0, ovrVertices.data(), (int)ovrIndices.size(), 0, ovrIndices.data() };
result = ovr_GetFovStencil(_session, &stencilDesc, &buffer);
if (!OVR_SUCCESS(result)) {
_stencilMeshesInitialized = false;
return;
}
std::vector<glm::vec3> vertices;
vertices.reserve(ovrVertices.size());
for (auto& ovrVertex : ovrVertices) {
// We need the vertices in clip space
vertices.emplace_back(ovrVertex.x - (1.0f - (float)eye), 2.0f * ovrVertex.y - 1.0f, 0.0f);
}
std::vector<uint32_t> indices;
indices.reserve(ovrIndices.size());
for (auto& ovrIndex : ovrIndices) {
indices.push_back(ovrIndex);
}
_stencilMeshes[eye] = graphics::Mesh::createIndexedTriangles_P3F((uint32_t)vertices.size(), (uint32_t)indices.size(), vertices.data(), indices.data());
});
}
if (_stencilMeshesInitialized) {
return [&](gpu::Batch& batch) {
for (auto& mesh : _stencilMeshes) {
batch.setIndexBuffer(mesh->getIndexBuffer());
batch.setInputFormat((mesh->getVertexFormat()));
batch.setInputStream(0, mesh->getVertexStream());
// Draw
auto part = mesh->getPartBuffer().get<graphics::Mesh::Part>(0);
batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex);
}
};
}
}
return nullptr;
}

View file

@ -16,6 +16,8 @@
#define OVRPL_DISABLED
#include <OVR_Platform.h>
#include <graphics/Geometry.h>
class OculusBaseDisplayPlugin : public HmdDisplayPlugin {
using Parent = HmdDisplayPlugin;
public:
@ -34,6 +36,9 @@ public:
QRectF getPlayAreaRect() override;
QVector<glm::vec3> getSensorPositions() override;
virtual StencilMaskMode getStencilMaskMode() const override { return StencilMaskMode::MESH; }
virtual StencilMaskMeshOperator getStencilMaskMeshOperator() override;
protected:
void customizeContext() override;
void uncustomizeContext() override;
@ -52,4 +57,7 @@ protected:
// ovrLayerEyeFovDepth _depthLayer;
bool _hmdMounted { false };
bool _visible { true };
std::array<graphics::MeshPointer, 2> _stencilMeshes;
bool _stencilMeshesInitialized { false };
};

View file

@ -784,3 +784,48 @@ QRectF OpenVrDisplayPlugin::getPlayAreaRect() {
return QRectF(center.x, center.y, dimensions.x, dimensions.y);
}
DisplayPlugin::StencilMaskMeshOperator OpenVrDisplayPlugin::getStencilMaskMeshOperator() {
if (_system) {
if (!_stencilMeshesInitialized) {
_stencilMeshesInitialized = true;
for (auto eye : VR_EYES) {
vr::HiddenAreaMesh_t stencilMesh = _system->GetHiddenAreaMesh(eye);
if (stencilMesh.pVertexData && stencilMesh.unTriangleCount > 0) {
std::vector<glm::vec3> vertices;
std::vector<uint32_t> indices;
const int NUM_INDICES_PER_TRIANGLE = 3;
int numIndices = stencilMesh.unTriangleCount * NUM_INDICES_PER_TRIANGLE;
vertices.reserve(numIndices);
indices.reserve(numIndices);
for (int i = 0; i < numIndices; i++) {
vr::HmdVector2_t vertex2D = stencilMesh.pVertexData[i];
// We need the vertices in clip space
vertices.emplace_back(vertex2D.v[0] - (1.0f - (float)eye), 2.0f * vertex2D.v[1] - 1.0f, 0.0f);
indices.push_back(i);
}
_stencilMeshes[eye] = graphics::Mesh::createIndexedTriangles_P3F((uint32_t)vertices.size(), (uint32_t)indices.size(), vertices.data(), indices.data());
} else {
_stencilMeshesInitialized = false;
}
}
}
if (_stencilMeshesInitialized) {
return [&](gpu::Batch& batch) {
for (auto& mesh : _stencilMeshes) {
batch.setIndexBuffer(mesh->getIndexBuffer());
batch.setInputFormat((mesh->getVertexFormat()));
batch.setInputStream(0, mesh->getVertexStream());
// Draw
auto part = mesh->getPartBuffer().get<graphics::Mesh::Part>(0);
batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex);
}
};
}
}
return nullptr;
}

View file

@ -13,6 +13,8 @@
#include <display-plugins/hmd/HmdDisplayPlugin.h>
#include <graphics/Geometry.h>
const float TARGET_RATE_OpenVr = 90.0f; // FIXME: get from sdk tracked device property? This number is vive-only.
namespace gl {
@ -67,6 +69,9 @@ public:
QRectF getPlayAreaRect() override;
virtual StencilMaskMode getStencilMaskMode() const override { return StencilMaskMode::MESH; }
virtual StencilMaskMeshOperator getStencilMaskMeshOperator() override;
protected:
bool internalActivate() override;
void internalDeactivate() override;
@ -94,4 +99,7 @@ private:
bool _asyncReprojectionActive { false };
bool _hmdMounted { false };
std::array<graphics::MeshPointer, 2> _stencilMeshes;
bool _stencilMeshesInitialized { false };
};

View file

@ -10,7 +10,12 @@
//
"use strict";
// traverse task tree
// traverse task tree recursively
//
// @param root: the root job config from where to traverse
// @param functor: the functor function() which is applied on every subjobs of root traversed
// if return true, then 'task_tree' is called recursively on that subjob
// @param depth: the depth of the recurse loop since the initial call.
function task_traverse(root, functor, depth) {
if (root.isTask()) {
depth++;
@ -22,6 +27,9 @@ function task_traverse(root, functor, depth) {
}
}
}
// same function as 'task_traverse' with the depth being 0
// and visisting the root job first.
function task_traverseTree(root, functor) {
if (functor(root, 0, 0)) {
task_traverse(root, functor, 0)

View file

@ -0,0 +1,123 @@
//
// jet/TaskPropView.qml
//
// Created by Sam Gateau, 2018/05/09
// Copyright 2018 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
//
import QtQuick 2.7
import QtQuick.Controls 1.4 as Original
import QtQuick.Controls.Styles 1.4
import stylesUit 1.0
import controlsUit 1.0 as HifiControls
import "../../prop" as Prop
import "../jet.js" as Jet
Prop.PropGroup {
id: root;
property var rootConfig : Render
property var jobPath: ""
property alias label: root.label
property alias indentDepth: root.indentDepth
property var showProps: true
property var showSubs: true
property var jobEnabled: rootConfig.getConfig(jobPath).enabled
property var jobCpuTime: pullCpuTime()
function pullCpuTime() {
if (jobEnabled) {
return rootConfig.getConfig(jobPath).cpuRunTime.toPrecision(3);
} else {
return '.'
}
}
property var toggleJobActivation: function() {
console.log("the button has been pressed and jobEnabled is " + jobEnabled )
jobEnabled = !jobEnabled;
rootConfig.getConfig(jobPath).enabled = jobEnabled;
}
// Panel Header Data Component
panelHeaderData: Component {
Item {
id: header
Prop.PropLabel {
text: root.label
horizontalAlignment: Text.AlignHCenter
anchors.left: parent.left
anchors.right: cpuTime.left
anchors.verticalCenter: parent.verticalCenter
}
Prop.PropLabel {
id: cpuTime
visible: false // root.jobEnabled
width: 50
text: jobCpuTime
horizontalAlignment: Text.AlignLeft
anchors.rightMargin: 5
anchors.right:enabledIcon.right
anchors.verticalCenter: parent.verticalCenter
}
Prop.PropCanvasIcon {
id: enabledIcon
anchors.right:parent.right
anchors.verticalCenter: parent.verticalCenter
filled: root.jobEnabled
fillColor: (root.jobEnabled ? global.colorGreenHighlight : global.colorOrangeAccent)
icon: 5
iconMouseArea.onClicked: { toggleJobActivation() }
}
}
}
function populatePropItems() {
var propsModel = []
var props = Jet.job_propKeys(rootConfig.getConfig(jobPath));
// console.log(JSON.stringify(props));
if (showProps) {
for (var p in props) {
propsModel.push({"object": rootConfig.getConfig(jobPath), "property":props[p] })
}
root.updatePropItems(root.propItemsPanel, propsModel);
}
if (showSubs) {
Jet.task_traverse(rootConfig.getConfig(jobPath),
function(job, depth, index) {
var component = Qt.createComponent("./TaskPropView.qml");
component.createObject(root.propItemsPanel, {
"label": job.objectName,
"rootConfig": root.rootConfig,
"jobPath": root.jobPath + '.' + job.objectName,
"showProps": root.showProps,
"showSubs": root.showSubs,
"indentDepth": root.indentDepth + 1,
})
/* var component = Qt.createComponent("../../prop/PropItem.qml");
component.createObject(root.propItemsPanel, {
"label": root.jobPath + '.' + job.objectName + ' num=' + index,
})*/
// propsModel.push({"type": "printLabel", "label": root.jobPath + '.' + job.objectName + ' num=' + index })
return (depth < 1);
}, 0)
}
}
Component.onCompleted: {
populatePropItems()
}
}

View file

@ -1,3 +1,4 @@
TaskList 1.0 TaskList.qml
TaskViewList 1.0 TaskViewList.qml
TaskTimeFrameView 1.0 TaskTimeFrameView.qml
TaskPropView 1.0 TaskPropView.qml

Some files were not shown because too many files have changed in this diff Show more