diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 9b23b4e695..7c6d5d0659 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -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; diff --git a/libraries/model-baking/CMakeLists.txt b/libraries/model-baking/CMakeLists.txt index a0774cdcc1..2e08488d69 100644 --- a/libraries/model-baking/CMakeLists.txt +++ b/libraries/model-baking/CMakeLists.txt @@ -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}) diff --git a/libraries/model-baking/src/Baker.cpp b/libraries/model-baking/src/Baker.cpp index b692c1d96b..666e59a073 100644 --- a/libraries/model-baking/src/Baker.cpp +++ b/libraries/model-baking/src/Baker.cpp @@ -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); diff --git a/libraries/model-baking/src/Baker.h b/libraries/model-baking/src/Baker.h index 6853620361..ab9c22ac53 100644 --- a/libraries/model-baking/src/Baker.h +++ b/libraries/model-baking/src/Baker.h @@ -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; diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index ff1d6659aa..0a25d3c299 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -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(); 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 bakingTexture { new TextureBaker(textureURL), &TextureBaker::deleteLater }; + QSharedPointer 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(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(); + } } } diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index ef94de7a2e..cca4878308 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -20,6 +20,8 @@ #include "Baker.h" #include "TextureBaker.h" +#include + 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>; @@ -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 _unbakedTextures; QHash _textureNameMatchCount; - QHash _textureTypes; QSet> _bakingTextures; QFutureSynchronizer _textureBakeSynchronizer; bool _copyOriginals { true }; - bool _finishedNonTextureOperations { false }; + bool _pendingErrorEmission { false }; }; #endif // hifi_FBXBaker_h diff --git a/libraries/model-baking/src/TextureBaker.cpp b/libraries/model-baking/src/TextureBaker.cpp index bdd20ea270..82f42a5776 100644 --- a/libraries/model-baking/src/TextureBaker.cpp +++ b/libraries/model-baking/src/TextureBaker.cpp @@ -9,20 +9,27 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include #include +#include +#include #include #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(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()); } } diff --git a/libraries/model-baking/src/TextureBaker.h b/libraries/model-baking/src/TextureBaker.h index 4f793af37d..17c725b57d 100644 --- a/libraries/model-baking/src/TextureBaker.h +++ b/libraries/model-baking/src/TextureBaker.h @@ -14,14 +14,19 @@ #include #include +#include + +#include #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 diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 1e61b9ecee..ade1acdb64 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -27,8 +27,6 @@ #include "KTXCache.h" -const int ABSOLUTE_MAX_TEXTURE_NUM_PIXELS = 8192 * 8192; - namespace gpu { class Batch; } diff --git a/libraries/shared/src/Profile.cpp b/libraries/shared/src/Profile.cpp index 7a8a8f0570..eb7440f4b3 100644 --- a/libraries/shared/src/Profile.cpp +++ b/libraries/shared/src/Profile.cpp @@ -34,7 +34,7 @@ Q_LOGGING_CATEGORY(trace_simulation_physics_detail, "trace.simulation.physics.de #endif static bool tracingEnabled() { - return DependencyManager::get()->isEnabled(); + return DependencyManager::isSet() && DependencyManager::get()->isEnabled(); } Duration::Duration(const QLoggingCategory& category, const QString& name, uint32_t argbColor, uint64_t payload, const QVariantMap& baseArgs) : _name(name), _category(category) { diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 2c5d0b98e5..1e644a2c62 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -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) diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index b53b74f227..6bee07986c 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -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 diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index 7c8c462cfd..c0b9c85910 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -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); } }