Merge branch 'master' of https://github.com/highfidelity/hifi into commerce_QmlWhitelist

This commit is contained in:
Zach Fox 2017-12-11 09:52:32 -08:00
commit b81cdf49ef
46 changed files with 3882 additions and 311 deletions

View file

@ -13,9 +13,25 @@ setup_memory_debugger()
link_hifi_libraries(
audio avatars octree gpu model fbx entities
networking animation recording shared script-engine embedded-webserver
controllers physics plugins midi baking image
controllers physics plugins midi image
)
add_dependencies(${TARGET_NAME} oven)
if (WIN32)
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
$<TARGET_FILE_DIR:oven>
$<TARGET_FILE_DIR:${TARGET_NAME}>)
else()
add_custom_command(
TARGET ${TARGET_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink
$<TARGET_FILE:oven>
$<TARGET_FILE_DIR:${TARGET_NAME}>/oven)
endif()
if (WIN32)
package_libraries_for_deployment()
endif()

View file

@ -29,11 +29,10 @@
#include <QtCore/QUrlQuery>
#include <ClientServerUtils.h>
#include <FBXBaker.h>
#include <JSBaker.h>
#include <NodeType.h>
#include <SharedUtil.h>
#include <PathUtils.h>
#include <image/Image.h>
#include "AssetServerLogging.h"
#include "BakeAssetTask.h"
@ -250,7 +249,7 @@ AssetServer::AssetServer(ReceivedMessage& message) :
image::setNormalTexturesCompressionEnabled(true);
image::setCubeTexturesCompressionEnabled(true);
BAKEABLE_TEXTURE_EXTENSIONS = TextureBaker::getSupportedFormats();
BAKEABLE_TEXTURE_EXTENSIONS = image::getSupportedFormats();
qDebug() << "Supported baking texture formats:" << BAKEABLE_MODEL_EXTENSIONS;
// Most of the work will be I/O bound, reading from disk and constructing packet objects,
@ -416,6 +415,9 @@ void AssetServer::completeSetup() {
if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < MAX_UPLOAD_SIZE) {
_filesizeLimit = assetsFilesizeLimit * BITS_PER_MEGABITS;
}
PathUtils::removeTemporaryApplicationDirs();
PathUtils::removeTemporaryApplicationDirs("Oven");
}
void AssetServer::cleanupUnmappedFiles() {

View file

@ -11,11 +11,18 @@
#include "BakeAssetTask.h"
#include <QtCore/QThread>
#include <mutex>
#include <QtCore/QThread>
#include <QCoreApplication>
#include <FBXBaker.h>
#include <PathUtils.h>
#include <JSBaker.h>
static const int OVEN_STATUS_CODE_SUCCESS { 0 };
static const int OVEN_STATUS_CODE_FAIL { 1 };
static const int OVEN_STATUS_CODE_ABORT { 2 };
std::once_flag registerMetaTypesFlag;
BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) :
_assetHash(assetHash),
@ -23,6 +30,10 @@ BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetP
_filePath(filePath)
{
std::call_once(registerMetaTypesFlag, []() {
qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus");
});
}
void cleanupTempFiles(QString tempOutputDir, std::vector<QString> files) {
@ -41,67 +52,76 @@ void cleanupTempFiles(QString tempOutputDir, std::vector<QString> files) {
};
void BakeAssetTask::run() {
_isBaking.store(true);
qRegisterMetaType<QVector<QString> >("QVector<QString>");
TextureBakerThreadGetter fn = []() -> QThread* { return QThread::currentThread(); };
QString tempOutputDir;
if (_assetPath.endsWith(".fbx")) {
tempOutputDir = PathUtils::generateTemporaryDir();
_baker = std::unique_ptr<FBXBaker> {
new FBXBaker(QUrl("file:///" + _filePath), fn, tempOutputDir)
};
} else if (_assetPath.endsWith(".js", Qt::CaseInsensitive)) {
_baker = std::unique_ptr<JSBaker>{
new JSBaker(QUrl("file:///" + _filePath), PathUtils::generateTemporaryDir())
};
} else {
tempOutputDir = PathUtils::generateTemporaryDir();
_baker = std::unique_ptr<TextureBaker> {
new TextureBaker(QUrl("file:///" + _filePath), image::TextureUsage::CUBE_TEXTURE,
tempOutputDir)
};
if (_isBaking.exchange(true)) {
qWarning() << "Tried to start bake asset task while already baking";
return;
}
QEventLoop loop;
connect(_baker.get(), &Baker::finished, &loop, &QEventLoop::quit);
connect(_baker.get(), &Baker::aborted, &loop, &QEventLoop::quit);
QMetaObject::invokeMethod(_baker.get(), "bake", Qt::QueuedConnection);
loop.exec();
QString tempOutputDir = PathUtils::generateTemporaryDir();
auto base = QFileInfo(QCoreApplication::applicationFilePath()).absoluteDir();
QString path = base.absolutePath() + "/oven";
QString extension = _assetPath.mid(_assetPath.lastIndexOf('.') + 1);
QStringList args {
"-i", _filePath,
"-o", tempOutputDir,
"-t", extension,
};
if (_baker->wasAborted()) {
qDebug() << "Aborted baking: " << _assetHash << _assetPath;
_ovenProcess.reset(new QProcess());
_wasAborted.store(true);
connect(_ovenProcess.get(), static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
this, [this, tempOutputDir](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug() << "Baking process finished: " << exitCode << exitStatus;
cleanupTempFiles(tempOutputDir, _baker->getOutputFiles());
if (exitStatus == QProcess::CrashExit) {
if (_wasAborted) {
emit bakeAborted(_assetHash, _assetPath);
} else {
QString errors = "Fatal error occurred while baking";
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 bakeAborted(_assetHash, _assetPath);
} else if (_baker->hasErrors()) {
qDebug() << "Failed to bake: " << _assetHash << _assetPath << _baker->getErrors();
emit bakeComplete(_assetHash, _assetPath, tempOutputDir, outputFiles);
} else if (exitStatus == QProcess::NormalExit && exitCode == OVEN_STATUS_CODE_ABORT) {
_wasAborted.store(true);
emit bakeAborted(_assetHash, _assetPath);
} else {
QString errors;
if (exitCode == OVEN_STATUS_CODE_FAIL) {
QDir outputDir = tempOutputDir;
auto errorFilePath = outputDir.absoluteFilePath("errors.txt");
QFile errorFile { errorFilePath };
if (errorFile.open(QIODevice::ReadOnly)) {
errors = errorFile.readAll();
errorFile.close();
} else {
errors = "Unknown error occurred while baking";
}
}
emit bakeFailed(_assetHash, _assetPath, errors);
}
auto errors = _baker->getErrors().join('\n'); // Join error list into a single string for convenience
_didFinish.store(true);
cleanupTempFiles(tempOutputDir, _baker->getOutputFiles());
});
qDebug() << "Starting oven for " << _assetPath;
_ovenProcess->start(path, args, QIODevice::ReadOnly);
if (!_ovenProcess->waitForStarted(-1)) {
QString errors = "Oven process failed to start";
emit bakeFailed(_assetHash, _assetPath, errors);
} else {
auto vectorOutputFiles = QVector<QString>::fromStdVector(_baker->getOutputFiles());
qDebug() << "Finished baking: " << _assetHash << _assetPath << vectorOutputFiles;
_didFinish.store(true);
emit bakeComplete(_assetHash, _assetPath, tempOutputDir, vectorOutputFiles);
return;
}
_ovenProcess->waitForFinished();
}
void BakeAssetTask::abort() {
if (_baker) {
_baker->abort();
if (!_wasAborted.exchange(true)) {
_ovenProcess->terminate();
}
}

View file

@ -17,9 +17,10 @@
#include <QtCore/QDebug>
#include <QtCore/QObject>
#include <QtCore/QRunnable>
#include <QDir>
#include <QProcess>
#include <AssetUtils.h>
#include <Baker.h>
class BakeAssetTask : public QObject, public QRunnable {
Q_OBJECT
@ -32,7 +33,6 @@ public:
void abort();
bool wasAborted() const { return _wasAborted.load(); }
bool didFinish() const { return _didFinish.load(); }
signals:
void bakeComplete(QString assetHash, QString assetPath, QString tempOutputDir, QVector<QString> outputFiles);
@ -44,9 +44,8 @@ private:
AssetHash _assetHash;
AssetPath _assetPath;
QString _filePath;
std::unique_ptr<Baker> _baker;
std::unique_ptr<QProcess> _ovenProcess { nullptr };
std::atomic<bool> _wasAborted { false };
std::atomic<bool> _didFinish { false };
};
#endif // hifi_BakeAssetTask_h

View file

@ -21,6 +21,8 @@ Item {
signal newViewRequestedCallback(var request)
signal loadingChangedCallback(var loadRequest)
width: parent.width
property bool interactive: false
StylesUIt.HifiConstants {
@ -58,7 +60,8 @@ Item {
WebEngineView {
id: webViewCore
anchors.fill: parent
width: parent.width
height: parent.height
profile: HFWebEngineProfile;
settings.pluginsEnabled: true
@ -91,20 +94,19 @@ Item {
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
property string newUrl: ""
Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
// Ensure the JS from the web-engine makes it to our logging
webViewCore.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
});
if (webViewCoreUserAgent !== undefined) {
webViewCore.profile.httpUserAgent = webViewCoreUserAgent
} else {
webViewCore.profile.httpUserAgent += " (HighFidelityInterface)";
}
// Ensure the JS from the web-engine makes it to our logging
webViewCore.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
});
}
onFeaturePermissionRequested: {

View file

@ -26,6 +26,7 @@ Rectangle {
// Style
color: "#E3E3E3";
// Properties
property bool debug: false;
property int myCardWidth: width - upperRightInfoContainer.width;
property int myCardHeight: 80;
property int rowHeight: 60;
@ -1120,7 +1121,9 @@ Rectangle {
break;
case 'connections':
var data = message.params;
console.log('Got connection data: ', JSON.stringify(data));
if (pal.debug) {
console.log('Got connection data: ', JSON.stringify(data));
}
connectionsUserModelData = data;
sortConnectionsModel();
connectionsLoading.visible = false;

View file

@ -6009,7 +6009,7 @@ bool Application::acceptURL(const QString& urlString, bool defaultUpload) {
}
}
if (defaultUpload) {
if (defaultUpload && !url.fileName().isEmpty() && url.isLocalFile()) {
showAssetServerWidget(urlString);
}
return defaultUpload;

View file

@ -1,4 +1,4 @@
//
//
// WalletScriptingInterface.cpp
// interface/src/scripting
//
@ -22,31 +22,3 @@ void WalletScriptingInterface::refreshWalletStatus() {
auto wallet = DependencyManager::get<Wallet>();
wallet->getWalletStatus();
}
static const QString CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml";
void WalletScriptingInterface::buy(const QString& name, const QString& id, const int& price, const QString& href) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "buy", Q_ARG(const QString&, name), Q_ARG(const QString&, id), Q_ARG(const int&, price), Q_ARG(const QString&, href));
return;
}
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
tablet->loadQMLSource(CHECKOUT_QML_PATH);
DependencyManager::get<HMDScriptingInterface>()->openTablet();
QQuickItem* root = nullptr;
if (tablet->getToolbarMode() || (!tablet->getTabletRoot() && !qApp->isHMDMode())) {
root = DependencyManager::get<OffscreenUi>()->getRootItem();
} else {
root = tablet->getTabletRoot();
}
CheckoutProxy* checkout = new CheckoutProxy(root->findChild<QObject*>("checkout"));
// Example: Wallet.buy("Test Flaregun", "0d90d21c-ce7a-4990-ad18-e9d2cf991027", 17, "http://mpassets.highfidelity.com/0d90d21c-ce7a-4990-ad18-e9d2cf991027-v1/flaregun.json");
checkout->writeProperty("itemName", name);
checkout->writeProperty("itemId", id);
checkout->writeProperty("itemPrice", price);
checkout->writeProperty("itemHref", href);
}

View file

@ -41,8 +41,6 @@ public:
Q_INVOKABLE uint getWalletStatus() { return _walletStatus; }
void setWalletStatus(const uint& status) { _walletStatus = status; }
Q_INVOKABLE void buy(const QString& name, const QString& id, const int& price, const QString& href);
signals:
void walletStatusChanged();
void walletNotSetup();

View file

@ -607,7 +607,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
return;
}
if (!TextureBaker::getSupportedFormats().contains(textureFileInfo.suffix())) {
if (!image::getSupportedFormats().contains(textureFileInfo.suffix())) {
// this is a texture format we don't bake, skip it
handleWarning(fbxTextureFileName + " is not a bakeable texture format");
continue;

View file

@ -61,14 +61,6 @@ void TextureBaker::abort() {
_abortProcessing.store(true);
}
const QStringList TextureBaker::getSupportedFormats() {
auto formats = QImageReader::supportedImageFormats();
QStringList stringFormats;
std::transform(formats.begin(), formats.end(), std::back_inserter(stringFormats),
[](QByteArray& format) -> QString { return format; });
return stringFormats;
}
void TextureBaker::loadTexture() {
// check if the texture is local or first needs to be downloaded
if (_textureURL.isLocalFile()) {
@ -121,8 +113,15 @@ void TextureBaker::handleTextureNetworkReply() {
}
void TextureBaker::processTexture() {
auto processedTexture = image::processImage(_originalTexture, _textureURL.toString().toStdString(),
// the baked textures need to have the source hash added for cache checks in Interface
// so we add that to the processed texture before handling it off to be serialized
auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5);
std::string hash = hashData.toHex().toStdString();
// IMPORTANT: _originalTexture is empty past this point
auto processedTexture = image::processImage(std::move(_originalTexture), _textureURL.toString().toStdString(),
ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, _abortProcessing);
processedTexture->setSourceHash(hash);
if (shouldStop()) {
return;
@ -133,11 +132,6 @@ void TextureBaker::processTexture() {
return;
}
// the baked textures need to have the source hash added for cache checks in Interface
// so we add that to the processed texture before handling it off to be serialized
auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5);
std::string hash = hashData.toHex().toStdString();
processedTexture->setSourceHash(hash);
auto memKTX = gpu::Texture::serialize(*processedTexture);

View file

@ -31,8 +31,6 @@ public:
const QDir& outputDirectory, const QString& bakedFilename = QString(),
const QByteArray& textureContent = QByteArray());
static const QStringList getSupportedFormats();
const QByteArray& getOriginalTexture() const { return _originalTexture; }
QUrl getTextureURL() const { return _textureURL; }

View file

@ -1149,7 +1149,7 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin
if (model && model->isLoaded()) {
if (!entity->_dimensionsInitialized || entity->_needsInitialSimulation) {
return true;
}
}
// Check to see if we need to update the model bounds
if (entity->needsUpdateModelBounds()) {

View file

@ -75,6 +75,14 @@ glm::uvec2 rectifyToSparseSize(const glm::uvec2& size) {
namespace image {
const QStringList getSupportedFormats() {
auto formats = QImageReader::supportedImageFormats();
QStringList stringFormats;
std::transform(formats.begin(), formats.end(), std::back_inserter(stringFormats),
[](QByteArray& format) -> QString { return format; });
return stringFormats;
}
QImage::Format QIMAGE_HDR_FORMAT = QImage::Format_RGB30;
TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) {
@ -110,64 +118,64 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con
}
}
gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureColorFromImage(srcImage, srcImageName, true, abortProcessing);
return process2DTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing);
}
gpu::TexturePointer TextureUsage::create2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::create2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureNormalMapFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureNormalMapFromImage(srcImage, srcImageName, true, abortProcessing);
return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, true, abortProcessing);
}
gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureGrayscaleFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureGrayscaleFromImage(srcImage, srcImageName, true, abortProcessing);
return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, true, abortProcessing);
}
gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return process2DTextureGrayscaleFromImage(srcImage, srcImageName, false, abortProcessing);
return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
gpu::TexturePointer TextureUsage::createCubeTextureFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return processCubeTextureColorFromImage(srcImage, srcImageName, true, abortProcessing);
return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing);
}
gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(QImage&& srcImage, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing) {
return processCubeTextureColorFromImage(srcImage, srcImageName, false, abortProcessing);
return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
@ -238,33 +246,43 @@ uint32 packR11G11B10F(const glm::vec3& color) {
return glm::packF2x11_1x10(ucolor);
}
gpu::TexturePointer processImage(const QByteArray& content, const std::string& filename,
int maxNumPixels, TextureUsage::Type textureType,
const std::atomic<bool>& abortProcessing) {
QImage processRawImageData(QByteArray&& content, const std::string& filename) {
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QByteArray localCopy = std::move(content);
// Help the QImage loader by extracting the image file format from the url filename ext.
// Some tga are not created properly without it.
auto filenameExtension = filename.substr(filename.find_last_of('.') + 1);
QBuffer buffer;
buffer.setData(content);
buffer.setData(localCopy);
QImageReader imageReader(&buffer, filenameExtension.c_str());
QImage image;
if (imageReader.canRead()) {
image = imageReader.read();
return imageReader.read();
} else {
// Extension could be incorrect, try to detect the format from the content
QImageReader newImageReader;
newImageReader.setDecideFormatFromContent(true);
buffer.setData(content);
buffer.setData(localCopy);
newImageReader.setDevice(&buffer);
if (newImageReader.canRead()) {
qCWarning(imagelogging) << "Image file" << filename.c_str() << "has extension" << filenameExtension.c_str()
<< "but is actually a" << qPrintable(newImageReader.format()) << "(recovering)";
image = newImageReader.read();
return newImageReader.read();
}
}
return QImage();
}
gpu::TexturePointer processImage(QByteArray&& content, const std::string& filename,
int maxNumPixels, TextureUsage::Type textureType,
const std::atomic<bool>& abortProcessing) {
QImage image = processRawImageData(std::move(content), filename);
int imageWidth = image.width();
int imageHeight = image.height();
@ -282,22 +300,26 @@ gpu::TexturePointer processImage(const QByteArray& content, const std::string& f
int originalHeight = imageHeight;
imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f);
imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f);
QImage newImage = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
image.swap(newImage);
image = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
qCDebug(imagelogging).nospace() << "Downscaled " << filename.c_str() << " (" <<
QSize(originalWidth, originalHeight) << " to " <<
QSize(imageWidth, imageHeight) << ")";
}
auto loader = TextureUsage::getTextureLoaderForType(textureType);
auto texture = loader(image, filename, abortProcessing);
auto texture = loader(std::move(image), filename, abortProcessing);
return texture;
}
QImage processSourceImage(const QImage& srcImage, bool cubemap) {
QImage processSourceImage(QImage&& srcImage, bool cubemap) {
PROFILE_RANGE(resource_parse, "processSourceImage");
const glm::uvec2 srcImageSize = toGlm(srcImage.size());
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QImage localCopy = std::move(srcImage);
const glm::uvec2 srcImageSize = toGlm(localCopy.size());
glm::uvec2 targetSize = srcImageSize;
while (glm::any(glm::greaterThan(targetSize, MAX_TEXTURE_SIZE))) {
@ -319,10 +341,10 @@ QImage processSourceImage(const QImage& srcImage, bool cubemap) {
if (targetSize != srcImageSize) {
PROFILE_RANGE(resource_parse, "processSourceImage Rectify");
qCDebug(imagelogging) << "Resizing texture from " << srcImageSize.x << "x" << srcImageSize.y << " to " << targetSize.x << "x" << targetSize.y;
return srcImage.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
return localCopy.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
return srcImage;
return localCopy;
}
#if defined(NVTT_API)
@ -429,10 +451,14 @@ public:
}
};
void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atomic<bool>& abortProcessing, int face) {
assert(image.format() == QIMAGE_HDR_FORMAT);
void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomic<bool>& abortProcessing, int face) {
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QImage localCopy = std::move(image);
const int width = image.width(), height = image.height();
assert(localCopy.format() == QIMAGE_HDR_FORMAT);
const int width = localCopy.width(), height = localCopy.height();
std::vector<glm::vec4> data;
std::vector<glm::vec4>::iterator dataIt;
auto mipFormat = texture->getStoredMipFormat();
@ -471,10 +497,10 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom
return;
}
data.resize(width*height);
data.resize(width * height);
dataIt = data.begin();
for (auto lineNb = 0; lineNb < height; lineNb++) {
const uint32* srcPixelIt = reinterpret_cast<const uint32*>( image.constScanLine(lineNb) );
const uint32* srcPixelIt = reinterpret_cast<const uint32*>(localCopy.constScanLine(lineNb));
const uint32* srcPixelEnd = srcPixelIt + width;
while (srcPixelIt < srcPixelEnd) {
@ -485,6 +511,9 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom
}
assert(dataIt == data.end());
// We're done with the localCopy, free up the memory to avoid bloating the heap
localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one.
nvtt::OutputOptions outputOptions;
outputOptions.setOutputHeader(false);
std::unique_ptr<nvtt::OutputHandler> outputHandler;
@ -497,7 +526,7 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom
// Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats
outputHandler.reset(new PackedFloatOutputHandler(texture, face, mipFormat));
} else {
outputHandler.reset( new OutputHandler(texture, face) );
outputHandler.reset(new OutputHandler(texture, face));
}
outputOptions.setOutputHandler(outputHandler.get());
@ -518,13 +547,17 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom
}
}
void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic<bool>& abortProcessing, int face) {
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
void generateLDRMips(gpu::Texture* texture, QImage&& image, const std::atomic<bool>& abortProcessing, int face) {
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QImage localCopy = std::move(image);
if (localCopy.format() != QImage::Format_ARGB32) {
localCopy = localCopy.convertToFormat(QImage::Format_ARGB32);
}
const int width = image.width(), height = image.height();
const void* data = static_cast<const void*>(image.constBits());
const int width = localCopy.width(), height = localCopy.height();
const void* data = static_cast<const void*>(localCopy.constBits());
nvtt::TextureType textureType = nvtt::TextureType_2D;
nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB;
@ -537,7 +570,11 @@ void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic<boo
nvtt::InputOptions inputOptions;
inputOptions.setTextureLayout(textureType, width, height);
inputOptions.setMipmapData(data, width, height);
// setMipmapData copies the memory, so free up the memory afterward to avoid bloating the heap
data = nullptr;
localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one.
inputOptions.setFormat(inputFormat);
inputOptions.setGamma(inputGamma, outputGamma);
@ -641,14 +678,14 @@ void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic<boo
void generateMips(gpu::Texture* texture, QImage& image, const std::atomic<bool>& abortProcessing = false, int face = -1) {
void generateMips(gpu::Texture* texture, QImage&& image, const std::atomic<bool>& abortProcessing = false, int face = -1) {
#if CPU_MIPMAPS
PROFILE_RANGE(resource_parse, "generateMips");
if (image.format() == QIMAGE_HDR_FORMAT) {
generateHDRMips(texture, image, abortProcessing, face);
generateHDRMips(texture, std::move(image), abortProcessing, face);
} else {
generateLDRMips(texture, image, abortProcessing, face);
generateLDRMips(texture, std::move(image), abortProcessing, face);
}
#else
texture->setAutoGenerateMips(true);
@ -682,10 +719,11 @@ void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAs
validAlpha = (numOpaques != NUM_PIXELS);
}
gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName,
bool isStrict, const std::atomic<bool>& abortProcessing) {
PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage");
QImage image = processSourceImage(srcImage, false);
QImage image = processSourceImage(std::move(srcImage), false);
bool validAlpha = image.hasAlphaChannel();
bool alphaAsMask = false;
@ -731,7 +769,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& s
}
theTexture->setUsage(usage.build());
theTexture->setStoredMipFormat(formatMip);
generateMips(theTexture.get(), image, abortProcessing);
generateMips(theTexture.get(), std::move(image), abortProcessing);
}
return theTexture;
@ -749,16 +787,20 @@ double mapComponent(double sobelValue) {
return (sobelValue + 1.0) * factor;
}
QImage processBumpMap(QImage& image) {
if (image.format() != QImage::Format_Grayscale8) {
image = image.convertToFormat(QImage::Format_Grayscale8);
QImage processBumpMap(QImage&& image) {
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QImage localCopy = std::move(image);
if (localCopy.format() != QImage::Format_Grayscale8) {
localCopy = localCopy.convertToFormat(QImage::Format_Grayscale8);
}
// PR 5540 by AlessandroSigna integrated here as a specialized TextureLoader for bumpmaps
// The conversion is done using the Sobel Filter to calculate the derivatives from the grayscale image
const double pStrength = 2.0;
int width = image.width();
int height = image.height();
int width = localCopy.width();
int height = localCopy.height();
QImage result(width, height, QImage::Format_ARGB32);
@ -771,14 +813,14 @@ QImage processBumpMap(QImage& image) {
const int jPrevClamped = clampPixelCoordinate(j - 1, height - 1);
// surrounding pixels
const QRgb topLeft = image.pixel(iPrevClamped, jPrevClamped);
const QRgb top = image.pixel(iPrevClamped, j);
const QRgb topRight = image.pixel(iPrevClamped, jNextClamped);
const QRgb right = image.pixel(i, jNextClamped);
const QRgb bottomRight = image.pixel(iNextClamped, jNextClamped);
const QRgb bottom = image.pixel(iNextClamped, j);
const QRgb bottomLeft = image.pixel(iNextClamped, jPrevClamped);
const QRgb left = image.pixel(i, jPrevClamped);
const QRgb topLeft = localCopy.pixel(iPrevClamped, jPrevClamped);
const QRgb top = localCopy.pixel(iPrevClamped, j);
const QRgb topRight = localCopy.pixel(iPrevClamped, jNextClamped);
const QRgb right = localCopy.pixel(i, jNextClamped);
const QRgb bottomRight = localCopy.pixel(iNextClamped, jNextClamped);
const QRgb bottom = localCopy.pixel(iNextClamped, j);
const QRgb bottomLeft = localCopy.pixel(iNextClamped, jPrevClamped);
const QRgb left = localCopy.pixel(i, jPrevClamped);
// take their gray intensities
// since it's a grayscale image, the value of each component RGB is the same
@ -807,13 +849,13 @@ QImage processBumpMap(QImage& image) {
return result;
}
gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName,
bool isBumpMap, const std::atomic<bool>& abortProcessing) {
PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage");
QImage image = processSourceImage(srcImage, false);
QImage image = processSourceImage(std::move(srcImage), false);
if (isBumpMap) {
image = processBumpMap(image);
image = processBumpMap(std::move(image));
}
// Make sure the normal map source image is ARGB32
@ -833,17 +875,17 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(const QImag
theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
generateMips(theTexture.get(), image, abortProcessing);
generateMips(theTexture.get(), std::move(image), abortProcessing);
}
return theTexture;
}
gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName,
bool isInvertedPixels,
const std::atomic<bool>& abortProcessing) {
PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage");
QImage image = processSourceImage(srcImage, false);
QImage image = processSourceImage(std::move(srcImage), false);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
@ -869,7 +911,7 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(const QImag
theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
generateMips(theTexture.get(), image, abortProcessing);
generateMips(theTexture.get(), std::move(image), abortProcessing);
}
return theTexture;
@ -939,7 +981,7 @@ public:
static QImage extractEquirectangularFace(const QImage& source, gpu::Texture::CubeFace face, int faceWidth) {
QImage image(faceWidth, faceWidth, source.format());
glm::vec2 dstInvSize(1.0f / (float)image.width(), 1.0f / (float)image.height());
glm::vec2 dstInvSize(1.0f / (float)source.width(), 1.0f / (float)source.height());
struct CubeToXYZ {
gpu::Texture::CubeFace _face;
@ -1142,8 +1184,12 @@ const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS)
//#define DEBUG_COLOR_PACKING
QImage convertToHDRFormat(QImage srcImage, gpu::Element format) {
QImage hdrImage(srcImage.width(), srcImage.height(), (QImage::Format)QIMAGE_HDR_FORMAT);
QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format) {
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QImage localCopy = std::move(srcImage);
QImage hdrImage(localCopy.width(), localCopy.height(), (QImage::Format)QIMAGE_HDR_FORMAT);
std::function<uint32(const glm::vec3&)> packFunc;
#ifdef DEBUG_COLOR_PACKING
std::function<glm::vec3(uint32)> unpackFunc;
@ -1165,13 +1211,13 @@ QImage convertToHDRFormat(QImage srcImage, gpu::Element format) {
default:
qCWarning(imagelogging) << "Unsupported HDR format";
Q_UNREACHABLE();
return srcImage;
return localCopy;
}
srcImage = srcImage.convertToFormat(QImage::Format_ARGB32);
for (auto y = 0; y < srcImage.height(); y++) {
const QRgb* srcLineIt = reinterpret_cast<const QRgb*>( srcImage.constScanLine(y) );
const QRgb* srcLineEnd = srcLineIt + srcImage.width();
localCopy = localCopy.convertToFormat(QImage::Format_ARGB32);
for (auto y = 0; y < localCopy.height(); y++) {
const QRgb* srcLineIt = reinterpret_cast<const QRgb*>( localCopy.constScanLine(y) );
const QRgb* srcLineEnd = srcLineIt + localCopy.width();
uint32* hdrLineIt = reinterpret_cast<uint32*>( hdrImage.scanLine(y) );
glm::vec3 color;
@ -1196,86 +1242,99 @@ QImage convertToHDRFormat(QImage srcImage, gpu::Element format) {
return hdrImage;
}
gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName,
gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName,
bool generateIrradiance,
const std::atomic<bool>& abortProcessing) {
PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage");
// Take a local copy to force move construction
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QImage localCopy = std::move(srcImage);
int originalWidth = localCopy.width();
int originalHeight = localCopy.height();
if ((originalWidth <= 0) && (originalHeight <= 0)) {
return nullptr;
}
gpu::TexturePointer theTexture = nullptr;
if ((srcImage.width() > 0) && (srcImage.height() > 0)) {
QImage image = processSourceImage(srcImage, true);
if (image.format() != QIMAGE_HDR_FORMAT) {
image = convertToHDRFormat(image, HDR_FORMAT);
QImage image = processSourceImage(std::move(localCopy), true);
if (image.format() != QIMAGE_HDR_FORMAT) {
image = convertToHDRFormat(std::move(image), HDR_FORMAT);
}
gpu::Element formatMip;
gpu::Element formatGPU;
if (isCubeTexturesCompressionEnabled()) {
formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB;
formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB;
} else {
formatMip = HDR_FORMAT;
formatGPU = HDR_FORMAT;
}
// Find the layout of the cubemap in the 2D image
// Use the original image size since processSourceImage may have altered the size / aspect ratio
int foundLayout = CubeLayout::findLayout(originalWidth, originalHeight);
if (foundLayout < 0) {
qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str());
return nullptr;
}
std::vector<QImage> faces;
// If found, go extract the faces as separate images
auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout];
if (layout._type == CubeLayout::FLAT) {
int faceWidth = image.width() / layout._widthRatio;
faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror));
} else if (layout._type == CubeLayout::EQUIRECTANGULAR) {
// THe face width is estimated from the input image
const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4;
const int EQUIRECT_MAX_FACE_WIDTH = 2048;
int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH);
for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) {
QImage faceImage = CubeLayout::extractEquirectangularFace(std::move(image), (gpu::Texture::CubeFace) face, faceWidth);
faces.push_back(std::move(faceImage));
}
}
gpu::Element formatMip;
gpu::Element formatGPU;
if (isCubeTexturesCompressionEnabled()) {
formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB;
formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB;
} else {
formatMip = HDR_FORMAT;
formatGPU = HDR_FORMAT;
}
// free up the memory afterward to avoid bloating the heap
image = QImage(); // QImage doesn't have a clear function, so override it with an empty one.
// Find the layout of the cubemap in the 2D image
// Use the original image size since processSourceImage may have altered the size / aspect ratio
int foundLayout = CubeLayout::findLayout(srcImage.width(), srcImage.height());
std::vector<QImage> faces;
// If found, go extract the faces as separate images
if (foundLayout >= 0) {
auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout];
if (layout._type == CubeLayout::FLAT) {
int faceWidth = image.width() / layout._widthRatio;
faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror));
faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror));
} else if (layout._type == CubeLayout::EQUIRECTANGULAR) {
// THe face width is estimated from the input image
const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4;
const int EQUIRECT_MAX_FACE_WIDTH = 2048;
int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH);
for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) {
QImage faceImage = CubeLayout::extractEquirectangularFace(image, (gpu::Texture::CubeFace) face, faceWidth);
faces.push_back(faceImage);
}
}
} else {
qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str());
return nullptr;
}
// If the 6 faces have been created go on and define the true Texture
if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) {
theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
// If the 6 faces have been created go on and define the true Texture
if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) {
theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
// Generate irradiance while we are at it
if (generateIrradiance) {
PROFILE_RANGE(resource_parse, "generateIrradiance");
auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
irradianceTexture->setSource(srcImageName);
irradianceTexture->setStoredMipFormat(HDR_FORMAT);
for (uint8 face = 0; face < faces.size(); ++face) {
generateMips(theTexture.get(), faces[face], abortProcessing, face);
irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits());
}
// Generate irradiance while we are at it
if (generateIrradiance) {
PROFILE_RANGE(resource_parse, "generateIrradiance");
auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
irradianceTexture->setSource(srcImageName);
irradianceTexture->setStoredMipFormat(HDR_FORMAT);
for (uint8 face = 0; face < faces.size(); ++face) {
irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits());
}
irradianceTexture->generateIrradiance();
irradianceTexture->generateIrradiance();
auto irradiance = irradianceTexture->getIrradiance();
theTexture->overrideIrradiance(irradiance);
}
auto irradiance = irradianceTexture->getIrradiance();
theTexture->overrideIrradiance(irradiance);
}
for (uint8 face = 0; face < faces.size(); ++face) {
generateMips(theTexture.get(), std::move(faces[face]), abortProcessing, face);
}
}

