handle skybox baking from oven menu

This commit is contained in:
Stephen Birarda 2017-04-17 16:02:49 -07:00
parent 25d24c445d
commit 980de595a9
11 changed files with 335 additions and 51 deletions

View file

@ -378,7 +378,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
_unbakedTextures.insert(urlToTexture, bakedTextureFileName);
// bake this texture asynchronously
bakeTexture(urlToTexture, textureType, bakedTextureFilePath);
bakeTexture(urlToTexture, textureType, _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER);
}
}
}
@ -391,10 +391,10 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
}
}
void FBXBaker::bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath) {
void FBXBaker::bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QDir& outputDir) {
// start a bake for this texture and add it to our list to keep track of
QSharedPointer<TextureBaker> bakingTexture {
new TextureBaker(textureURL, textureType, destinationFilePath),
new TextureBaker(textureURL, textureType, outputDir),
&TextureBaker::deleteLater
};

View file

@ -74,7 +74,7 @@ private:
QString createBakedTextureFileName(const QFileInfo& textureFileInfo);
QUrl getTextureURL(const QFileInfo& textureFileInfo, fbxsdk::FbxFileTexture* fileTexture);
void bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath);
void bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QDir& outputDir);
QString pathToCopyOfOriginal() const;

View file

@ -24,12 +24,14 @@
const QString BAKED_TEXTURE_EXT = ".ktx";
TextureBaker::TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath) :
TextureBaker::TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QDir& outputDirectory) :
_textureURL(textureURL),
_textureType(textureType),
_destinationFilePath(destinationFilePath)
_outputDirectory(outputDirectory)
{
// figure out the baked texture filename
auto originalFilename = textureURL.fileName();
_bakedTextureFileName = originalFilename.left(originalFilename.indexOf('.')) + BAKED_TEXTURE_EXT;
}
void TextureBaker::bake() {
@ -117,7 +119,7 @@ void TextureBaker::processTexture() {
const size_t length = memKTX->_storage->size();
// attempt to write the baked texture to the destination file path
QFile bakedTextureFile { _destinationFilePath };
QFile bakedTextureFile { _outputDirectory.absoluteFilePath(_bakedTextureFileName) };
if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) {
handleError("Could not write baked texture for " + _textureURL.toString());

View file

@ -26,13 +26,14 @@ class TextureBaker : public Baker {
Q_OBJECT
public:
TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath);
TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QDir& outputDirectory);
const QByteArray& getOriginalTexture() const { return _originalTexture; }
const QUrl& getTextureURL() const { return _textureURL; }
QUrl getTextureURL() const { return _textureURL; }
const QString& getDestinationFilePath() const { return _destinationFilePath; }
QString getDestinationFilePath() const { return _outputDirectory.absoluteFilePath(_bakedTextureFileName); }
QString getBakedTextureFileName() const { return _bakedTextureFileName; }
public slots:
virtual void bake() override;
@ -51,7 +52,8 @@ private:
QByteArray _originalTexture;
gpu::TextureType _textureType;
QString _destinationFilePath;
QDir _outputDirectory;
QString _bakedTextureFileName;
};
#endif // hifi_TextureBaker_h

View file

