diff --git a/libraries/baking/CMakeLists.txt b/libraries/baking/CMakeLists.txt index cce76f152f..2fa4c86691 100644 --- a/libraries/baking/CMakeLists.txt +++ b/libraries/baking/CMakeLists.txt @@ -1,7 +1,7 @@ set(TARGET_NAME baking) setup_hifi_library(Concurrent) -link_hifi_libraries(shared graphics networking ktx image fbx) +link_hifi_libraries(shared shaders graphics networking material-networking ktx image fbx) include_hifi_library_headers(gpu) include_hifi_library_headers(hfm) diff --git a/libraries/baking/src/MaterialBaker.cpp b/libraries/baking/src/MaterialBaker.cpp new file mode 100644 index 0000000000..3427663b09 --- /dev/null +++ b/libraries/baking/src/MaterialBaker.cpp @@ -0,0 +1,118 @@ +// +// MaterialBaker.cpp +// libraries/baking/src +// +// Created by Sam Gondelman on 2/26/2019 +// Copyright 2019 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 "MaterialBaker.h" + +#include "QJsonObject" +#include "QJsonDocument" + +#include "MaterialBakingLoggingCategory.h" + +#include +#include + +MaterialBaker::MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir) : + _materialData(materialData), + _isURL(isURL), + _bakedOutputDir(bakedOutputDir) +{ +} + +void MaterialBaker::bake() { + qDebug(material_baking) << "Material Baker" << _materialData << "bake starting"; + + // once our script is loaded, kick off a the processing + connect(this, &MaterialBaker::originalMaterialLoaded, this, &MaterialBaker::processMaterial); + + if (!_materialResource) { + // first load the material (either locally or remotely) + loadMaterial(); + } else { + // we already have a material passed to us, use that + if (_materialResource->isLoaded()) { + emit originalMaterialLoaded(); + } else { + connect(_materialResource.data(), &Resource::finished, this, &MaterialBaker::originalMaterialLoaded); + } + } +} + +void MaterialBaker::loadMaterial() { + if (!_isURL) { + qCDebug(material_baking) << "Loading local material" << _materialData; + + _materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource()); + // TODO: add baseURL to allow these to reference relative files next to them + _materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromVariant(_materialData), QUrl()); + } else { + qCDebug(material_baking) << "Downloading material" << _materialData; + _materialResource = MaterialCache::instance().getMaterial(_materialData); + } + + if (_materialResource) { + if (_materialResource->isLoaded()) { + emit originalMaterialLoaded(); + } else { + connect(_materialResource.data(), &Resource::finished, this, &MaterialBaker::originalMaterialLoaded); + } + } else { + handleError("Error loading " + _materialData); + } +} + +void MaterialBaker::processMaterial() { + if (!_materialResource || _materialResource->parsedMaterials.networkMaterials.size() == 0) { + handleError("Error processing " + _materialData); + } + + _numTexturesToLoad = _materialResource->parsedMaterials.networkMaterials.size(); + _numTexturesLoaded = 0; + + for (auto networkMaterial : _materialResource->parsedMaterials.networkMaterials) { + if (networkMaterial.second) { + auto textureMaps = networkMaterial.second->getTextureMaps(); + for (auto textureMap : textureMaps) { + if (textureMap.second && textureMap.second->getTextureSource()) { + auto texture = textureMap.second->getTextureSource(); + graphics::Material::MapChannel mapChannel = textureMap.first; + + qDebug() << "boop" << mapChannel << texture->getUrl(); + } + } + } + } +} + +void MaterialBaker::outputMaterial() { + //if (_isURL) { + // auto fileName = _materialData; + // auto baseName = fileName.left(fileName.lastIndexOf('.')); + // auto bakedFilename = baseName + BAKED_MATERIAL_EXTENSION; + + // _bakedMaterialData = _bakedOutputDir + "/" + bakedFilename; + + // QFile bakedFile; + // bakedFile.setFileName(_bakedMaterialData); + // if (!bakedFile.open(QIODevice::WriteOnly)) { + // handleError("Error opening " + _bakedMaterialData + " for writing"); + // return; + // } + + // bakedFile.write(outputMaterial); + + // // Export successful + // _outputFiles.push_back(_bakedMaterialData); + // qCDebug(material_baking) << "Exported" << _materialData << "to" << _bakedMaterialData; + //} + + // emit signal to indicate the material baking is finished + emit finished(); +} diff --git a/libraries/baking/src/MaterialBaker.h b/libraries/baking/src/MaterialBaker.h new file mode 100644 index 0000000000..6113515b81 --- /dev/null +++ b/libraries/baking/src/MaterialBaker.h @@ -0,0 +1,58 @@ +// +// MaterialBaker.h +// libraries/baking/src +// +// Created by Sam Gondelman on 2/26/2019 +// Copyright 2019 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_MaterialBaker_h +#define hifi_MaterialBaker_h + +#include "Baker.h" + +#include "TextureBaker.h" + +#include + +static const QString BAKED_MATERIAL_EXTENSION = ".baked.json"; + +class MaterialBaker : public Baker { + Q_OBJECT +public: + MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir); + + QString getMaterialData() const { return _materialData; } + bool isURL() const { return _isURL; } + QString getBakedMaterialData() const { return _bakedMaterialData; } + +public slots: + virtual void bake() override; + +signals: + void originalMaterialLoaded(); + +private slots: + void processMaterial(); + void outputMaterial(); + +private: + void loadMaterial(); + + QString _materialData; + bool _isURL; + + NetworkMaterialResourcePointer _materialResource; + size_t _numTexturesToLoad { 0 }; + size_t _numTexturesLoaded { 0 }; + + QHash> _textureBakers; + + QString _bakedOutputDir; + QString _bakedMaterialData; +}; + +#endif // !hifi_MaterialBaker_h diff --git a/libraries/baking/src/MaterialBakingLoggingCategory.cpp b/libraries/baking/src/MaterialBakingLoggingCategory.cpp new file mode 100644 index 0000000000..75c0e6319c --- /dev/null +++ b/libraries/baking/src/MaterialBakingLoggingCategory.cpp @@ -0,0 +1,14 @@ +// +// MaterialBakingLoggingCategory.cpp +// libraries/baking/src +// +// Created by Sam Gondelman on 2/26/2019 +// Copyright 2019 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 "MaterialBakingLoggingCategory.h" + +Q_LOGGING_CATEGORY(material_baking, "hifi.material-baking"); diff --git a/libraries/baking/src/MaterialBakingLoggingCategory.h b/libraries/baking/src/MaterialBakingLoggingCategory.h new file mode 100644 index 0000000000..768bd9d769 --- /dev/null +++ b/libraries/baking/src/MaterialBakingLoggingCategory.h @@ -0,0 +1,19 @@ +// +// MaterialBakingLoggingCategory.h +// libraries/baking/src +// +// Created by Sam Gondelman on 2/26/2019 +// Copyright 2019 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_MaterialBakingLoggingCategory_h +#define hifi_MaterialBakingLoggingCategory_h + +#include + +Q_DECLARE_LOGGING_CATEGORY(material_baking) + +#endif // hifi_MaterialBakingLoggingCategory_h diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index 747788aef8..d2d330167d 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -96,6 +96,8 @@ namespace scriptable { bool defaultFallthrough; std::unordered_map propertyFallthroughs; // not actually exposed to script + + graphics::MaterialKey key { 0 }; }; /**jsdoc diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index 848f9d42ac..1fd7ad9df5 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -364,20 +364,81 @@ namespace scriptable { obj.setProperty("model", material.model); const QScriptValue FALLTHROUGH("fallthrough"); - obj.setProperty("opacity", material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_VAL_BIT) ? FALLTHROUGH : material.opacity); - obj.setProperty("roughness", material.propertyFallthroughs.at(graphics::MaterialKey::GLOSSY_VAL_BIT) ? FALLTHROUGH : material.roughness); - obj.setProperty("metallic", material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_VAL_BIT) ? FALLTHROUGH : material.metallic); - obj.setProperty("scattering", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_VAL_BIT) ? FALLTHROUGH : material.scattering); - obj.setProperty("unlit", material.propertyFallthroughs.at(graphics::MaterialKey::UNLIT_VAL_BIT) ? FALLTHROUGH : material.unlit); - obj.setProperty("emissive", material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_VAL_BIT) ? FALLTHROUGH : vec3ColorToScriptValue(engine, material.emissive)); - obj.setProperty("albedo", material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_VAL_BIT) ? FALLTHROUGH : vec3ColorToScriptValue(engine, material.albedo)); + if (material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_VAL_BIT)) { + obj.setProperty("opacity", FALLTHROUGH); + } else if (material.key.isTranslucentFactor()) { + obj.setProperty("opacity", material.opacity); + } - obj.setProperty("emissiveMap", material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_MAP_BIT) ? FALLTHROUGH : material.emissiveMap); - obj.setProperty("albedoMap", material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_MAP_BIT) ? FALLTHROUGH : material.albedoMap); - obj.setProperty("opacityMap", material.opacityMap); - obj.setProperty("occlusionMap", material.propertyFallthroughs.at(graphics::MaterialKey::OCCLUSION_MAP_BIT) ? FALLTHROUGH : material.occlusionMap); - obj.setProperty("lightmapMap", material.propertyFallthroughs.at(graphics::MaterialKey::LIGHTMAP_MAP_BIT) ? FALLTHROUGH : material.lightmapMap); - obj.setProperty("scatteringMap", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_MAP_BIT) ? FALLTHROUGH : material.scatteringMap); + if (material.propertyFallthroughs.at(graphics::MaterialKey::GLOSSY_VAL_BIT)) { + obj.setProperty("roughness", FALLTHROUGH); + } else if (material.key.isGlossy()) { + obj.setProperty("roughness", material.roughness); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_VAL_BIT)) { + obj.setProperty("metallic", FALLTHROUGH); + } else if (material.key.isMetallic()) { + obj.setProperty("metallic", material.metallic); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_VAL_BIT)) { + obj.setProperty("scattering", FALLTHROUGH); + } else if (material.key.isScattering()) { + obj.setProperty("scattering", material.scattering); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::UNLIT_VAL_BIT)) { + obj.setProperty("unlit", FALLTHROUGH); + } else if (material.key.isUnlit()) { + obj.setProperty("unlit", material.unlit); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_VAL_BIT)) { + obj.setProperty("emissive", FALLTHROUGH); + } else if (material.key.isEmissive()) { + obj.setProperty("emissive", vec3ColorToScriptValue(engine, material.emissive)); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_VAL_BIT)) { + obj.setProperty("albedo", FALLTHROUGH); + } else if (material.key.isAlbedo()) { + obj.setProperty("albedo", vec3ColorToScriptValue(engine, material.albedo)); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_MAP_BIT)) { + obj.setProperty("emissiveMap", FALLTHROUGH); + } else if (!material.emissiveMap.isEmpty()) { + obj.setProperty("emissiveMap", material.emissiveMap); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_MAP_BIT)) { + obj.setProperty("albedoMap", FALLTHROUGH); + } else if (!material.albedoMap.isEmpty()) { + obj.setProperty("albedoMap", material.albedoMap); + } + + if (!material.opacityMap.isEmpty()) { + obj.setProperty("opacityMap", material.opacityMap); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::OCCLUSION_MAP_BIT)) { + obj.setProperty("occlusionMap", FALLTHROUGH); + } else if (!material.occlusionMap.isEmpty()) { + obj.setProperty("occlusionMap", material.occlusionMap); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::LIGHTMAP_MAP_BIT)) { + obj.setProperty("lightmapMap", FALLTHROUGH); + } else if (!material.lightmapMap.isEmpty()) { + obj.setProperty("lightmapMap", material.lightmapMap); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_MAP_BIT)) { + obj.setProperty("scatteringMap", FALLTHROUGH); + } else if (!material.scatteringMap.isEmpty()) { + obj.setProperty("scatteringMap", material.scatteringMap); + } // Only set one of each of these if (material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_MAP_BIT)) { diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index 4ff751782c..fdd06ffa64 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -45,75 +45,80 @@ scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const defaultFallthrough = material.defaultFallthrough; propertyFallthroughs = material.propertyFallthroughs; + key = material.key; + return *this; } -scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPointer& material) : - name(material->getName().c_str()), - model(material->getModel().c_str()), - opacity(material->getOpacity()), - roughness(material->getRoughness()), - metallic(material->getMetallic()), - scattering(material->getScattering()), - unlit(material->isUnlit()), - emissive(material->getEmissive()), - albedo(material->getAlbedo()), - defaultFallthrough(material->getDefaultFallthrough()), - propertyFallthroughs(material->getPropertyFallthroughs()) -{ - auto map = material->getTextureMap(graphics::Material::MapChannel::EMISSIVE_MAP); - if (map && map->getTextureSource()) { - emissiveMap = map->getTextureSource()->getUrl().toString(); - } +scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPointer& material) { + if (material) { + name = material->getName().c_str(); + model = material->getModel().c_str(); + opacity = material->getOpacity(); + roughness = material->getRoughness(); + metallic = material->getMetallic(); + scattering = material->getScattering(); + unlit = material->isUnlit(); + emissive = material->getEmissive(); + albedo = material->getAlbedo(); + defaultFallthrough = material->getDefaultFallthrough(); + propertyFallthroughs = material->getPropertyFallthroughs(); + key = material->getKey(); - map = material->getTextureMap(graphics::Material::MapChannel::ALBEDO_MAP); - if (map && map->getTextureSource()) { - albedoMap = map->getTextureSource()->getUrl().toString(); - if (map->useAlphaChannel()) { - opacityMap = albedoMap; + auto map = material->getTextureMap(graphics::Material::MapChannel::EMISSIVE_MAP); + if (map && map->getTextureSource()) { + emissiveMap = map->getTextureSource()->getUrl().toString(); } - } - map = material->getTextureMap(graphics::Material::MapChannel::METALLIC_MAP); - if (map && map->getTextureSource()) { - if (map->getTextureSource()->getType() == image::TextureUsage::Type::METALLIC_TEXTURE) { - metallicMap = map->getTextureSource()->getUrl().toString(); - } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::SPECULAR_TEXTURE) { - specularMap = map->getTextureSource()->getUrl().toString(); + map = material->getTextureMap(graphics::Material::MapChannel::ALBEDO_MAP); + if (map && map->getTextureSource()) { + albedoMap = map->getTextureSource()->getUrl().toString(); + if (map->useAlphaChannel()) { + opacityMap = albedoMap; + } } - } - map = material->getTextureMap(graphics::Material::MapChannel::ROUGHNESS_MAP); - if (map && map->getTextureSource()) { - if (map->getTextureSource()->getType() == image::TextureUsage::Type::ROUGHNESS_TEXTURE) { - roughnessMap = map->getTextureSource()->getUrl().toString(); - } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::GLOSS_TEXTURE) { - glossMap = map->getTextureSource()->getUrl().toString(); + map = material->getTextureMap(graphics::Material::MapChannel::METALLIC_MAP); + if (map && map->getTextureSource()) { + if (map->getTextureSource()->getType() == image::TextureUsage::Type::METALLIC_TEXTURE) { + metallicMap = map->getTextureSource()->getUrl().toString(); + } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::SPECULAR_TEXTURE) { + specularMap = map->getTextureSource()->getUrl().toString(); + } } - } - map = material->getTextureMap(graphics::Material::MapChannel::NORMAL_MAP); - if (map && map->getTextureSource()) { - if (map->getTextureSource()->getType() == image::TextureUsage::Type::NORMAL_TEXTURE) { - normalMap = map->getTextureSource()->getUrl().toString(); - } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::BUMP_TEXTURE) { - bumpMap = map->getTextureSource()->getUrl().toString(); + map = material->getTextureMap(graphics::Material::MapChannel::ROUGHNESS_MAP); + if (map && map->getTextureSource()) { + if (map->getTextureSource()->getType() == image::TextureUsage::Type::ROUGHNESS_TEXTURE) { + roughnessMap = map->getTextureSource()->getUrl().toString(); + } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::GLOSS_TEXTURE) { + glossMap = map->getTextureSource()->getUrl().toString(); + } } - } - map = material->getTextureMap(graphics::Material::MapChannel::OCCLUSION_MAP); - if (map && map->getTextureSource()) { - occlusionMap = map->getTextureSource()->getUrl().toString(); - } + map = material->getTextureMap(graphics::Material::MapChannel::NORMAL_MAP); + if (map && map->getTextureSource()) { + if (map->getTextureSource()->getType() == image::TextureUsage::Type::NORMAL_TEXTURE) { + normalMap = map->getTextureSource()->getUrl().toString(); + } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::BUMP_TEXTURE) { + bumpMap = map->getTextureSource()->getUrl().toString(); + } + } - map = material->getTextureMap(graphics::Material::MapChannel::LIGHTMAP_MAP); - if (map && map->getTextureSource()) { - lightmapMap = map->getTextureSource()->getUrl().toString(); - } + map = material->getTextureMap(graphics::Material::MapChannel::OCCLUSION_MAP); + if (map && map->getTextureSource()) { + occlusionMap = map->getTextureSource()->getUrl().toString(); + } - map = material->getTextureMap(graphics::Material::MapChannel::SCATTERING_MAP); - if (map && map->getTextureSource()) { - scatteringMap = map->getTextureSource()->getUrl().toString(); + map = material->getTextureMap(graphics::Material::MapChannel::LIGHTMAP_MAP); + if (map && map->getTextureSource()) { + lightmapMap = map->getTextureSource()->getUrl().toString(); + } + + map = material->getTextureMap(graphics::Material::MapChannel::SCATTERING_MAP); + if (map && map->getTextureSource()) { + scatteringMap = map->getTextureSource()->getUrl().toString(); + } } } diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 022c9769fe..18ad37d7b9 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -2,7 +2,7 @@ set(TARGET_NAME oven) setup_hifi_project(Widgets Gui Concurrent) -link_hifi_libraries(networking shared image gpu ktx fbx hfm baking graphics) +link_hifi_libraries(shared shaders image gpu ktx fbx hfm baking graphics networking material-networking) setup_memory_debugger() diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index f5fffe6ea3..1aae6ccb72 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -23,6 +23,7 @@ #include "baking/BakerLibrary.h" #include "JSBaker.h" #include "TextureBaker.h" +#include "MaterialBaker.h" BakerCLI::BakerCLI(OvenCLIApplication* parent) : QObject(parent) { @@ -60,8 +61,8 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& _baker = std::unique_ptr { new JSBaker(inputUrl, outputPath) }; _baker->moveToThread(Oven::instance().getNextWorkerThread()); } else if (type == MATERIAL_EXTENSION) { - //_baker = std::unique_ptr { new MaterialBaker(inputUrl, outputPath) }; - //_baker->moveToThread(Oven::instance().getNextWorkerThread()); + _baker = std::unique_ptr { new MaterialBaker(inputUrl.toDisplayString(), true, outputPath) }; + _baker->moveToThread(Oven::instance().getNextWorkerThread()); } else { // If the type doesn't match the above, we assume we have a texture, and the type specified is the // texture usage type (albedo, cubemap, normals, etc.) diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index 2eb2c8a36b..ca5c9b85fe 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -187,8 +187,8 @@ void DomainBaker::addTextureBaker(const QString& property, const QString& url, i // setup a texture baker for this URL, as long as we aren't baking a texture already if (!_textureBakers.contains(textureURL)) { - // setup a baker for this texture + // setup a baker for this texture QSharedPointer textureBaker { new TextureBaker(textureURL, type, _contentOutputPath), &TextureBaker::deleteLater @@ -220,16 +220,16 @@ void DomainBaker::addScriptBaker(const QString& property, const QString& url, QJ // grab a clean version of the URL without a query or fragment QUrl scriptURL = QUrl(url).adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); - // setup a texture baker for this URL, as long as we aren't baking a texture already + // setup a script baker for this URL, as long as we aren't baking a texture already if (!_scriptBakers.contains(scriptURL)) { - // setup a baker for this texture + // setup a baker for this script QSharedPointer scriptBaker { new JSBaker(scriptURL, _contentOutputPath), &JSBaker::deleteLater }; - // make sure our handler is called when the texture baker is done + // make sure our handler is called when the script baker is done connect(scriptBaker.data(), &JSBaker::finished, this, &DomainBaker::handleFinishedScriptBaker); // insert it into our bakers hash so we hold a strong pointer to it @@ -243,11 +243,48 @@ void DomainBaker::addScriptBaker(const QString& property, const QString& url, QJ ++_totalNumberOfSubBakes; } - // add this QJsonValueRef to our multi hash so that it can re-write the texture URL + // add this QJsonValueRef to our multi hash so that it can re-write the script URL // to the baked version once the baker is complete _entitiesNeedingRewrite.insert(scriptURL, { property, jsonRef }); } +void DomainBaker::addMaterialBaker(const QString& property, const QString& data, bool isURL, QJsonValueRef& jsonRef) { + // grab a clean version of the URL without a query or fragment + QString materialData; + if (isURL) { + materialData = QUrl(data).adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment).toDisplayString(); + } else { + materialData = data; + } + + // setup a material baker for this URL, as long as we aren't baking a material already + if (!_materialBakers.contains(materialData)) { + + // setup a baker for this material + QSharedPointer materialBaker { + new MaterialBaker(data, isURL, _contentOutputPath), + &MaterialBaker::deleteLater + }; + + // make sure our handler is called when the material baker is done + connect(materialBaker.data(), &MaterialBaker::finished, this, &DomainBaker::handleFinishedMaterialBaker); + + // insert it into our bakers hash so we hold a strong pointer to it + _materialBakers.insert(materialData, materialBaker); + + // move the baker to a worker thread and kickoff the bake + materialBaker->moveToThread(Oven::instance().getNextWorkerThread()); + QMetaObject::invokeMethod(materialBaker.data(), "bake"); + + // keep track of the total number of baking entities + ++_totalNumberOfSubBakes; + } + + // add this QJsonValueRef to our multi hash so that it can re-write the texture URL + // to the baked version once the baker is complete + _entitiesNeedingRewrite.insert(materialData, { property, jsonRef }); +} + // All the Entity Properties that can be baked // *************************************************************************************** @@ -348,7 +385,12 @@ void DomainBaker::enumerateEntities() { } // Materials - // TODO + if (entity.contains(MATERIAL_URL_KEY)) { + addMaterialBaker(MATERIAL_URL_KEY, entity[MATERIAL_URL_KEY].toString(), true, *it); + } + if (entity.contains(MATERIAL_DATA_KEY)) { + addMaterialBaker(MATERIAL_DATA_KEY, entity[MATERIAL_URL_KEY].toString(), false, *it); + } } } @@ -568,6 +610,91 @@ void DomainBaker::handleFinishedScriptBaker() { } } +void DomainBaker::handleFinishedMaterialBaker() { + auto baker = qobject_cast(sender()); + + if (baker) { + if (!baker->hasErrors()) { + // this MaterialBaker is done and everything went according to plan + qDebug() << "Re-writing entity references to" << baker->getMaterialData(); + + QString newDataOrURL; + if (baker->isURL()) { + newDataOrURL = _destinationPath.resolved(baker->getBakedMaterialData()).toDisplayString(); + } else { + newDataOrURL = baker->getBakedMaterialData(); + } + + // enumerate the QJsonRef values for the URL of this material from our multi hash of + // entity objects needing a URL re-write + for (auto propertyEntityPair : _entitiesNeedingRewrite.values(baker->getMaterialData())) { + QString property = propertyEntityPair.first; + // convert the entity QJsonValueRef to a QJsonObject so we can modify its URL + auto entity = propertyEntityPair.second.toObject(); + + if (!property.contains(".")) { + // grab the old URL + QUrl oldURL = entity[property].toString(); + + // copy the fragment and query, and user info from the old material data + if (baker->isURL()) { + QUrl newURL = newDataOrURL; + newURL.setQuery(oldURL.query()); + newURL.setFragment(oldURL.fragment()); + newURL.setUserInfo(oldURL.userInfo()); + + // set the new URL as the value in our temp QJsonObject + entity[property] = newURL.toString(); + } else { + entity[property] = newDataOrURL; + } + } else { + // Group property + QStringList propertySplit = property.split("."); + assert(propertySplit.length() == 2); + // grab the old URL + auto oldObject = entity[propertySplit[0]].toObject(); + QUrl oldURL = oldObject[propertySplit[1]].toString(); + + // copy the fragment and query, and user info from the old material data + if (baker->isURL()) { + QUrl newURL = newDataOrURL; + newURL.setQuery(oldURL.query()); + newURL.setFragment(oldURL.fragment()); + newURL.setUserInfo(oldURL.userInfo()); + + // set the new URL as the value in our temp QJsonObject + oldObject[propertySplit[1]] = newURL.toString(); + entity[propertySplit[0]] = oldObject; + } else { + oldObject[propertySplit[1]] = newDataOrURL; + entity[propertySplit[0]] = oldObject; + } + } + + // replace our temp object with the value referenced by our QJsonValueRef + propertyEntityPair.second = entity; + } + } else { + // this material failed to bake - this doesn't fail the entire bake but we need to add + // the errors from the material to our warnings + _warningList << baker->getErrors(); + } + + // remove the baked URL from the multi hash of entities needing a re-write + _entitiesNeedingRewrite.remove(baker->getMaterialData()); + + // drop our shared pointer to this baker so that it gets cleaned up + _materialBakers.remove(baker->getMaterialData()); + + // emit progress to tell listeners how many materials we have baked + emit bakeProgress(++_completedSubBakes, _totalNumberOfSubBakes); + + // check if this was the last material we needed to re-write and if we are done now + checkIfRewritingComplete(); + } +} + void DomainBaker::checkIfRewritingComplete() { if (_entitiesNeedingRewrite.isEmpty()) { writeNewEntitiesFile(); diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index 2a9522143e..4504d5b8fa 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -21,6 +21,7 @@ #include "ModelBaker.h" #include "TextureBaker.h" #include "JSBaker.h" +#include "MaterialBaker.h" class DomainBaker : public Baker { Q_OBJECT @@ -40,6 +41,7 @@ private slots: void handleFinishedModelBaker(); void handleFinishedTextureBaker(); void handleFinishedScriptBaker(); + void handleFinishedMaterialBaker(); private: void setupOutputFolder(); @@ -62,6 +64,7 @@ private: QHash> _modelBakers; QHash> _textureBakers; QHash> _scriptBakers; + QHash> _materialBakers; QMultiHash> _entitiesNeedingRewrite; @@ -71,6 +74,7 @@ private: void addModelBaker(const QString& property, const QString& url, QJsonValueRef& jsonRef); void addTextureBaker(const QString& property, const QString& url, image::TextureUsage::Type type, QJsonValueRef& jsonRef); void addScriptBaker(const QString& property, const QString& url, QJsonValueRef& jsonRef); + void addMaterialBaker(const QString& property, const QString& data, bool isURL, QJsonValueRef& jsonRef); }; #endif // hifi_DomainBaker_h diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index af98376034..6fdc45f6eb 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -63,6 +63,10 @@ void Oven::setupWorkerThreads(int numWorkerThreads) { } QThread* Oven::getNextWorkerThread() { + // FIXME: we assign these threads when we make the bakers, but if certain bakers finish quickly, we could end up + // in a situation where threads have finished and others have tons of work queued. Instead of assigning them at initialization, + // we should build a queue of bakers, and when threads finish, they can take the next available baker. + // Here we replicate some of the functionality of QThreadPool by giving callers an available worker thread to use. // We can't use QThreadPool because we want to put QObjects with signals/slots on these threads. // So instead we setup our own list of threads, up to one less than the ideal thread count