View file

@ -41,45 +41,47 @@ enum Type {
UNUSED_TEXTURE
};
using TextureLoader = std::function<gpu::TexturePointer(const QImage&, const std::string&, const std::atomic<bool>&)>;
using TextureLoader = std::function<gpu::TexturePointer(QImage&&, const std::string&, const std::atomic<bool>&)>;
TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap());
gpu::TexturePointer create2DTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createStrict2DTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createAlbedoTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createEmissiveTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createNormalTextureFromNormalImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createNormalTextureFromBumpImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createRoughnessTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createRoughnessTextureFromGlossImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createMetallicTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createCubeTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createLightmapTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isStrict,
gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool isStrict,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer process2DTextureNormalMapFromImage(const QImage& srcImage, const std::string& srcImageName, bool isBumpMap,
gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool isBumpMap,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer process2DTextureGrayscaleFromImage(const QImage& srcImage, const std::string& srcImageName, bool isInvertedPixels,
gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool isInvertedPixels,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool generateIrradiance,
gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool generateIrradiance,
const std::atomic<bool>& abortProcessing);
} // namespace TextureUsage
const QStringList getSupportedFormats();
bool isColorTexturesCompressionEnabled();
bool isNormalTexturesCompressionEnabled();
bool isGrayscaleTexturesCompressionEnabled();
@ -90,7 +92,7 @@ void setNormalTexturesCompressionEnabled(bool enabled);
void setGrayscaleTexturesCompressionEnabled(bool enabled);
void setCubeTexturesCompressionEnabled(bool enabled);
gpu::TexturePointer processImage(const QByteArray& content, const std::string& url,
gpu::TexturePointer processImage(QByteArray&& content, const std::string& url,
int maxNumPixels, TextureUsage::Type textureType,
const std::atomic<bool>& abortProcessing = false);

View file

@ -262,7 +262,7 @@ gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) {
gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type, QVariantMap options) {
QImage image = QImage(path);
auto loader = image::TextureUsage::getTextureLoaderForType(type, options);
return gpu::TexturePointer(loader(image, QUrl::fromLocalFile(path).fileName().toStdString(), false));
return gpu::TexturePointer(loader(std::move(image), QUrl::fromLocalFile(path).fileName().toStdString(), false));
}
QSharedPointer<Resource> TextureCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
@ -952,7 +952,9 @@ void ImageReader::read() {
gpu::TexturePointer texture;
{
PROFILE_RANGE_EX(resource_parse_image_raw, __FUNCTION__, 0xffff0000, 0);
texture = image::processImage(_content, _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType());
// IMPORTANT: _content is empty past this point
texture = image::processImage(std::move(_content), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType());
if (!texture) {
qCWarning(modelnetworking) << "Could not process:" << _url;

View file

@ -19,8 +19,14 @@
#include <QDir>
#include <QUrl>
#include <QtCore/QStandardPaths>
#include <QRegularExpression>
#include <mutex> // std::once
#include "shared/GlobalAppProperties.h"
#include "SharedUtil.h"
// Format: AppName-PID-Timestamp
// Example: ...
QString TEMP_DIR_FORMAT { "%1-%2-%3" };
const QString& PathUtils::resourcesPath() {
#ifdef Q_OS_MAC
@ -81,7 +87,8 @@ QString PathUtils::generateTemporaryDir() {
QString appName = qApp->applicationName();
for (auto i = 0; i < 64; ++i) {
auto now = std::chrono::system_clock::now().time_since_epoch().count();
QDir tempDir = rootTempDir.filePath(appName + "-" + QString::number(now));
auto dirName = TEMP_DIR_FORMAT.arg(appName).arg(qApp->applicationPid()).arg(now);
QDir tempDir = rootTempDir.filePath(dirName);
if (tempDir.mkpath(".")) {
return tempDir.absolutePath();
}
@ -89,6 +96,39 @@ QString PathUtils::generateTemporaryDir() {
return "";
}
// Delete all temporary directories for an application
int PathUtils::removeTemporaryApplicationDirs(QString appName) {
if (appName.isNull()) {
appName = qApp->applicationName();
}
auto dirName = TEMP_DIR_FORMAT.arg(appName).arg("*").arg("*");
QDir rootTempDir = QDir::tempPath();
auto dirs = rootTempDir.entryInfoList({ dirName }, QDir::Dirs);
int removed = 0;
for (auto& dir : dirs) {
auto dirName = dir.fileName();
auto absoluteDirPath = QDir(dir.absoluteFilePath());
QRegularExpression re { "^" + QRegularExpression::escape(appName) + "\\-(?<pid>\\d+)\\-(?<timestamp>\\d+)$" };
auto match = re.match(dirName);
if (match.hasMatch()) {
auto pid = match.capturedRef("pid").toLongLong();
auto timestamp = match.capturedRef("timestamp");
if (!processIsRunning(pid)) {
qDebug() << " Removing old temporary directory: " << dir.absoluteFilePath();
absoluteDirPath.removeRecursively();
removed++;
} else {
qDebug() << " Not removing (process is running): " << dir.absoluteFilePath();
}
}
}
return removed;
}
QString fileNameWithoutExtension(const QString& fileName, const QVector<QString> possibleExtensions) {
QString fileNameLowered = fileName.toLower();
foreach (const QString possibleExtension, possibleExtensions) {

View file

@ -43,6 +43,8 @@ public:
static QString generateTemporaryDir();
static int removeTemporaryApplicationDirs(QString appName = QString::null);
static Qt::CaseSensitivity getFSCaseSensitivity();
static QString stripFilename(const QUrl& url);
// note: this is FS-case-sensitive version of parentURL.isParentOf(childURL)

View file

@ -44,6 +44,12 @@ extern "C" FILE * __cdecl __iob_func(void) {
#include <CoreFoundation/CoreFoundation.h>
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
#include <signal.h>
#include <cerrno>
#endif
#include <QtCore/QDebug>
#include <QDateTime>
#include <QElapsedTimer>
@ -1078,6 +1084,24 @@ void setMaxCores(uint8_t maxCores) {
#endif
}
bool processIsRunning(int64_t pid) {
#ifdef Q_OS_WIN
HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (process) {
DWORD exitCode;
if (GetExitCodeProcess(process, &exitCode) != 0) {
return exitCode == STILL_ACTIVE;
}
}
return false;
#else
if (kill(pid, 0) == -1) {
return errno != ESRCH;
}
return true;
#endif
}
void quitWithParentProcess() {
if (qApp) {
qDebug() << "Parent process died, quitting";

View file

@ -238,6 +238,8 @@ void setMaxCores(uint8_t maxCores);
const QString PARENT_PID_OPTION = "parent-pid";
void watchParentProcess(int parentPID);
bool processIsRunning(int64_t pid);
#ifdef Q_OS_WIN
void* createProcessGroup();

View file

@ -1190,6 +1190,7 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT
touchEvent.setTarget(_rootItem);
touchEvent.setTouchPoints(touchPoints);
touchEvent.setTouchPointStates(touchPointStates);
touchEvent.setTimestamp((ulong)QDateTime::currentMSecsSinceEpoch());
touchEvent.ignore();
}

View file

@ -376,6 +376,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
if (PROFILE) {
Script.endProfileRange("dispatch.run");
}
Script.setTimeout(_this.update, BASIC_TIMER_INTERVAL_MS);
};
this.setBlacklist = function() {
@ -470,7 +471,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
};
this.cleanup = function () {
Script.update.disconnect(_this.update);
Controller.disableMapping(MAPPING_NAME);
_this.pointerManager.removePointers();
Pointers.removePointer(this.mouseRayPick);
@ -501,5 +501,5 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
Messages.subscribe('Hifi-Hand-RayPick-Blacklist');
Messages.messageReceived.connect(controllerDispatcher.handleHandMessage);
Script.scriptEnding.connect(controllerDispatcher.cleanup);
Script.update.connect(controllerDispatcher.update);
Script.setTimeout(controllerDispatcher.update, BASIC_TIMER_INTERVAL_MS);
}());

View file

@ -242,7 +242,9 @@ GridTool = function(opts) {
horizontalGrid.addListener(function(data) {
webView.emitScriptEvent(JSON.stringify(data));
selectionDisplay.updateHandles();
if (selectionDisplay) {
selectionDisplay.updateHandles();
}
});
webView.webEventReceived.connect(function(data) {

View file

@ -11,9 +11,12 @@
/* global Tablet, Script, HMD, UserActivityLogger, Entities */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
var selectionDisplay = null; // for gridTool.js to ignore
(function () { // BEGIN LOCAL_SCOPE
Script.include("../libraries/WebTablet.js");
Script.include("/~/system/libraries/WebTablet.js");
Script.include("/~/system/libraries/gridTool.js");
var METAVERSE_SERVER_URL = Account.metaverseServerURL;
var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace";
@ -169,6 +172,33 @@
}));
}
var grid = new Grid();
function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) {
// Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original
// position in the given direction.
var CORNERS = [
{ x: 0, y: 0, z: 0 },
{ x: 0, y: 0, z: 1 },
{ x: 0, y: 1, z: 0 },
{ x: 0, y: 1, z: 1 },
{ x: 1, y: 0, z: 0 },
{ x: 1, y: 0, z: 1 },
{ x: 1, y: 1, z: 0 },
{ x: 1, y: 1, z: 1 },
];
// Go through all corners and find least (most negative) distance in front of position.
var distance = 0;
for (var i = 0, length = CORNERS.length; i < length; i++) {
var cornerVector =
Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions));
var cornerDistance = Vec3.dot(cornerVector, direction);
distance = Math.min(cornerDistance, distance);
}
position = Vec3.sum(Vec3.multiply(distance, direction), position);
return position;
}
var HALF_TREE_SCALE = 16384;
function getPositionToCreateEntity(extra) {
var CREATE_DISTANCE = 2;
@ -257,10 +287,6 @@
}
}
}
if (isActive) {
selectionManager.setSelections(pastedEntityIDs);
}
} else {
Window.notifyEditError("Can't import entities: entities would be out of bounds.");
}

2250
server-console/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,6 @@
""
],
"devDependencies": {
"electron-compilers": "^1.0.1",
"electron-packager": "^6.0.2",
"electron-prebuilt": "0.37.5"
},

View file

@ -23,4 +23,8 @@ if (BUILD_TOOLS)
add_subdirectory(oven)
set_target_properties(oven PROPERTIES FOLDER "Tools")
add_subdirectory(auto-tester)
set_target_properties(auto-tester PROPERTIES FOLDER "Tools")
endif()

View file

@ -0,0 +1,57 @@
set(TARGET_NAME auto-tester)
project(${TARGET_NAME})
# Automatically run UIC and MOC. This replaces the older WRAP macros
SET(CMAKE_AUTOUIC ON)
SET(CMAKE_AUTOMOC ON)
setup_hifi_project(Core Widgets)
link_hifi_libraries()
# FIX: Qt was built with -reduce-relocations
if (Qt5_POSITION_INDEPENDENT_CODE)
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()
# Qt includes
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${Qt5Core_INCLUDE_DIRS})
include_directories(${Qt5Widgets_INCLUDE_DIRS})
set(QT_LIBRARIES Qt5::Core Qt5::Widgets)
# Find all sources files
file (GLOB_RECURSE SOURCES src/*.cpp)
file (GLOB_RECURSE HEADERS src/*.h)
file (GLOB_RECURSE UIS src/ui/*.ui)
if (WIN32)
# Do not show Console
set_property(TARGET auto-tester PROPERTY WIN32_EXECUTABLE true)
endif()
add_executable(PROJECT_NAME ${SOURCES} ${HEADERS} ${UIS})
target_link_libraries(PROJECT_NAME ${QT_LIBRARIES})
# Copy required dll's.
add_custom_command(TARGET auto-tester POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Core> $<TARGET_FILE_DIR:auto-tester>
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Gui> $<TARGET_FILE_DIR:auto-tester>
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Widgets> $<TARGET_FILE_DIR:auto-tester>
)
if (WIN32)
find_program(WINDEPLOYQT_COMMAND windeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH)
if (NOT WINDEPLOYQT_COMMAND)
message(FATAL_ERROR "Could not find windeployqt at ${QT_DIR}/bin. windeployqt is required.")
endif ()
# add a post-build command to call windeployqt to copy Qt plugins
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> \"$<TARGET_FILE:${TARGET_NAME}>\""
)
endif ()

View file

@ -0,0 +1,7 @@
After building auto-tester, it needs to be deployed to Amazon SW
* In folder hifi/build/tools/auto-tester
* Right click on the Release folder
* Select 7-Zip -> Add to archive
* Select Option ```Create SFX archive``` to create Release.exe
* Use Cyberduck (or any other AWS S3 client) to copy Release.exe to hifi-content/nissim/autoTester/

View file

@ -0,0 +1,119 @@
//
// ImageComparer.cpp
//
// Created by Nissim Hadar on 18 Nov 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ImageComparer.h"
#include <cmath>
// Computes SSIM - see https://en.wikipedia.org/wiki/Structural_similarity
// The value is computed for the luminance component and the average value is returned
double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) const {
// Make sure the image is 8 bits per colour
QImage::Format format = expectedImage.format();
if (format != QImage::Format::Format_RGB32) {
throw -1;
}
const int L = 255; // (2^number of bits per pixel) - 1
const double K1{ 0.01 };
const double K2{ 0.03 };
const double c1 = pow((K1 * L), 2);
const double c2 = pow((K2 * L), 2);
// Coefficients for luminosity calculation
const double R_Y = 0.212655f;
const double G_Y = 0.715158f;
const double B_Y = 0.072187f;
// First go over all full 8x8 blocks
// This is done in 3 loops
// 1) Read the pixels into a linear array (an optimization)
// 2) Calculate mean
// 3) Calculate variance and covariance
//
// p - pixel in expected image
// q - pixel in result image
//
const int WIN_SIZE = 8;
int x{ 0 }; // column index (start of block)
int y{ 0 }; // row index (start of block
// Pixels are processed in square blocks
double p[WIN_SIZE * WIN_SIZE];
double q[WIN_SIZE * WIN_SIZE];
int windowCounter{ 0 };
double ssim{ 0.0 };
while (x < expectedImage.width()) {
int lastX = x + WIN_SIZE - 1;
if (lastX > expectedImage.width() - 1) {
x -= (lastX - expectedImage.width());
}
while (y < expectedImage.height()) {
int lastY = y + WIN_SIZE - 1;
if (lastY > expectedImage.height() - 1) {
y -= (lastY - expectedImage.height());
}
// Collect pixels into linear arrays
int i{ 0 };
for (int xx = 0; xx < WIN_SIZE; ++xx) {
for (int yy = 0; yy < WIN_SIZE; ++yy) {
// Get pixels
QRgb pixelP = expectedImage.pixel(QPoint(x + xx, y + yy));
QRgb pixelQ = resultImage.pixel(QPoint(x + xx, y + yy));
// Convert to luminance
p[i] = R_Y * qRed(pixelP) + G_Y * qGreen(pixelP) + B_Y * qBlue(pixelP);
q[i] = R_Y * qRed(pixelQ) + G_Y * qGreen(pixelQ) + B_Y * qBlue(pixelQ);
++i;
}
}
// Calculate mean
double mP{ 0.0 }; // average value of expected pixel
double mQ{ 0.0 }; // average value of result pixel
for (int j = 0; j < WIN_SIZE * WIN_SIZE; ++j) {
mP += p[j];
mQ += q[j];
}
mP /= (WIN_SIZE * WIN_SIZE);
mQ /= (WIN_SIZE * WIN_SIZE);
// Calculate variance and covariance
double sigsqP{ 0.0 };
double sigsqQ{ 0.0 };
double sigPQ{ 0.0 };
for (int j = 0; j < WIN_SIZE * WIN_SIZE; ++j) {
sigsqP += pow((p[j] - mP), 2);
sigsqQ += pow((q[j] - mQ), 2);
sigPQ += (p[j] - mP) * (q[j] - mQ);
}
sigsqP /= (WIN_SIZE * WIN_SIZE);
sigsqQ /= (WIN_SIZE * WIN_SIZE);
sigPQ /= (WIN_SIZE * WIN_SIZE);
double numerator = (2.0 * mP * mQ + c1) * (2.0 * sigPQ + c2);
double denominator = (mP * mP + mQ * mQ + c1) * (sigsqP + sigsqQ + c2);
ssim += numerator / denominator;
++windowCounter;
y += WIN_SIZE;
}
x += WIN_SIZE;
y = 0;
}
return ssim / windowCounter;
};

View file

@ -0,0 +1,21 @@
//
// ImageComparer.h
//
// Created by Nissim Hadar on 18 Nov 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ImageComparer_h
#define hifi_ImageComparer_h
#include <QtCore/QString>
#include <QImage>
class ImageComparer {
public:
double compareImages(QImage resultImage, QImage expectedImage) const;
};
#endif // hifi_ImageComparer_h

View file

@ -0,0 +1,383 @@
//
// Test.cpp
//
// Created by Nissim Hadar on 2 Nov 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Test.h"
#include <assert.h>
#include <QtCore/QTextStream>
#include <QDirIterator>
Test::Test() {
snapshotFilenameFormat = QRegularExpression("hifi-snap-by-.+-on-\\d\\d\\d\\d-\\d\\d-\\d\\d_\\d\\d-\\d\\d-\\d\\d.jpg");
expectedImageFilenameFormat = QRegularExpression("ExpectedImage_\\d+.jpg");
mismatchWindow.setModal(true);
}
bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages) {
// Loop over both lists and compare each pair of images
// Quit loop if user has aborted due to a failed test.
const double THRESHOLD{ 0.999 };
bool success{ true };
bool keepOn{ true };
for (int i = 0; keepOn && i < expectedImages.length(); ++i) {
// First check that images are the same size
QImage resultImage(resultImages[i]);
QImage expectedImage(expectedImages[i]);
if (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height()) {
messageBox.critical(0, "Internal error", "Images are not the same size");
exit(-1);
}
double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical
try {
similarityIndex = imageComparer.compareImages(resultImage, expectedImage);
} catch (...) {
messageBox.critical(0, "Internal error", "Image not in expected format");
exit(-1);
}
if (similarityIndex < THRESHOLD) {
mismatchWindow.setTestFailure(TestFailure{
(float)similarityIndex,
expectedImages[i].left(expectedImages[i].lastIndexOf("/") + 1), // path to the test (including trailing /)
QFileInfo(expectedImages[i].toStdString().c_str()).fileName(), // filename of expected image
QFileInfo(resultImages[i].toStdString().c_str()).fileName() // filename of result image
});
mismatchWindow.exec();
switch (mismatchWindow.getUserResponse()) {
case USER_RESPONSE_PASS:
break;
case USE_RESPONSE_FAIL:
success = false;
break;
case USER_RESPONSE_ABORT:
keepOn = false;
success = false;
break;
default:
assert(false);
break;
}
}
}
return success;
}
void Test::evaluateTests() {
// Get list of JPEG images in folder, sorted by name
QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly);
if (pathToImageDirectory == "") {
return;
}
QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory);
// Separate images into two lists. The first is the expected images, the second is the test results
// Images that are in the wrong format are ignored.
QStringList expectedImages;
QStringList resultImages;
foreach(QString currentFilename, sortedImageFilenames) {
QString fullCurrentFilename = pathToImageDirectory + "/" + currentFilename;
if (isInExpectedImageFilenameFormat(currentFilename)) {
expectedImages << fullCurrentFilename;
} else if (isInSnapshotFilenameFormat(currentFilename)) {
resultImages << fullCurrentFilename;
}
}
// The number of images in each list should be identical
if (expectedImages.length() != resultImages.length()) {
messageBox.critical(0,
"Test failed",
"Found " + QString::number(resultImages.length()) + " images in directory" +
"\nExpected to find " + QString::number(expectedImages.length()) + " images"
);
exit(-1);
}
bool success = compareImageLists(expectedImages, resultImages);
if (success) {
messageBox.information(0, "Success", "All images are as expected");
} else {
messageBox.information(0, "Failure", "One or more images are not as expected");
}
}
// Two criteria are used to decide if a folder contains valid test results.
// 1) a 'test'js' file exists in the folder
// 2) the folder has the same number of actual and expected images
void Test::evaluateTestsRecursively() {
// Select folder to start recursing from
QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly);
if (topLevelDirectory == "") {
return;
}
bool success{ true };
QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
if (directory[directory.length() - 1] == '.') {
// ignore '.', '..' directories
continue;
}
//
const QString testPathname{ directory + "/" + testFilename };
QFileInfo fileInfo(testPathname);
if (!fileInfo.exists()) {
// Folder does not contain 'test.js'
continue;
}
QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(directory);
// Separate images into two lists. The first is the expected images, the second is the test results
// Images that are in the wrong format are ignored.
QStringList expectedImages;
QStringList resultImages;
foreach(QString currentFilename, sortedImageFilenames) {
QString fullCurrentFilename = directory + "/" + currentFilename;
if (isInExpectedImageFilenameFormat(currentFilename)) {
expectedImages << fullCurrentFilename;
} else if (isInSnapshotFilenameFormat(currentFilename)) {
resultImages << fullCurrentFilename;
}
}
if (expectedImages.length() != resultImages.length()) {
// Number of images doesn't match
continue;
}
// Set success to false if any test has failed
success &= compareImageLists(expectedImages, resultImages);
}
if (success) {
messageBox.information(0, "Success", "All images are as expected");
} else {
messageBox.information(0, "Failure", "One or more images are not as expected");
}
}
void Test::importTest(QTextStream& textStream, const QString& testPathname, int testNumber) {
textStream << "var test" << testNumber << " = Script.require(\"" << "file:///" << testPathname + "\");" << endl;
}
// Creates a single script in a user-selected folder.
// This script will run all text.js scripts in every applicable sub-folder
void Test::createRecursiveScript() {
// Select folder to start recursing from
QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly);
if (topLevelDirectory == "") {
return;
}
QFile allTestsFilename(topLevelDirectory + "/" + "allTests.js");
if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) {
messageBox.critical(0,
"Internal Error",
"Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\"");
exit(-1);
}
QTextStream textStream(&allTestsFilename);
textStream << "// This is an automatically generated file, created by auto-tester" << endl;
// The main will call each test after the previous test is completed
// This is implemented with an interval timer that periodically tests if a
// running test has increment a testNumber variable that it received as an input.
int testNumber = 1;
QVector<QString> testPathnames;
// First test if top-level folder has a test.js file
const QString testPathname{ topLevelDirectory + "/" + testFilename };
QFileInfo fileInfo(testPathname);
if (fileInfo.exists()) {
// Current folder contains a test
importTest(textStream, testPathname, testNumber);
++testNumber;
testPathnames << testPathname;
}
QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
if (directory[directory.length() - 1] == '.') {
// ignore '.', '..' directories
continue;
}
const QString testPathname{ directory + "/" + testFilename };
QFileInfo fileInfo(testPathname);
if (fileInfo.exists()) {
// Current folder contains a test
importTest(textStream, testPathname, testNumber);
++testNumber;
testPathnames << testPathname;
}
}
if (testPathnames.length() <= 0) {
messageBox.information(0, "Failure", "No \"test.js\" files found");
allTestsFilename.close();
return;
}
textStream << endl;
// Define flags for each test
for (int i = 1; i <= testPathnames.length(); ++i) {
textStream << "var test" << i << "HasNotStarted = true;" << endl;
}
// Leave a blank line in the main
textStream << endl;
const int TEST_PERIOD = 1000; // in milliseconds
const QString tab = " ";
textStream << "// Check every second if the current test is complete and the next test can be run" << endl;
textStream << "var testTimer = Script.setInterval(" << endl;
textStream << tab << "function() {" << endl;
const QString testFunction = "test";
for (int i = 1; i <= testPathnames.length(); ++i) {
// First test starts immediately, all other tests wait for the previous test to complete.
// The script produced will look as follows:
// if (test1HasNotStarted) {
// test1HasNotStarted = false;
// test1.test();
// print("******started test 1******");
// }
// |
// |
// if (test5.complete && test6HasNotStarted) {
// test6HasNotStarted = false;
// test7.test();
// print("******started test 6******");
// }
// |
// |
// if (test12.complete) {
// print("******stopping******");
// Script.stop();
// }
//
if (i == 1) {
textStream << tab << tab << "if (test1HasNotStarted) {" << endl;
} else {
textStream << tab << tab << "if (test" << i - 1 << ".complete && test" << i << "HasNotStarted) {" << endl;
}
textStream << tab << tab << tab << "test" << i << "HasNotStarted = false;" << endl;
textStream << tab << tab << tab << "test" << i << "." << testFunction << "();" << endl;
textStream << tab << tab << tab << "print(\"******started test " << i << "******\");" << endl;
textStream << tab << tab << "}" << endl << endl;
}
// Add extra step to stop the script
textStream << tab << tab << "if (test" << testPathnames.length() << ".complete) {" << endl;
textStream << tab << tab << tab << "print(\"******stopping******\");" << endl;
textStream << tab << tab << tab << "Script.stop();" << endl;
textStream << tab << tab << "}" << endl << endl;
textStream << tab << "}," << endl;
textStream << endl;
textStream << tab << TEST_PERIOD << endl;
textStream << ");" << endl << endl;
textStream << "// Stop the timer and clear the module cache" << endl;
textStream << "Script.scriptEnding.connect(" << endl;
textStream << tab << "function() {" << endl;
textStream << tab << tab << "Script.clearInterval(testTimer);" << endl;
textStream << tab << tab << "Script.require.cache = {};" << endl;
textStream << tab << "}" << endl;
textStream << ");" << endl;
allTestsFilename.close();
messageBox.information(0, "Success", "Script has been created");
}
void Test::createTest() {
// Rename files sequentially, as ExpectedResult_1.jpeg, ExpectedResult_2.jpg and so on
// Any existing expected result images will be deleted
QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly);
if (pathToImageDirectory == "") {
return;
}
QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory);
int i = 1;
foreach (QString currentFilename, sortedImageFilenames) {
QString fullCurrentFilename = pathToImageDirectory + "/" + currentFilename;
if (isInExpectedImageFilenameFormat(currentFilename)) {
if (!QFile::remove(fullCurrentFilename)) {
messageBox.critical(0, "Error", "Could not delete existing file: " + currentFilename + "\nTest creation aborted");
exit(-1);
}
} else if (isInSnapshotFilenameFormat(currentFilename)) {
const int MAX_IMAGES = 100000;
if (i >= MAX_IMAGES) {
messageBox.critical(0, "Error", "More than 100,000 images not supported");
exit(-1);
}
QString newFilename = "ExpectedImage_" + QString::number(i-1).rightJustified(5, '0') + ".jpg";
QString fullNewFileName = pathToImageDirectory + "/" + newFilename;
if (!imageDirectory.rename(fullCurrentFilename, newFilename)) {
if (!QFile::exists(fullCurrentFilename)) {
messageBox.critical(0, "Error", "Could not rename file: " + fullCurrentFilename + " to: " + newFilename + "\n"
+ fullCurrentFilename + " not found"
+ "\nTest creation aborted"
);
exit(-1);
} else {
messageBox.critical(0, "Error", "Could not rename file: " + fullCurrentFilename + " to: " + newFilename + "\n"
+ "unknown error" + "\nTest creation aborted"
);
exit(-1);
}
}
++i;
}
}
messageBox.information(0, "Success", "Test images have been created");
}
QStringList Test::createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory) {
imageDirectory = QDir(pathToImageDirectory);
QStringList nameFilters;
nameFilters << "*.jpg";
return imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name);
}
bool Test::isInSnapshotFilenameFormat(QString filename) {
return (snapshotFilenameFormat.match(filename).hasMatch());
}
bool Test::isInExpectedImageFilenameFormat(QString filename) {
return (expectedImageFilenameFormat.match(filename).hasMatch());
}

View file

@ -0,0 +1,55 @@
//
// Test.h
// zone/ambientLightInheritence
//
// Created by Nissim Hadar on 2 Nov 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_test_h
#define hifi_test_h
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include <QtCore/QRegularExpression>
#include "ImageComparer.h"
#include "ui/MismatchWindow.h"
class Test {
public:
Test();
void evaluateTests();
void evaluateTestsRecursively();
void createRecursiveScript();
void createTest();
QStringList createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory);
bool isInSnapshotFilenameFormat(QString filename);
bool isInExpectedImageFilenameFormat(QString filename);
void importTest(QTextStream& textStream, const QString& testPathname, int testNumber);
private:
const QString testFilename{ "test.js" };
QMessageBox messageBox;
QDir imageDirectory;
QRegularExpression snapshotFilenameFormat;
QRegularExpression expectedImageFilenameFormat;
MismatchWindow mismatchWindow;
ImageComparer imageComparer;
bool compareImageLists(QStringList expectedImages, QStringList resultImages);
};
#endif // hifi_test_h

View file

@ -0,0 +1,37 @@
//
// common.h
//
// Created by Nissim Hadar on 10 Nov 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_common_h
#define hifi_common_h
#include <QtCore/QString>
class TestFailure {
public:
TestFailure(float error, QString pathname, QString expectedImageFilename, QString actualImageFilename) :
_error(error),
_pathname(pathname),
_expectedImageFilename(expectedImageFilename),
_actualImageFilename(actualImageFilename)
{}
double _error;
QString _pathname;
QString _expectedImageFilename;
QString _actualImageFilename;
};
enum UserResponse {
USER_RESPONSE_INVALID,
USER_RESPONSE_PASS,
USE_RESPONSE_FAIL,
USER_RESPONSE_ABORT
};
#endif // hifi_common_h

View file

@ -0,0 +1,20 @@
//
// main.cpp
//
// Created by Nissim Hadar on 2 Nov 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtWidgets/QApplication>
#include "ui/AutoTester.h"
int main(int argc, char *argv[]) {
QApplication application(argc, argv);
AutoTester autoTester;
autoTester.show();
return application.exec();
}

View file

@ -0,0 +1,35 @@
//
// AutoTester.cpp
// zone/ambientLightInheritence
//
// Created by Nissim Hadar on 2 Nov 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AutoTester.h"
AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) {
ui.setupUi(this);
}
void AutoTester::on_evaluateTestsButton_clicked() {
test.evaluateTests();
}
void AutoTester::on_evaluateTestsRecursivelyButton_clicked() {
test.evaluateTestsRecursively();
}
void AutoTester::on_createRecursiveScriptButton_clicked() {
test.createRecursiveScript();
}
void AutoTester::on_createTestButton_clicked() {
test.createTest();
}
void AutoTester::on_closeButton_clicked() {
exit(0);
}

View file

@ -0,0 +1,37 @@
//
// AutoTester.h
// zone/ambientLightInheritence
//
// Created by Nissim Hadar on 2 Nov 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AutoTester_h
#define hifi_AutoTester_h
#include <QtWidgets/QMainWindow>
#include "ui_AutoTester.h"
#include "../Test.h"
class AutoTester : public QMainWindow {
Q_OBJECT
public:
AutoTester(QWidget *parent = Q_NULLPTR);
private slots:
void on_evaluateTestsButton_clicked();
void on_evaluateTestsRecursivelyButton_clicked();
void on_createRecursiveScriptButton_clicked();
void on_createTestButton_clicked();
void on_closeButton_clicked();
private:
Ui::AutoTesterClass ui;
Test test;
};
#endif // hifi_AutoTester_h

View file

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AutoTesterClass</class>
<widget class="QMainWindow" name="AutoTesterClass">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>286</width>
<height>470</height>
</rect>
</property>
<property name="windowTitle">
<string>AutoTester</string>
</property>
<widget class="QWidget" name="centralWidget">
<widget class="QPushButton" name="closeButton">
<property name="geometry">
<rect>
<x>60</x>
<y>360</y>
<width>160</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Close</string>
</property>
</widget>
<widget class="QPushButton" name="createTestButton">
<property name="geometry">
<rect>
<x>60</x>
<y>270</y>
<width>160</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Create Test</string>
</property>
</widget>
<widget class="QPushButton" name="evaluateTestsButton">
<property name="geometry">
<rect>
<x>60</x>
<y>20</y>
<width>160</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Evaluate Test</string>
</property>
</widget>
<widget class="QPushButton" name="createRecursiveScriptButton">
<property name="geometry">
<rect>
<x>60</x>
<y>210</y>
<width>160</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Create Recursive Script</string>
</property>
</widget>
<widget class="QPushButton" name="evaluateTestsRecursivelyButton">
<property name="geometry">
<rect>
<x>60</x>
<y>75</y>
<width>160</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Evaluate Tests Recursively</string>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>286</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,46 @@
//
// MismatchWindow.cpp
//
// Created by Nissim Hadar on 9 Nov 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "MismatchWindow.h"
#include <QtCore/QFileInfo>
MismatchWindow::MismatchWindow(QWidget *parent) : QDialog(parent) {
setupUi(this);
expectedImage->setScaledContents(true);
resultImage->setScaledContents(true);
}
void MismatchWindow::setTestFailure(TestFailure testFailure) {
errorLabel->setText("Similarity: " + QString::number(testFailure._error));
imagePath->setText("Path to test: " + testFailure._pathname);
expectedFilename->setText(testFailure._expectedImageFilename);
expectedImage->setPixmap(QPixmap(testFailure._pathname + testFailure._expectedImageFilename));
resultFilename->setText(testFailure._actualImageFilename);
resultImage->setPixmap(QPixmap(testFailure._pathname + testFailure._actualImageFilename));
}
void MismatchWindow::on_passTestButton_clicked() {
_userResponse = USER_RESPONSE_PASS;
close();
}
void MismatchWindow::on_failTestButton_clicked() {
_userResponse = USE_RESPONSE_FAIL;
close();
}
void MismatchWindow::on_abortTestsButton_clicked() {
_userResponse = USER_RESPONSE_ABORT;
close();
}

View file

@ -0,0 +1,38 @@
//
// MismatchWindow.h
//
// Created by Nissim Hadar on 9 Nov 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_MismatchWindow_h
#define hifi_MismatchWindow_h
#include "ui_MismatchWindow.h"
#include "../common.h"
class MismatchWindow : public QDialog, public Ui::MismatchWindow
{
Q_OBJECT
public:
MismatchWindow(QWidget *parent = Q_NULLPTR);
void setTestFailure(TestFailure testFailure);
UserResponse getUserResponse() { return _userResponse; }
private slots:
void on_passTestButton_clicked();
void on_failTestButton_clicked();
void on_abortTestsButton_clicked();
private:
UserResponse _userResponse{ USER_RESPONSE_INVALID };
};
#endif // hifi_MismatchWindow_h

View file

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MismatchWindow</class>
<widget class="QDialog" name="MismatchWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1585</width>
<height>694</height>
</rect>
</property>
<property name="windowTitle">
<string>MismatchWindow</string>
</property>
<widget class="QLabel" name="expectedImage">
<property name="geometry">
<rect>
<x>20</x>
<y>170</y>
<width>720</width>
<height>362</height>
</rect>
</property>
<property name="text">
<string>expected image</string>
</property>
</widget>
<widget class="QLabel" name="resultImage">
<property name="geometry">
<rect>
<x>760</x>
<y>170</y>
<width>720</width>
<height>362</height>
</rect>
</property>
<property name="text">
<string>result image</string>
</property>
</widget>
<widget class="QLabel" name="resultFilename">
<property name="geometry">
<rect>
<x>760</x>
<y>90</y>
<width>800</width>
<height>28</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>result image filename</string>
</property>
</widget>
<widget class="QLabel" name="expectedFilename">
<property name="geometry">
<rect>
<x>40</x>
<y>90</y>
<width>700</width>
<height>28</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>expected image filename</string>
</property>
</widget>
<widget class="QLabel" name="imagePath">
<property name="geometry">
<rect>
<x>40</x>
<y>30</y>
<width>1200</width>
<height>28</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>image path</string>
</property>
</widget>
<widget class="QPushButton" name="passTestButton">
<property name="geometry">
<rect>
<x>30</x>
<y>600</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Pass</string>
</property>
</widget>
<widget class="QPushButton" name="failTestButton">
<property name="geometry">
<rect>
<x>330</x>
<y>600</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Fail</string>
</property>
</widget>
<widget class="QPushButton" name="abortTestsButton">
<property name="geometry">
<rect>
<x>630</x>
<y>600</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Abort Tests</string>
</property>
</widget>
<widget class="QLabel" name="errorLabel">
<property name="geometry">
<rect>
<x>810</x>
<y>600</y>
<width>720</width>
<height>28</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>similarity</string>
</property>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

View file

@ -17,4 +17,4 @@ if (UNIX)
endif()
endif ()
set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE)
install_beside_console()

View file

@ -12,6 +12,7 @@
#include <QObject>
#include <QImageReader>
#include <QtCore/QDebug>
#include <QFile>
#include "ModelBakingLoggingCategory.h"
#include "Oven.h"
@ -22,22 +23,30 @@
BakerCLI::BakerCLI(Oven* parent) : QObject(parent) {
}
void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) {
void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& type) {
// if the URL doesn't have a scheme, assume it is a local file
if (inputUrl.scheme() != "http" && inputUrl.scheme() != "https" && inputUrl.scheme() != "ftp") {
inputUrl.setScheme("file");
}
static const QString MODEL_EXTENSION { ".fbx" };
qDebug() << "Baking file type: " << type;
static const QString MODEL_EXTENSION { "fbx" };
QString extension = type;
if (extension.isNull()) {
auto url = inputUrl.toDisplayString();
extension = url.mid(url.lastIndexOf('.'));
}
// check what kind of baker we should be creating
bool isFBX = inputUrl.toDisplayString().endsWith(MODEL_EXTENSION, Qt::CaseInsensitive);
bool isSupportedImage = false;
bool isFBX = extension == MODEL_EXTENSION;
for (QByteArray format : QImageReader::supportedImageFormats()) {
isSupportedImage |= inputUrl.toDisplayString().endsWith(format, Qt::CaseInsensitive);
}
bool isSupportedImage = QImageReader::supportedImageFormats().contains(extension.toLatin1());
_outputPath = outputPath;
// create our appropiate baker
if (isFBX) {
@ -48,7 +57,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) {
_baker->moveToThread(qApp->getNextWorkerThread());
} else {
qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl;
QApplication::exit(1);
QApplication::exit(OVEN_STATUS_CODE_FAIL);
}
// invoke the bake method on the baker thread
@ -60,5 +69,17 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) {
void BakerCLI::handleFinishedBaker() {
qCDebug(model_baking) << "Finished baking file.";
QApplication::exit(_baker.get()->hasErrors());
int exitCode = OVEN_STATUS_CODE_SUCCESS;
// Do we need this?
if (_baker->wasAborted()) {
exitCode = OVEN_STATUS_CODE_ABORT;
} else if (_baker->hasErrors()) {
exitCode = OVEN_STATUS_CODE_FAIL;
QFile errorFile { _outputPath.absoluteFilePath(OVEN_ERROR_FILENAME) };
if (errorFile.open(QFile::WriteOnly)) {
errorFile.write(_baker->getErrors().join('\n').toUtf8());
errorFile.close();
}
}
QApplication::exit(exitCode);
}

View file

@ -13,22 +13,32 @@
#define hifi_BakerCLI_h
#include <QtCore/QObject>
#include <QDir>
#include <memory>
#include "Baker.h"
#include "Oven.h"
static const int OVEN_STATUS_CODE_SUCCESS { 0 };
static const int OVEN_STATUS_CODE_FAIL { 1 };
static const int OVEN_STATUS_CODE_ABORT { 2 };
static const QString OVEN_ERROR_FILENAME = "errors.txt";
class BakerCLI : public QObject {
Q_OBJECT
public:
BakerCLI(Oven* parent);
void bakeFile(QUrl inputUrl, const QString outputPath);
void bakeFile(QUrl inputUrl, const QString& outputPath, const QString& type = QString::null);
private slots:
void handleFinishedBaker();
private:
QDir _outputPath;
std::unique_ptr<Baker> _baker;
};
#endif // hifi_BakerCLI_h
#endif // hifi_BakerCLI_h

View file

@ -24,6 +24,7 @@ static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/exp
static const QString CLI_INPUT_PARAMETER = "i";
static const QString CLI_OUTPUT_PARAMETER = "o";
static const QString CLI_TYPE_PARAMETER = "t";
Oven::Oven(int argc, char* argv[]) :
QApplication(argc, argv)
@ -39,7 +40,8 @@ Oven::Oven(int argc, char* argv[]) :
parser.addOptions({
{ CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" },
{ CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" }
{ CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" },
{ CLI_TYPE_PARAMETER, "Type of asset.", "type" }
});
parser.addHelpOption();
parser.process(*this);
@ -59,7 +61,8 @@ Oven::Oven(int argc, char* argv[]) :
BakerCLI* cli = new BakerCLI(this);
QUrl inputUrl(QDir::fromNativeSeparators(parser.value(CLI_INPUT_PARAMETER)));
QUrl outputUrl(QDir::fromNativeSeparators(parser.value(CLI_OUTPUT_PARAMETER)));
cli->bakeFile(inputUrl, outputUrl.toString());
QString type = parser.isSet(CLI_TYPE_PARAMETER) ? parser.value(CLI_TYPE_PARAMETER) : QString::null;
cli->bakeFile(inputUrl, outputUrl.toString(), type);
} else {
parser.showHelp();
QApplication::quit();

View file

@ -14,6 +14,8 @@
#include <QtWidgets/QWidget>
#include <memory>
#include <Baker.h>
class BakeWidget : public QWidget {