call image library for texture baking

This commit is contained in:
Stephen Birarda 2017-04-17 09:14:34 -07:00
parent 429e65888b
commit 49e7ae6dbc
13 changed files with 158 additions and 124 deletions

View file

@ -22,6 +22,8 @@
#include "Forward.h" #include "Forward.h"
#include "Resource.h" #include "Resource.h"
const int ABSOLUTE_MAX_TEXTURE_NUM_PIXELS = 8192 * 8192;
namespace ktx { namespace ktx {
class KTX; class KTX;
using KTXUniquePointer = std::unique_ptr<KTX>; using KTXUniquePointer = std::unique_ptr<KTX>;

View file

@ -2,7 +2,7 @@ set(TARGET_NAME model-baking)
setup_hifi_library(Concurrent) setup_hifi_library(Concurrent)
link_hifi_libraries(networking) link_hifi_libraries(networking image gpu shared ktx)
find_package(FBXSDK REQUIRED) find_package(FBXSDK REQUIRED)
target_link_libraries(${TARGET_NAME} ${FBX_LIBRARIES}) target_link_libraries(${TARGET_NAME} ${FBX_LIBRARIES})

View file

@ -19,7 +19,7 @@ void Baker::handleError(const QString& error) {
emit finished(); emit finished();
} }
void Baker::appendErrors(const QStringList& errors) { void Baker::handleErrors(const QStringList& errors) {
// we're appending errors, presumably from a baking operation we called // we're appending errors, presumably from a baking operation we called
// add those to our list and emit that we are finished // add those to our list and emit that we are finished
_errorList.append(errors); _errorList.append(errors);

View file

@ -33,8 +33,7 @@ protected:
void handleError(const QString& error); void handleError(const QString& error);
void handleWarning(const QString& warning); void handleWarning(const QString& warning);
void appendErrors(const QStringList& errors); void handleErrors(const QStringList& errors);
void appendWarnings(const QStringList& warnings) { _warningList << warnings; }
QStringList _errorList; QStringList _errorList;
QStringList _warningList; QStringList _warningList;

View file

@ -207,8 +207,6 @@ void FBXBaker::importScene() {
importer->Destroy(); importer->Destroy();
} }
static const QString BAKED_TEXTURE_EXT = ".ktx";
QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) { QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) {
auto fbxPath = fbxURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); auto fbxPath = fbxURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment);
auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment);
@ -256,49 +254,25 @@ QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, FbxFileTexture* f
QString relativeFileName = fileTexture->GetRelativeFileName(); QString relativeFileName = fileTexture->GetRelativeFileName();
auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/")); auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/"));
#ifndef Q_OS_WIN // this is a relative file path which will require different handling
// it turns out that paths that start with a drive letter and a colon appear to QFileInfo // depending on the location of the original FBX
// as a relative path on UNIX systems - we perform a special check here to handle that case if (_fbxURL.isLocalFile() && apparentRelativePath.exists() && apparentRelativePath.isFile()) {
bool isAbsolute = relativeFileName[1] == ':' || apparentRelativePath.isAbsolute(); // the absolute path we ran into for the texture in the FBX exists on this machine
#else // so use that file
bool isAbsolute = apparentRelativePath.isAbsolute(); urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath());
#endif
if (isAbsolute) {
// this is a relative file path which will require different handling
// depending on the location of the original FBX
if (_fbxURL.isLocalFile()) {
// since the loaded FBX is loaded, first check if we actually have the texture locally
// at the absolute path
if (apparentRelativePath.exists() && apparentRelativePath.isFile()) {
// the absolute path we ran into for the texture in the FBX exists on this machine
// so use that file
urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath());
} else {
// we didn't find the texture on this machine at the absolute path
// so assume that it is right beside the FBX to match the behaviour of interface
urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName());
}
} else {
// the original FBX was remote and downloaded
// since this "relative" texture path is actually absolute, we have to assume it is beside the FBX
// which matches the behaviour of Interface
// append that path to our list of unbaked textures
urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName());
}
} else { } else {
// simply construct a URL with the relative path to the asset, locally or remotely // we didn't find the texture on this machine at the absolute path
// and append that to the list of unbaked textures // so assume that it is right beside the FBX to match the behaviour of interface
urlToTexture = _fbxURL.resolved(apparentRelativePath.filePath()); urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName());
} }
} }
return urlToTexture; return urlToTexture;
} }
TextureType textureTypeForMaterialProperty(FbxProperty& property, FbxSurfaceMaterial* material) { gpu::TextureType textureTypeForMaterialProperty(FbxProperty& property, FbxSurfaceMaterial* material) {
using namespace gpu;
// this is a property we know has a texture, we need to match it to a High Fidelity known texture type // this is a property we know has a texture, we need to match it to a High Fidelity known texture type
// since that information is passed to the baking process // since that information is passed to the baking process
@ -366,7 +340,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
// figure out the type of texture from the material property // figure out the type of texture from the material property
auto textureType = textureTypeForMaterialProperty(property, material); auto textureType = textureTypeForMaterialProperty(property, material);
if (textureType != UNUSED_TEXTURE) { if (textureType != gpu::UNUSED_TEXTURE) {
int numTextures = property.GetSrcObjectCount<FbxFileTexture>(); int numTextures = property.GetSrcObjectCount<FbxFileTexture>();
for (int j = 0; j < numTextures; j++) { for (int j = 0; j < numTextures; j++) {
@ -401,7 +375,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
_unbakedTextures.insert(urlToTexture, bakedTextureFileName); _unbakedTextures.insert(urlToTexture, bakedTextureFileName);
// bake this texture asynchronously // bake this texture asynchronously
bakeTexture(urlToTexture); bakeTexture(urlToTexture, textureType, bakedTextureFilePath);
} }
} }
} }
@ -414,63 +388,91 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
} }
} }
void FBXBaker::bakeTexture(const QUrl& textureURL) { void FBXBaker::bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath) {
// start a bake for this texture and add it to our list to keep track of // start a bake for this texture and add it to our list to keep track of
QSharedPointer<TextureBaker> bakingTexture { new TextureBaker(textureURL), &TextureBaker::deleteLater }; QSharedPointer<TextureBaker> bakingTexture {
new TextureBaker(textureURL, textureType, destinationFilePath),
&TextureBaker::deleteLater
};
// make sure we hear when the baking texture is done
connect(bakingTexture.data(), &Baker::finished, this, &FBXBaker::handleBakedTexture); connect(bakingTexture.data(), &Baker::finished, this, &FBXBaker::handleBakedTexture);
QtConcurrent::run(bakingTexture.data(), &TextureBaker::bake); // keep a shared pointer to the baking texture
_bakingTextures.insert(bakingTexture); _bakingTextures.insert(bakingTexture);
// start baking the texture on our thread pool
QtConcurrent::run(bakingTexture.data(), &TextureBaker::bake);
} }
void FBXBaker::handleBakedTexture() { void FBXBaker::handleBakedTexture() {
TextureBaker* bakedTexture = qobject_cast<TextureBaker*>(sender()); TextureBaker* bakedTexture = qobject_cast<TextureBaker*>(sender());
// make sure we haven't already run into errors, and that this is a valid texture // make sure we haven't already run into errors, and that this is a valid texture
if (!hasErrors() && bakedTexture) { if (bakedTexture) {
if (!bakedTexture->hasErrors()) { if (!hasErrors()) {
// use the path to the texture being baked to determine if this was an embedded or a linked texture if (!bakedTexture->hasErrors()) {
if (_copyOriginals) {
// we've been asked to make copies of the originals, so we need to make copies of this if it is a linked texture
// it is embeddded if the texure being baked was inside the original output folder // use the path to the texture being baked to determine if this was an embedded or a linked texture
// since that is where the FBX SDK places the .fbm folder it generates when importing the FBX
auto originalOutputFolder = QUrl::fromLocalFile(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER); // it is embeddded if the texure being baked was inside the original output folder
// since that is where the FBX SDK places the .fbm folder it generates when importing the FBX
if (!originalOutputFolder.isParentOf(bakedTexture->getTextureURL())) { auto originalOutputFolder = QUrl::fromLocalFile(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER);
// for linked textures we want to save a copy of original texture beside the original FBX
qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL(); if (!originalOutputFolder.isParentOf(bakedTexture->getTextureURL())) {
// for linked textures we want to save a copy of original texture beside the original FBX
// check if we have a relative path to use for the texture qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL();
auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL());
QFile originalTextureFile { // check if we have a relative path to use for the texture
_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + relativeTexturePath + bakedTexture->getTextureURL().fileName() auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL());
};
if (relativeTexturePath.length() > 0) { QFile originalTextureFile {
// make the folders needed by the relative path _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + relativeTexturePath + bakedTexture->getTextureURL().fileName()
};
if (relativeTexturePath.length() > 0) {
// make the folders needed by the relative path
}
if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) {
qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName()
<< "for" << _fbxURL;
} else {
handleError("Could not save original external texture " + originalTextureFile.fileName()
+ " for " + _fbxURL.toString());
return;
}
}
} }
if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) {
qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName() // now that this texture has been baked and handled, we can remove that TextureBaker from our list
<< "for" << _fbxURL; _unbakedTextures.remove(bakedTexture->getTextureURL());
} else {
handleError("Could not save original external texture " + originalTextureFile.fileName() checkIfTexturesFinished();
+ " for " + _fbxURL.toString()); } else {
return; // there was an error baking this texture - add it to our list of errors
} _errorList.append(bakedTexture->getErrors());
// we don't emit finished yet so that the other textures can finish baking first
_pendingErrorEmission = true;
// now that this texture has been baked, even though it failed, we can remove that TextureBaker from our list
_unbakedTextures.remove(bakedTexture->getTextureURL());
checkIfTexturesFinished();
} }
} else {
// now that this texture has been baked and handled, we can remove that TextureBaker from our list // we have errors to attend to, so we don't do extra processing for this texture
// but we do need to remove that TextureBaker from our list
// and then check if we're done with all textures
_unbakedTextures.remove(bakedTexture->getTextureURL()); _unbakedTextures.remove(bakedTexture->getTextureURL());
checkIfTexturesFinished(); checkIfTexturesFinished();
} else {
// there was an error baking this texture - add it to our list of errors and stop processing this FBX
appendErrors(bakedTexture->getErrors());
} }
} }
} }
@ -516,23 +518,27 @@ void FBXBaker::possiblyCleanupOriginals() {
void FBXBaker::checkIfTexturesFinished() { void FBXBaker::checkIfTexturesFinished() {
// check if we're done everything we need to do for this FBX // check if we're done everything we need to do for this FBX
// and emit our finished signal if we're done // and emit our finished signal if we're done
if (_unbakedTextures.isEmpty()) { if (_unbakedTextures.isEmpty()) {
// remove the embedded media folder that the FBX SDK produces when reading the original // remove the embedded media folder that the FBX SDK produces when reading the original
removeEmbeddedMediaFolder(); removeEmbeddedMediaFolder();
if (hasErrors()) {
return;
}
// cleanup the originals if we weren't asked to keep them around // cleanup the originals if we weren't asked to keep them around
possiblyCleanupOriginals(); possiblyCleanupOriginals();
if (hasErrors()) { if (hasErrors()) {
return; // if we're checking for completion but we have errors
} // that means one or more of our texture baking operations failed
qCDebug(model_baking) << "Finished baking" << _fbxURL; if (_pendingErrorEmission) {
emit finished();
emit finished(); }
return;
} else {
qCDebug(model_baking) << "Finished baking" << _fbxURL;
emit finished();
}
} }
} }

View file

@ -20,6 +20,8 @@
#include "Baker.h" #include "Baker.h"
#include "TextureBaker.h" #include "TextureBaker.h"
#include <gpu/Texture.h>
namespace fbxsdk { namespace fbxsdk {
class FbxManager; class FbxManager;
class FbxProperty; class FbxProperty;
@ -27,25 +29,6 @@ namespace fbxsdk {
class FbxFileTexture; class FbxFileTexture;
} }
enum TextureType {
DEFAULT_TEXTURE,
STRICT_TEXTURE,
ALBEDO_TEXTURE,
NORMAL_TEXTURE,
BUMP_TEXTURE,
SPECULAR_TEXTURE,
METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey
ROUGHNESS_TEXTURE,
GLOSS_TEXTURE,
EMISSIVE_TEXTURE,
CUBE_TEXTURE,
OCCLUSION_TEXTURE,
SCATTERING_TEXTURE = OCCLUSION_TEXTURE,
LIGHTMAP_TEXTURE,
CUSTOM_TEXTURE,
UNUSED_TEXTURE = -1
};
static const QString BAKED_FBX_EXTENSION = ".baked.fbx"; static const QString BAKED_FBX_EXTENSION = ".baked.fbx";
using FBXSDKManagerUniquePointer = std::unique_ptr<fbxsdk::FbxManager, std::function<void (fbxsdk::FbxManager *)>>; using FBXSDKManagerUniquePointer = std::unique_ptr<fbxsdk::FbxManager, std::function<void (fbxsdk::FbxManager *)>>;
@ -89,7 +72,7 @@ private:
QString createBakedTextureFileName(const QFileInfo& textureFileInfo); QString createBakedTextureFileName(const QFileInfo& textureFileInfo);
QUrl getTextureURL(const QFileInfo& textureFileInfo, fbxsdk::FbxFileTexture* fileTexture); QUrl getTextureURL(const QFileInfo& textureFileInfo, fbxsdk::FbxFileTexture* fileTexture);
void bakeTexture(const QUrl& textureURL); void bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath);
QString pathToCopyOfOriginal() const; QString pathToCopyOfOriginal() const;
@ -103,18 +86,15 @@ private:
static FBXSDKManagerUniquePointer _sdkManager; static FBXSDKManagerUniquePointer _sdkManager;
fbxsdk::FbxScene* _scene { nullptr }; fbxsdk::FbxScene* _scene { nullptr };
QStringList _errorList;
QHash<QUrl, QString> _unbakedTextures; QHash<QUrl, QString> _unbakedTextures;
QHash<QString, int> _textureNameMatchCount; QHash<QString, int> _textureNameMatchCount;
QHash<uint64_t, TextureType> _textureTypes;
QSet<QSharedPointer<TextureBaker>> _bakingTextures; QSet<QSharedPointer<TextureBaker>> _bakingTextures;
QFutureSynchronizer<void> _textureBakeSynchronizer; QFutureSynchronizer<void> _textureBakeSynchronizer;
bool _copyOriginals { true }; bool _copyOriginals { true };
bool _finishedNonTextureOperations { false }; bool _pendingErrorEmission { false };
}; };
#endif // hifi_FBXBaker_h #endif // hifi_FBXBaker_h

