mirror of
https://github.com/lubosz/overte.git
synced 2025-04-09 00:02:39 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into commerce_QmlWhitelist
This commit is contained in:
commit
b81cdf49ef
46 changed files with 3882 additions and 311 deletions
|
@ -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()
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}());
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
2250
server-console/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -8,7 +8,6 @@
|
|||
""
|
||||
],
|
||||
"devDependencies": {
|
||||
"electron-compilers": "^1.0.1",
|
||||
"electron-packager": "^6.0.2",
|
||||
"electron-prebuilt": "0.37.5"
|
||||
},
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
57
tools/auto-tester/CMakeLists.txt
Normal file
57
tools/auto-tester/CMakeLists.txt
Normal 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 ()
|
7
tools/auto-tester/ReadMe.md
Normal file
7
tools/auto-tester/ReadMe.md
Normal 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/
|
119
tools/auto-tester/src/ImageComparer.cpp
Normal file
119
tools/auto-tester/src/ImageComparer.cpp
Normal 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;
|
||||
};
|
21
tools/auto-tester/src/ImageComparer.h
Normal file
21
tools/auto-tester/src/ImageComparer.h
Normal 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
|
383
tools/auto-tester/src/Test.cpp
Normal file
383
tools/auto-tester/src/Test.cpp
Normal 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());
|
||||
}
|
55
tools/auto-tester/src/Test.h
Normal file
55
tools/auto-tester/src/Test.h
Normal 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
|
37
tools/auto-tester/src/common.h
Normal file
37
tools/auto-tester/src/common.h
Normal 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
|
20
tools/auto-tester/src/main.cpp
Normal file
20
tools/auto-tester/src/main.cpp
Normal 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();
|
||||
}
|
35
tools/auto-tester/src/ui/AutoTester.cpp
Normal file
35
tools/auto-tester/src/ui/AutoTester.cpp
Normal 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);
|
||||
}
|
37
tools/auto-tester/src/ui/AutoTester.h
Normal file
37
tools/auto-tester/src/ui/AutoTester.h
Normal 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
|
106
tools/auto-tester/src/ui/AutoTester.ui
Normal file
106
tools/auto-tester/src/ui/AutoTester.ui
Normal 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>
|
46
tools/auto-tester/src/ui/MismatchWindow.cpp
Normal file
46
tools/auto-tester/src/ui/MismatchWindow.cpp
Normal 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();
|
||||
}
|
38
tools/auto-tester/src/ui/MismatchWindow.h
Normal file
38
tools/auto-tester/src/ui/MismatchWindow.h
Normal 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
|
157
tools/auto-tester/src/ui/MismatchWindow.ui
Normal file
157
tools/auto-tester/src/ui/MismatchWindow.ui
Normal 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>
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <Baker.h>
|
||||
|
||||
class BakeWidget : public QWidget {
|
||||
|
|
Loading…
Reference in a new issue