@ -225,7 +225,9 @@ void DomainBaker::bakeSkybox(QUrl skyboxURL, QJsonValueRef entity) {
auto skyboxFileName = skyboxURL.fileName();
static const QStringList BAKEABLE_SKYBOX_EXTENSIONS { ".jpg" };
static const QStringList BAKEABLE_SKYBOX_EXTENSIONS {
".jpg", ".png", ".gif", ".bmp", ".pbm", ".pgm", ".ppm", ".xbm", ".xpm", ".svg"
};
auto completeLowerExtension = skyboxFileName.mid(skyboxFileName.indexOf('.')).toLower();
if (BAKEABLE_SKYBOX_EXTENSIONS.contains(completeLowerExtension)) {
@ -234,13 +236,10 @@ void DomainBaker::bakeSkybox(QUrl skyboxURL, QJsonValueRef entity) {
// setup a texture baker for this URL, as long as we aren't baking a skybox already
if (!_skyboxBakers.contains(skyboxURL)) {
// figure out the path for this baked skybox
auto skyboxFileName = skyboxURL.fileName();
auto bakedSkyboxFileName = skyboxFileName.left(skyboxFileName.indexOf('.')) + BAKED_TEXTURE_EXT;
auto bakedTextureDestination = QDir(_contentOutputPath).absoluteFilePath(bakedSkyboxFileName);
// setup a baker for this skybox
QSharedPointer<TextureBaker> skyboxBaker {
new TextureBaker(skyboxURL, gpu::CUBE_TEXTURE, bakedTextureDestination)
new TextureBaker(skyboxURL, gpu::CUBE_TEXTURE, _contentOutputPath)
};
// make sure our handler is called when the skybox baker is done
@ -409,9 +408,8 @@ bool DomainBaker::rewriteSkyboxURL(QJsonValueRef urlValue, TextureBaker* baker)
if (oldSkyboxURL.matches(baker->getTextureURL(), QUrl::RemoveQuery | QUrl::RemoveFragment)) {
// change the URL to point to the baked texture with its original query and fragment
auto bakedSkyboxFileName = QFileInfo(baker->getDestinationFilePath()).fileName();
auto newSkyboxURL = _destinationPath.resolved(bakedSkyboxFileName);
auto newSkyboxURL = _destinationPath.resolved(baker->getBakedTextureFileName());
newSkyboxURL.setQuery(oldSkyboxURL.query());
newSkyboxURL.setFragment(oldSkyboxURL.fragment());
newSkyboxURL.setUserInfo(oldSkyboxURL.userInfo());

View file

@ -25,24 +25,17 @@
#include "ModelBakeWidget.h"
static const QString EXPORT_DIR_SETTING_KEY = "model_export_directory";
static const QString MODEL_START_DIR_SETTING_KEY = "model_search_directory";
static const auto EXPORT_DIR_SETTING_KEY = "model_export_directory";
static const auto MODEL_START_DIR_SETTING_KEY = "model_search_directory";
ModelBakeWidget::ModelBakeWidget(QWidget* parent, Qt::WindowFlags flags) :
QWidget(parent, flags),
_exportDirectory(EXPORT_DIR_SETTING_KEY),
_modelStartDirectory(MODEL_START_DIR_SETTING_KEY),
_bakerThread(new QThread(this))
_modelStartDirectory(MODEL_START_DIR_SETTING_KEY)
{
setupUI();
}
ModelBakeWidget::~ModelBakeWidget() {
// before we go down, stop the baker thread and make sure it's done
_bakerThread->quit();
_bakerThread->wait();
}
void ModelBakeWidget::setupUI() {
// setup a grid layout to hold everything
QGridLayout* gridLayout = new QGridLayout;
@ -189,13 +182,8 @@ void ModelBakeWidget::bakeButtonClicked() {
}, false)
};
// move the baker to the baker thread
baker->moveToThread(_bakerThread);
// make sure we start the baker thread if it isn't already running
if (!_bakerThread->isRunning()) {
_bakerThread->start();
}
// move the baker to the FBX baker thread
baker->moveToThread(qApp->getFBXBakerThread());
// invoke the bake method on the baker thread
QMetaObject::invokeMethod(baker.get(), "bake");
@ -216,7 +204,7 @@ void ModelBakeWidget::bakeButtonClicked() {
void ModelBakeWidget::handleFinishedBaker() {
if (auto baker = qobject_cast<FBXBaker*>(sender())) {
// add the results of this bake to the results window
auto it = std::remove_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) {
auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) {
return value.first.get() == baker;
});
@ -229,6 +217,8 @@ void ModelBakeWidget::handleFinishedBaker() {
} else {
resultsWindow->changeStatusForRow(resultRow, "Success");
}
_bakers.erase(it);
}
}
}

View file

@ -26,7 +26,6 @@ class ModelBakeWidget : public QWidget {
public:
ModelBakeWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags());
~ModelBakeWidget();
private slots:
void chooseFileButtonClicked();
@ -50,8 +49,6 @@ private:
Setting::Handle<QString> _exportDirectory;
Setting::Handle<QString> _modelStartDirectory;
QThread* _bakerThread;
};
#endif // hifi_ModelBakeWidget_h

View file

@ -15,6 +15,7 @@
#include "DomainBakeWidget.h"
#include "ModelBakeWidget.h"
#include "SkyboxBakeWidget.h"
#include "ModesWidget.h"
@ -28,19 +29,20 @@ void ModesWidget::setupUI() {
// setup a horizontal box layout to hold our mode buttons
QHBoxLayout* horizontalLayout = new QHBoxLayout;
// add a button for model baking
QPushButton* modelsButton = new QPushButton("Bake Models");
connect(modelsButton, &QPushButton::clicked, this, &ModesWidget::showModelBakingWidget);
horizontalLayout->addWidget(modelsButton);
// add a button for domain baking
QPushButton* domainButton = new QPushButton("Bake Domain");
connect(domainButton, &QPushButton::clicked, this, &ModesWidget::showDomainBakingWidget);
horizontalLayout->addWidget(domainButton);
// add a button for texture baking
QPushButton* textureButton = new QPushButton("Bake Textures");
horizontalLayout->addWidget(textureButton);
// add a button for model baking
QPushButton* modelsButton = new QPushButton("Bake Models");
connect(modelsButton, &QPushButton::clicked, this, &ModesWidget::showModelBakingWidget);
horizontalLayout->addWidget(modelsButton);
// add a button for skybox baking
QPushButton* skyboxButton = new QPushButton("Bake Skyboxes");
connect(skyboxButton, &QPushButton::clicked, this, &ModesWidget::showSkyboxBakingWidget);
horizontalLayout->addWidget(skyboxButton);
setLayout(horizontalLayout);
}
@ -48,13 +50,20 @@ void ModesWidget::setupUI() {
void ModesWidget::showModelBakingWidget() {
auto stackedWidget = qobject_cast<QStackedWidget*>(parentWidget());
// add a new widget for making baking to the stack, and switch to it
// add a new widget for model baking to the stack, and switch to it
stackedWidget->setCurrentIndex(stackedWidget->addWidget(new ModelBakeWidget));
}
void ModesWidget::showDomainBakingWidget() {
auto stackedWidget = qobject_cast<QStackedWidget*>(parentWidget());
// add a new widget for making baking to the stack, and switch to it
// add a new widget for domain baking to the stack, and switch to it
stackedWidget->setCurrentIndex(stackedWidget->addWidget(new DomainBakeWidget));
}
void ModesWidget::showSkyboxBakingWidget() {
auto stackedWidget = qobject_cast<QStackedWidget*>(parentWidget());
// add a new widget for skybox baking to the stack, and switch to it
stackedWidget->setCurrentIndex(stackedWidget->addWidget(new SkyboxBakeWidget));
}

View file

@ -22,6 +22,7 @@ public:
private slots:
void showModelBakingWidget();
void showDomainBakingWidget();
void showSkyboxBakingWidget();
private:
void setupUI();

View file

@ -0,0 +1,232 @@
//
// SkyboxBakeWidget.cpp
// tools/oven/src/ui
//
// Created by Stephen Birarda on 4/17/17.
// Copyright 2017 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/QFileDialog>
#include <QtWidgets/QGridLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QStackedWidget>
#include <QtCore/QDir>
#include <QtCore/QDebug>
#include <QtCore/QThread>
#include "../Oven.h"
#include "OvenMainWindow.h"
#include "SkyboxBakeWidget.h"
static const auto EXPORT_DIR_SETTING_KEY = "skybox_export_directory";
static const auto SELECTION_START_DIR_SETTING_KEY = "skybox_search_directory";
SkyboxBakeWidget::SkyboxBakeWidget(QWidget* parent, Qt::WindowFlags flags) :
QWidget(parent, flags),
_exportDirectory(EXPORT_DIR_SETTING_KEY),
_selectionStartDirectory(SELECTION_START_DIR_SETTING_KEY)
{
setupUI();
}
void SkyboxBakeWidget::setupUI() {
// setup a grid layout to hold everything
QGridLayout* gridLayout = new QGridLayout;
int rowIndex = 0;
// setup a section to choose the file being baked
QLabel* skyboxFileLabel = new QLabel("Skybox File(s)");
_selectionLineEdit = new QLineEdit;
_selectionLineEdit->setPlaceholderText("File or URL");
QPushButton* chooseFileButton = new QPushButton("Browse...");
connect(chooseFileButton, &QPushButton::clicked, this, &SkyboxBakeWidget::chooseFileButtonClicked);
// add the components for the model file picker to the layout
gridLayout->addWidget(skyboxFileLabel, rowIndex, 0);
gridLayout->addWidget(_selectionLineEdit, rowIndex, 1, 1, 3);
gridLayout->addWidget(chooseFileButton, rowIndex, 4);
// start a new row for next component
++rowIndex;
// setup a section to choose the output directory
QLabel* outputDirectoryLabel = new QLabel("Output Directory");
_outputDirLineEdit = new QLineEdit;
// set the current export directory to whatever was last used
_outputDirLineEdit->setText(_exportDirectory.get());
// whenever the output directory line edit changes, update the value in settings
connect(_outputDirLineEdit, &QLineEdit::textChanged, this, &SkyboxBakeWidget::outputDirectoryChanged);
QPushButton* chooseOutputDirectoryButton = new QPushButton("Browse...");
connect(chooseOutputDirectoryButton, &QPushButton::clicked, this, &SkyboxBakeWidget::chooseOutputDirButtonClicked);
// add the components for the output directory picker to the layout
gridLayout->addWidget(outputDirectoryLabel, rowIndex, 0);
gridLayout->addWidget(_outputDirLineEdit, rowIndex, 1, 1, 3);
gridLayout->addWidget(chooseOutputDirectoryButton, rowIndex, 4);
// start a new row for the next component
++rowIndex;
// add a horizontal line to split the bake/cancel buttons off
QFrame* lineFrame = new QFrame;
lineFrame->setFrameShape(QFrame::HLine);
lineFrame->setFrameShadow(QFrame::Sunken);
gridLayout->addWidget(lineFrame, rowIndex, 0, 1, -1);
// start a new row for the next component
++rowIndex;
// add a button that will kickoff the bake
QPushButton* bakeButton = new QPushButton("Bake");
connect(bakeButton, &QPushButton::clicked, this, &SkyboxBakeWidget::bakeButtonClicked);
gridLayout->addWidget(bakeButton, rowIndex, 3);
// add a cancel button to go back to the modes page
QPushButton* cancelButton = new QPushButton("Cancel");
connect(cancelButton, &QPushButton::clicked, this, &SkyboxBakeWidget::cancelButtonClicked);
gridLayout->addWidget(cancelButton, rowIndex, 4);
setLayout(gridLayout);
}
void SkyboxBakeWidget::chooseFileButtonClicked() {
// pop a file dialog so the user can select the skybox file(s)
// if we have picked a skybox before, start in the folder that matches the last path
// otherwise start in the home directory
auto startDir = _selectionStartDirectory.get();
if (startDir.isEmpty()) {
startDir = QDir::homePath();
}
auto selectedFiles = QFileDialog::getOpenFileNames(this, "Choose Skybox", startDir);
if (!selectedFiles.isEmpty()) {
// set the contents of the file select text box to be the path to the selected file
_selectionLineEdit->setText(selectedFiles.join(','));
if (_outputDirLineEdit->text().isEmpty()) {
auto directoryOfSkybox = QFileInfo(selectedFiles[0]).absolutePath();
// if our output directory is not yet set, set it to the directory of this skybox
_outputDirLineEdit->setText(directoryOfSkybox);
}
}
}
void SkyboxBakeWidget::chooseOutputDirButtonClicked() {
// pop a file dialog so the user can select the output directory
// if we have a previously selected output directory, use that as the initial path in the choose dialog
// otherwise use the user's home directory
auto startDir = _exportDirectory.get();
if (startDir.isEmpty()) {
startDir = QDir::homePath();
}
auto selectedDir = QFileDialog::getExistingDirectory(this, "Choose Output Directory", startDir);
if (!selectedDir.isEmpty()) {
// set the contents of the output directory text box to be the path to the directory
_outputDirLineEdit->setText(selectedDir);
}
}
void SkyboxBakeWidget::outputDirectoryChanged(const QString& newDirectory) {
// update the export directory setting so we can re-use it next time
_exportDirectory.set(newDirectory);
}
void SkyboxBakeWidget::bakeButtonClicked() {
// make sure we have a valid output directory
QDir outputDirectory(_outputDirLineEdit->text());
if (!outputDirectory.exists()) {
return;
}
// make sure we have a non empty URL to a skybox to bake
if (_selectionLineEdit->text().isEmpty()) {
return;
}
// split the list from the model line edit to see how many models we need to bake
auto fileURLStrings = _selectionLineEdit->text().split(',');
foreach (QString fileURLString, fileURLStrings) {
// construct a URL from the path in the model file text box
QUrl skyboxToBakeURL(fileURLString);
// if the URL doesn't have a scheme, assume it is a local file
if (skyboxToBakeURL.scheme().isEmpty()) {
skyboxToBakeURL.setScheme("file");
}
// everything seems to be in place, kick off a bake for this model now
auto baker = std::unique_ptr<TextureBaker> {
new TextureBaker(skyboxToBakeURL, gpu::CUBE_TEXTURE, outputDirectory.absolutePath())
};
// move the baker to a worker thread
baker->moveToThread(qApp->getNextWorkerThread());
// invoke the bake method on the baker thread
QMetaObject::invokeMethod(baker.get(), "bake");
// make sure we hear about the results of this baker when it is done
connect(baker.get(), &TextureBaker::finished, this, &SkyboxBakeWidget::handleFinishedBaker);
// add a pending row to the results window to show that this bake is in process
auto resultsWindow = qApp->getMainWindow()->showResultsWindow();
auto resultsRow = resultsWindow->addPendingResultRow(skyboxToBakeURL.fileName(), outputDirectory);
// keep a unique_ptr to this baker
// and remember the row that represents it in the results table
_bakers.emplace_back(std::move(baker), resultsRow);
}
}
void SkyboxBakeWidget::handleFinishedBaker() {
if (auto baker = qobject_cast<TextureBaker*>(sender())) {
// add the results of this bake to the results window
auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) {
return value.first.get() == baker;
});
if (it != _bakers.end()) {
auto resultRow = it->second;
auto resultsWindow = qApp->getMainWindow()->showResultsWindow();
if (baker->hasErrors()) {
resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n"));
} else {
resultsWindow->changeStatusForRow(resultRow, "Success");
}
// drop our strong pointer to the baker now that we are done with it
_bakers.erase(it);
}
}
}
void SkyboxBakeWidget::cancelButtonClicked() {
// the user wants to go back to the mode selection screen
// remove ourselves from the stacked widget and call delete later so we'll be cleaned up
auto stackedWidget = qobject_cast<QStackedWidget*>(parentWidget());
stackedWidget->removeWidget(this);
this->deleteLater();
}

View file

@ -0,0 +1,53 @@
//
// SkyboxBakeWidget.h
// tools/oven/src/ui
//
// Created by Stephen Birarda on 4/17/17.
// Copyright 2017 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_SkyboxBakeWidget_h
#define hifi_SkyboxBakeWidget_h
#include <QtWidgets/QWidget>
#include <SettingHandle.h>
#include <TextureBaker.h>
class QLineEdit;
class SkyboxBakeWidget : public QWidget {
Q_OBJECT
public:
SkyboxBakeWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags());
private slots:
void chooseFileButtonClicked();
void chooseOutputDirButtonClicked();
void bakeButtonClicked();
void cancelButtonClicked();
void outputDirectoryChanged(const QString& newDirectory);
void handleFinishedBaker();
private:
void setupUI();
using BakerRowPair = std::pair<std::unique_ptr<TextureBaker>, int>;
using BakerRowPairList = std::list<BakerRowPair>;
BakerRowPairList _bakers;
QLineEdit* _selectionLineEdit;
QLineEdit* _outputDirLineEdit;
Setting::Handle<QString> _exportDirectory;
Setting::Handle<QString> _selectionStartDirectory;
};
#endif // hifi_SkyboxBakeWidget_h