View file

@ -9,20 +9,27 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include <QtCore/QDir>
#include <QtCore/QEventLoop> #include <QtCore/QEventLoop>
#include <QtCore/QFile> #include <QtCore/QFile>
#include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkReply>
#include <image/Image.h>
#include <ktx/KTX.h>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include "ModelBakingLoggingCategory.h" #include "ModelBakingLoggingCategory.h"
#include "TextureBaker.h" #include "TextureBaker.h"
TextureBaker::TextureBaker(const QUrl& textureURL) : const QString BAKED_TEXTURE_EXT = ".ktx";
_textureURL(textureURL)
TextureBaker::TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath) :
_textureURL(textureURL),
_textureType(textureType),
_destinationFilePath(destinationFilePath)
{ {
} }
void TextureBaker::bake() { void TextureBaker::bake() {
@ -35,6 +42,14 @@ void TextureBaker::bake() {
qCDebug(model_baking) << "Baking texture at" << _textureURL; qCDebug(model_baking) << "Baking texture at" << _textureURL;
processTexture();
if (hasErrors()) {
return;
}
qCDebug(model_baking) << "Baked texture at" << _textureURL;
emit finished(); emit finished();
} }
@ -83,6 +98,33 @@ void TextureBaker::handleTextureNetworkReply(QNetworkReply* requestReply) {
_originalTexture = requestReply->readAll(); _originalTexture = requestReply->readAll();
} else { } else {
// add an error to our list stating that this texture could not be downloaded // add an error to our list stating that this texture could not be downloaded
qCDebug(model_baking) << "Error downloading texture" << requestReply->errorString(); handleError("Error downloading " + _textureURL.toString() + " - " + requestReply->errorString());
}
}
void TextureBaker::processTexture() {
auto processedTexture = image::processImage(_originalTexture, _textureURL.toString().toStdString(),
ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType);
if (!processedTexture) {
handleError("Could not process texture " + _textureURL.toString());
return;
}
auto memKTX = gpu::Texture::serialize(*processedTexture);
if (!memKTX) {
handleError("Could not serialize " + _textureURL.toString() + " to KTX");
return;
}
const char* data = reinterpret_cast<const char*>(memKTX->_storage->data());
const size_t length = memKTX->_storage->size();
// attempt to write the baked texture to the destination file path
QFile bakedTextureFile { _destinationFilePath };
if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) {
handleError("Could not write baked texture for " + _textureURL.toString());
} }
} }

