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 "Resource.h"
const int ABSOLUTE_MAX_TEXTURE_NUM_PIXELS = 8192 * 8192;
namespace ktx {
class KTX;
using KTXUniquePointer = std::unique_ptr<KTX>;

View file

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

View file

@ -19,7 +19,7 @@ void Baker::handleError(const QString& error) {
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
// add those to our list and emit that we are finished
_errorList.append(errors);

View file

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

View file

@ -207,8 +207,6 @@ void FBXBaker::importScene() {
importer->Destroy();
}
static const QString BAKED_TEXTURE_EXT = ".ktx";
QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) {
auto fbxPath = fbxURL.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();
auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/"));
#ifndef Q_OS_WIN
// it turns out that paths that start with a drive letter and a colon appear to QFileInfo
// as a relative path on UNIX systems - we perform a special check here to handle that case
bool isAbsolute = relativeFileName[1] == ':' || apparentRelativePath.isAbsolute();
#else
bool isAbsolute = apparentRelativePath.isAbsolute();
#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());
}
// this is a relative file path which will require different handling
// depending on the location of the original FBX
if (_fbxURL.isLocalFile() && 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 {
// simply construct a URL with the relative path to the asset, locally or remotely
// and append that to the list of unbaked textures
urlToTexture = _fbxURL.resolved(apparentRelativePath.filePath());
// 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());
}
}
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
// 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
auto textureType = textureTypeForMaterialProperty(property, material);
if (textureType != UNUSED_TEXTURE) {
if (textureType != gpu::UNUSED_TEXTURE) {
int numTextures = property.GetSrcObjectCount<FbxFileTexture>();
for (int j = 0; j < numTextures; j++) {
@ -401,7 +375,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
_unbakedTextures.insert(urlToTexture, bakedTextureFileName);
// 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
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);
QtConcurrent::run(bakingTexture.data(), &TextureBaker::bake);
// keep a shared pointer to the baking texture
_bakingTextures.insert(bakingTexture);
// start baking the texture on our thread pool
QtConcurrent::run(bakingTexture.data(), &TextureBaker::bake);
}
void FBXBaker::handleBakedTexture() {
TextureBaker* bakedTexture = qobject_cast<TextureBaker*>(sender());
// make sure we haven't already run into errors, and that this is a valid texture
if (!hasErrors() && bakedTexture) {
if (!bakedTexture->hasErrors()) {
// use the path to the texture being baked to determine if this was an embedded or a linked texture
if (bakedTexture) {
if (!hasErrors()) {
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
// since that is where the FBX SDK places the .fbm folder it generates when importing the FBX
// use the path to the texture being baked to determine if this was an embedded or a linked texture
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())) {
// for linked textures we want to save a copy of original texture beside the original FBX
auto originalOutputFolder = QUrl::fromLocalFile(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER);
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
auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL());
qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL();
QFile originalTextureFile {
_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + relativeTexturePath + bakedTexture->getTextureURL().fileName()
};
// check if we have a relative path to use for the texture
auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL());
if (relativeTexturePath.length() > 0) {
// make the folders needed by the relative path
QFile originalTextureFile {
_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()
<< "for" << _fbxURL;
} else {
handleError("Could not save original external texture " + originalTextureFile.fileName()
+ " for " + _fbxURL.toString());
return;
}
// now that this texture has been baked and handled, we can remove that TextureBaker from our list
_unbakedTextures.remove(bakedTexture->getTextureURL());
checkIfTexturesFinished();
} else {
// 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();
}
// now that this texture has been baked and handled, we can remove that TextureBaker from our list
} else {
// 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());
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() {
// check if we're done everything we need to do for this FBX
// and emit our finished signal if we're done
if (_unbakedTextures.isEmpty()) {
// remove the embedded media folder that the FBX SDK produces when reading the original
removeEmbeddedMediaFolder();
if (hasErrors()) {
return;
}
// cleanup the originals if we weren't asked to keep them around
possiblyCleanupOriginals();
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;
emit finished();
if (_pendingErrorEmission) {
emit finished();
}
return;
} else {
qCDebug(model_baking) << "Finished baking" << _fbxURL;
emit finished();
}
}
}

View file

@ -20,6 +20,8 @@
#include "Baker.h"
#include "TextureBaker.h"
#include <gpu/Texture.h>
namespace fbxsdk {
class FbxManager;
class FbxProperty;
@ -27,25 +29,6 @@ namespace fbxsdk {
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";
using FBXSDKManagerUniquePointer = std::unique_ptr<fbxsdk::FbxManager, std::function<void (fbxsdk::FbxManager *)>>;
@ -89,7 +72,7 @@ private:
QString createBakedTextureFileName(const QFileInfo& textureFileInfo);
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;
@ -103,18 +86,15 @@ private:
static FBXSDKManagerUniquePointer _sdkManager;
fbxsdk::FbxScene* _scene { nullptr };
QStringList _errorList;
QHash<QUrl, QString> _unbakedTextures;
QHash<QString, int> _textureNameMatchCount;
QHash<uint64_t, TextureType> _textureTypes;
QSet<QSharedPointer<TextureBaker>> _bakingTextures;
QFutureSynchronizer<void> _textureBakeSynchronizer;
bool _copyOriginals { true };
bool _finishedNonTextureOperations { false };
bool _pendingErrorEmission { false };
};
#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
//
#include <QtCore/QDir>
#include <QtCore/QEventLoop>
#include <QtCore/QFile>
#include <QtNetwork/QNetworkReply>
#include <image/Image.h>
#include <ktx/KTX.h>
#include <NetworkAccessManager.h>
#include "ModelBakingLoggingCategory.h"
#include "TextureBaker.h"
TextureBaker::TextureBaker(const QUrl& textureURL) :
_textureURL(textureURL)
const QString BAKED_TEXTURE_EXT = ".ktx";
TextureBaker::TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath) :
_textureURL(textureURL),
_textureType(textureType),
_destinationFilePath(destinationFilePath)
{
}
void TextureBaker::bake() {
@ -35,6 +42,14 @@ void TextureBaker::bake() {
qCDebug(model_baking) << "Baking texture at" << _textureURL;
processTexture();
if (hasErrors()) {
return;
}
qCDebug(model_baking) << "Baked texture at" << _textureURL;
emit finished();
}
@ -83,6 +98,33 @@ void TextureBaker::handleTextureNetworkReply(QNetworkReply* requestReply) {
_originalTexture = requestReply->readAll();
} else {
// 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/QUrl>
#include <QtCore/QRunnable>
#include <gpu/Texture.h>
#include "Baker.h"
extern const QString BAKED_TEXTURE_EXT;
class TextureBaker : public Baker {
Q_OBJECT
public:
TextureBaker(const QUrl& textureURL);
TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath);
void bake();
@ -33,8 +38,13 @@ private:
void loadTexture();
void handleTextureNetworkReply(QNetworkReply* requestReply);
void processTexture();
QUrl _textureURL;
QByteArray _originalTexture;
gpu::TextureType _textureType;
QString _destinationFilePath;
};
#endif // hifi_TextureBaker_h

View file

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

View file

@ -34,7 +34,7 @@ Q_LOGGING_CATEGORY(trace_simulation_physics_detail, "trace.simulation.physics.de
#endif
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) {

View file

@ -2,4 +2,4 @@ set(TARGET_NAME oven)
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() };
// 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
newModelURL.setQuery(oldModelURL.query());
@ -251,7 +251,7 @@ void DomainBaker::handleFinishedBaker() {
} else {
// 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
appendWarnings(baker->getErrors());
_warningList << baker->getErrors();
}
// 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
_browseStartDirectory.set(directoryOfEntitiesFile);
// if our output directory is not yet set, set it to the directory of this entities file
_outputDirLineEdit->setText(directoryOfEntitiesFile);
}
}