View file

@ -14,14 +14,19 @@
#include <QtCore/QObject> #include <QtCore/QObject>
#include <QtCore/QUrl> #include <QtCore/QUrl>
#include <QtCore/QRunnable>
#include <gpu/Texture.h>
#include "Baker.h" #include "Baker.h"
extern const QString BAKED_TEXTURE_EXT;
class TextureBaker : public Baker { class TextureBaker : public Baker {
Q_OBJECT Q_OBJECT
public: public:
TextureBaker(const QUrl& textureURL); TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath);
void bake(); void bake();
@ -33,8 +38,13 @@ private:
void loadTexture(); void loadTexture();
void handleTextureNetworkReply(QNetworkReply* requestReply); void handleTextureNetworkReply(QNetworkReply* requestReply);
void processTexture();
QUrl _textureURL; QUrl _textureURL;
QByteArray _originalTexture; QByteArray _originalTexture;
gpu::TextureType _textureType;
QString _destinationFilePath;
}; };
#endif // hifi_TextureBaker_h #endif // hifi_TextureBaker_h

View file

@ -27,8 +27,6 @@
#include "KTXCache.h" #include "KTXCache.h"
const int ABSOLUTE_MAX_TEXTURE_NUM_PIXELS = 8192 * 8192;
namespace gpu { namespace gpu {
class Batch; class Batch;
} }

View file

@ -34,7 +34,7 @@ Q_LOGGING_CATEGORY(trace_simulation_physics_detail, "trace.simulation.physics.de
#endif #endif
static bool tracingEnabled() { static bool tracingEnabled() {
return DependencyManager::get<tracing::Tracer>()->isEnabled(); return DependencyManager::isSet<tracing::Tracer>() && DependencyManager::get<tracing::Tracer>()->isEnabled();
} }
Duration::Duration(const QLoggingCategory& category, const QString& name, uint32_t argbColor, uint64_t payload, const QVariantMap& baseArgs) : _name(name), _category(category) { Duration::Duration(const QLoggingCategory& category, const QString& name, uint32_t argbColor, uint64_t payload, const QVariantMap& baseArgs) : _name(name), _category(category) {

View file

@ -2,4 +2,4 @@ set(TARGET_NAME oven)
setup_hifi_project(Widgets Gui Concurrent) setup_hifi_project(Widgets Gui Concurrent)
link_hifi_libraries(model-baking shared) link_hifi_libraries(model-baking shared image gpu ktx)

View file

@ -236,7 +236,7 @@ void DomainBaker::handleFinishedBaker() {
QUrl oldModelURL { entity[ENTITY_MODEL_URL_KEY].toString() }; QUrl oldModelURL { entity[ENTITY_MODEL_URL_KEY].toString() };
// setup a new URL using the prefix we were passed // setup a new URL using the prefix we were passed
QUrl newModelURL = _destinationPath.resolved(baker->getBakedFBXRelativePath().mid(1)); QUrl newModelURL = _destinationPath.resolved(baker->getBakedFBXRelativePath());
// copy the fragment and query from the old model URL // copy the fragment and query from the old model URL
newModelURL.setQuery(oldModelURL.query()); newModelURL.setQuery(oldModelURL.query());
@ -251,7 +251,7 @@ void DomainBaker::handleFinishedBaker() {
} else { } else {
// this model failed to bake - this doesn't fail the entire bake but we need to add // this model failed to bake - this doesn't fail the entire bake but we need to add
// the errors from the model to our errors // the errors from the model to our errors
appendWarnings(baker->getErrors()); _warningList << baker->getErrors();
} }
// remove the baked URL from the multi hash of entities needing a re-write // remove the baked URL from the multi hash of entities needing a re-write

View file

@ -160,9 +160,6 @@ void DomainBakeWidget::chooseFileButtonClicked() {
// save the directory containing this entities file so we can default to it next time we show the file dialog // save the directory containing this entities file so we can default to it next time we show the file dialog
_browseStartDirectory.set(directoryOfEntitiesFile); _browseStartDirectory.set(directoryOfEntitiesFile);
// if our output directory is not yet set, set it to the directory of this entities file
_outputDirLineEdit->setText(directoryOfEntitiesFile);
} }
} }