Unified · Split
| @@ -1,7 +1,7 @@ | |||
| 1 | set(TARGET_NAME baking) | 1 | set(TARGET_NAME baking) |
| 2 | setup_hifi_library(Concurrent) | 2 | setup_hifi_library(Concurrent) |
| 3 | 3 | ||
| 4 | link_hifi_libraries(shared graphics networking ktx image fbx) | 4 | link_hifi_libraries(shared shaders graphics networking material-networking ktx image fbx) |
| 5 | include_hifi_library_headers(gpu) | 5 | include_hifi_library_headers(gpu) |
| 6 | include_hifi_library_headers(hfm) | 6 | include_hifi_library_headers(hfm) |
| 7 | 7 | ||
| @@ -0,0 +1,118 @@ | |||
| 1 | // | ||
| 2 | // MaterialBaker.cpp | ||
| 3 | // libraries/baking/src | ||
| 4 | // | ||
| 5 | // Created by Sam Gondelman on 2/26/2019 | ||
| 6 | // Copyright 2019 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | |||
| 12 | #include "MaterialBaker.h" | ||
| 13 | |||
| 14 | #include "QJsonObject" | ||
| 15 | #include "QJsonDocument" | ||
| 16 | |||
| 17 | #include "MaterialBakingLoggingCategory.h" | ||
| 18 | |||
| 19 | #include <SharedUtil.h> | ||
| 20 | #include <PathUtils.h> | ||
| 21 | |||
| 22 | MaterialBaker::MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir) : | ||
| 23 | _materialData(materialData), | ||
| 24 | _isURL(isURL), | ||
| 25 | _bakedOutputDir(bakedOutputDir) | ||
| 26 | { | ||
| 27 | } | ||
| 28 | |||
| 29 | void MaterialBaker::bake() { | ||
| 30 | qDebug(material_baking) << "Material Baker" << _materialData << "bake starting"; | ||
| 31 | |||
| 32 | // once our script is loaded, kick off a the processing | ||
| 33 | connect(this, &MaterialBaker::originalMaterialLoaded, this, &MaterialBaker::processMaterial); | ||
| 34 | |||
| 35 | if (!_materialResource) { | ||
| 36 | // first load the material (either locally or remotely) | ||
| 37 | loadMaterial(); | ||
| 38 | } else { | ||
| 39 | // we already have a material passed to us, use that | ||
| 40 | if (_materialResource->isLoaded()) { | ||
| 41 | emit originalMaterialLoaded(); | ||
| 42 | } else { | ||
| 43 | connect(_materialResource.data(), &Resource::finished, this, &MaterialBaker::originalMaterialLoaded); | ||
| 44 | } | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | void MaterialBaker::loadMaterial() { | ||
| 49 | if (!_isURL) { | ||
| 50 | qCDebug(material_baking) << "Loading local material" << _materialData; | ||
| 51 | |||
| 52 | _materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource()); | ||
| 53 | // TODO: add baseURL to allow these to reference relative files next to them | ||
| 54 | _materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromVariant(_materialData), QUrl()); | ||
| 55 | } else { | ||
| 56 | qCDebug(material_baking) << "Downloading material" << _materialData; | ||
| 57 | _materialResource = MaterialCache::instance().getMaterial(_materialData); | ||
| 58 | } | ||
| 59 | |||
| 60 | if (_materialResource) { | ||
| 61 | if (_materialResource->isLoaded()) { | ||
| 62 | emit originalMaterialLoaded(); | ||
| 63 | } else { | ||
| 64 | connect(_materialResource.data(), &Resource::finished, this, &MaterialBaker::originalMaterialLoaded); | ||
| 65 | } | ||
| 66 | } else { | ||
| 67 | handleError("Error loading " + _materialData); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | void MaterialBaker::processMaterial() { | ||
| 72 | if (!_materialResource || _materialResource->parsedMaterials.networkMaterials.size() == 0) { | ||
| 73 | handleError("Error processing " + _materialData); | ||
| 74 | } | ||
| 75 | |||
| 76 | _numTexturesToLoad = _materialResource->parsedMaterials.networkMaterials.size(); | ||
| 77 | _numTexturesLoaded = 0; | ||
| 78 | |||
| 79 | for (auto networkMaterial : _materialResource->parsedMaterials.networkMaterials) { | ||
| 80 | if (networkMaterial.second) { | ||
| 81 | auto textureMaps = networkMaterial.second->getTextureMaps(); | ||
| 82 | for (auto textureMap : textureMaps) { | ||
| 83 | if (textureMap.second && textureMap.second->getTextureSource()) { | ||
| 84 | auto texture = textureMap.second->getTextureSource(); | ||
| 85 | graphics::Material::MapChannel mapChannel = textureMap.first; | ||
| 86 | |||
| 87 | qDebug() << "boop" << mapChannel << texture->getUrl(); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | void MaterialBaker::outputMaterial() { | ||
| 95 | //if (_isURL) { | ||
| 96 | // auto fileName = _materialData; | ||
| 97 | // auto baseName = fileName.left(fileName.lastIndexOf('.')); | ||
| 98 | // auto bakedFilename = baseName + BAKED_MATERIAL_EXTENSION; | ||
| 99 | |||
| 100 | // _bakedMaterialData = _bakedOutputDir + "/" + bakedFilename; | ||
| 101 | |||
| 102 | // QFile bakedFile; | ||
| 103 | // bakedFile.setFileName(_bakedMaterialData); | ||
| 104 | // if (!bakedFile.open(QIODevice::WriteOnly)) { | ||
| 105 | // handleError("Error opening " + _bakedMaterialData + " for writing"); | ||
| 106 | // return; | ||
| 107 | // } | ||
| 108 | |||
| 109 | // bakedFile.write(outputMaterial); | ||
| 110 | |||
| 111 | // // Export successful | ||
| 112 | // _outputFiles.push_back(_bakedMaterialData); | ||
| 113 | // qCDebug(material_baking) << "Exported" << _materialData << "to" << _bakedMaterialData; | ||
| 114 | //} | ||
| 115 | |||
| 116 | // emit signal to indicate the material baking is finished | ||
| 117 | emit finished(); | ||
| 118 | } | ||
| @@ -0,0 +1,58 @@ | |||
| 1 | // | ||
| 2 | // MaterialBaker.h | ||
| 3 | // libraries/baking/src | ||
| 4 | // | ||
| 5 | // Created by Sam Gondelman on 2/26/2019 | ||
| 6 | // Copyright 2019 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | |||
| 12 | #ifndef hifi_MaterialBaker_h | ||
| 13 | #define hifi_MaterialBaker_h | ||
| 14 | |||
| 15 | #include "Baker.h" | ||
| 16 | |||
| 17 | #include "TextureBaker.h" | ||
| 18 | |||
| 19 | #include <material-networking/MaterialCache.h> | ||
| 20 | |||
| 21 | static const QString BAKED_MATERIAL_EXTENSION = ".baked.json"; | ||
| 22 | |||
| 23 | class MaterialBaker : public Baker { | ||
| 24 | Q_OBJECT | ||
| 25 | public: | ||
| 26 | MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir); | ||
| 27 | |||
| 28 | QString getMaterialData() const { return _materialData; } | ||
| 29 | bool isURL() const { return _isURL; } | ||
| 30 | QString getBakedMaterialData() const { return _bakedMaterialData; } | ||
| 31 | |||
| 32 | public slots: | ||
| 33 | virtual void bake() override; | ||
| 34 | |||
| 35 | signals: | ||
| 36 | void originalMaterialLoaded(); | ||
| 37 | |||
| 38 | private slots: | ||
| 39 | void processMaterial(); | ||
| 40 | void outputMaterial(); | ||
| 41 | |||
| 42 | private: | ||
| 43 | void loadMaterial(); | ||
| 44 | |||
| 45 | QString _materialData; | ||
| 46 | bool _isURL; | ||
| 47 | |||
| 48 | NetworkMaterialResourcePointer _materialResource; | ||
| 49 | size_t _numTexturesToLoad { 0 }; | ||
| 50 | size_t _numTexturesLoaded { 0 }; | ||
| 51 | |||
| 52 | QHash<QUrl, QSharedPointer<TextureBaker>> _textureBakers; | ||
| 53 | |||
| 54 | QString _bakedOutputDir; | ||
| 55 | QString _bakedMaterialData; | ||
| 56 | }; | ||
| 57 | |||
| 58 | #endif // !hifi_MaterialBaker_h | ||
| @@ -0,0 +1,14 @@ | |||
| 1 | // | ||
| 2 | // MaterialBakingLoggingCategory.cpp | ||
| 3 | // libraries/baking/src | ||
| 4 | // | ||
| 5 | // Created by Sam Gondelman on 2/26/2019 | ||
| 6 | // Copyright 2019 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | |||
| 12 | #include "MaterialBakingLoggingCategory.h" | ||
| 13 | |||
| 14 | Q_LOGGING_CATEGORY(material_baking, "hifi.material-baking"); | ||
| @@ -0,0 +1,19 @@ | |||
| 1 | // | ||
| 2 | // MaterialBakingLoggingCategory.h | ||
| 3 | // libraries/baking/src | ||
| 4 | // | ||
| 5 | // Created by Sam Gondelman on 2/26/2019 | ||
| 6 | // Copyright 2019 High Fidelity, Inc. | ||
| 7 | // | ||
| 8 | // Distributed under the Apache License, Version 2.0. | ||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | ||
| 10 | // | ||
| 11 | |||
| 12 | #ifndef hifi_MaterialBakingLoggingCategory_h | ||
| 13 | #define hifi_MaterialBakingLoggingCategory_h | ||
| 14 | |||
| 15 | #include <QtCore/QLoggingCategory> | ||
| 16 | |||
| 17 | Q_DECLARE_LOGGING_CATEGORY(material_baking) | ||
| 18 | |||
| 19 | #endif // hifi_MaterialBakingLoggingCategory_h | ||
| @@ -96,6 +96,8 @@ namespace scriptable { · 95 unmodified lines | |||
| 96 | 96 | ||
| 97 | bool defaultFallthrough; | 97 | bool defaultFallthrough; |
| 98 | std::unordered_map<uint, bool> propertyFallthroughs; // not actually exposed to script | 98 | std::unordered_map<uint, bool> propertyFallthroughs; // not actually exposed to script |
| 99 | |||
| 100 | graphics::MaterialKey key { 0 }; | ||
| 99 | }; | 101 | }; |
| 100 | 102 | ||
| 101 | /**jsdoc | 103 | /**jsdoc |
| @@ -364,20 +364,81 @@ namespace scriptable { · 363 unmodified lines | |||
| 364 | obj.setProperty("model", material.model); | 364 | obj.setProperty("model", material.model); |
| 365 | 365 | ||
| 366 | const QScriptValue FALLTHROUGH("fallthrough"); | 366 | const QScriptValue FALLTHROUGH("fallthrough"); |
| 367 | obj.setProperty("opacity", material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_VAL_BIT) ? FALLTHROUGH : material.opacity); | 367 | if (material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_VAL_BIT)) { |
| 368 | obj.setProperty("roughness", material.propertyFallthroughs.at(graphics::MaterialKey::GLOSSY_VAL_BIT) ? FALLTHROUGH : material.roughness); | 368 | obj.setProperty("opacity", FALLTHROUGH); |
| 369 | obj.setProperty("metallic", material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_VAL_BIT) ? FALLTHROUGH : material.metallic); | 369 | } else if (material.key.isTranslucentFactor()) { |
| 370 | obj.setProperty("scattering", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_VAL_BIT) ? FALLTHROUGH : material.scattering); | 370 | obj.setProperty("opacity", material.opacity); |
| 371 | obj.setProperty("unlit", material.propertyFallthroughs.at(graphics::MaterialKey::UNLIT_VAL_BIT) ? FALLTHROUGH : material.unlit); | 371 | } |
| 372 | obj.setProperty("emissive", material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_VAL_BIT) ? FALLTHROUGH : vec3ColorToScriptValue(engine, material.emissive)); | 372 | |
| 373 | obj.setProperty("albedo", material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_VAL_BIT) ? FALLTHROUGH : vec3ColorToScriptValue(engine, material.albedo)); | 373 | if (material.propertyFallthroughs.at(graphics::MaterialKey::GLOSSY_VAL_BIT)) { |
| 374 | 374 | obj.setProperty("roughness", FALLTHROUGH); | |
| 375 | obj.setProperty("emissiveMap", material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_MAP_BIT) ? FALLTHROUGH : material.emissiveMap); | 375 | } else if (material.key.isGlossy()) { |
| 376 | obj.setProperty("albedoMap", material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_MAP_BIT) ? FALLTHROUGH : material.albedoMap); | 376 | obj.setProperty("roughness", material.roughness); |
| 377 | obj.setProperty("opacityMap", material.opacityMap); | 377 | } |
| 378 | obj.setProperty("occlusionMap", material.propertyFallthroughs.at(graphics::MaterialKey::OCCLUSION_MAP_BIT) ? FALLTHROUGH : material.occlusionMap); | 378 | |
| 379 | obj.setProperty("lightmapMap", material.propertyFallthroughs.at(graphics::MaterialKey::LIGHTMAP_MAP_BIT) ? FALLTHROUGH : material.lightmapMap); | 379 | if (material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_VAL_BIT)) { |
| 380 | obj.setProperty("scatteringMap", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_MAP_BIT) ? FALLTHROUGH : material.scatteringMap); | 380 | obj.setProperty("metallic", FALLTHROUGH); |
| 381 | } else if (material.key.isMetallic()) { | ||
| 382 | obj.setProperty("metallic", material.metallic); | ||
| 383 | } | ||
| 384 | |||
| 385 | if (material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_VAL_BIT)) { | ||
| 386 | obj.setProperty("scattering", FALLTHROUGH); | ||
| 387 | } else if (material.key.isScattering()) { | ||
| 388 | obj.setProperty("scattering", material.scattering); | ||
| 389 | } | ||
| 390 | |||
| 391 | if (material.propertyFallthroughs.at(graphics::MaterialKey::UNLIT_VAL_BIT)) { | ||
| 392 | obj.setProperty("unlit", FALLTHROUGH); | ||
| 393 | } else if (material.key.isUnlit()) { | ||
| 394 | obj.setProperty("unlit", material.unlit); | ||
| 395 | } | ||
| 396 | |||
| 397 | if (material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_VAL_BIT)) { | ||
| 398 | obj.setProperty("emissive", FALLTHROUGH); | ||
| 399 | } else if (material.key.isEmissive()) { | ||
| 400 | obj.setProperty("emissive", vec3ColorToScriptValue(engine, material.emissive)); | ||
| 401 | } | ||
| 402 | |||
| 403 | if (material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_VAL_BIT)) { | ||
| 404 | obj.setProperty("albedo", FALLTHROUGH); | ||
| 405 | } else if (material.key.isAlbedo()) { | ||
| 406 | obj.setProperty("albedo", vec3ColorToScriptValue(engine, material.albedo)); | ||
| 407 | } | ||
| 408 | |||
| 409 | if (material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_MAP_BIT)) { | ||
| 410 | obj.setProperty("emissiveMap", FALLTHROUGH); | ||
| 411 | } else if (!material.emissiveMap.isEmpty()) { | ||
| 412 | obj.setProperty("emissiveMap", material.emissiveMap); | ||
| 413 | } | ||
| 414 | |||
| 415 | if (material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_MAP_BIT)) { | ||
| 416 | obj.setProperty("albedoMap", FALLTHROUGH); | ||
| 417 | } else if (!material.albedoMap.isEmpty()) { | ||
| 418 | obj.setProperty("albedoMap", material.albedoMap); | ||
| 419 | } | ||
| 420 | |||
| 421 | if (!material.opacityMap.isEmpty()) { | ||
| 422 | obj.setProperty("opacityMap", material.opacityMap); | ||
| 423 | } | ||
| 424 | |||
| 425 | if (material.propertyFallthroughs.at(graphics::MaterialKey::OCCLUSION_MAP_BIT)) { | ||
| 426 | obj.setProperty("occlusionMap", FALLTHROUGH); | ||
| 427 | } else if (!material.occlusionMap.isEmpty()) { | ||
| 428 | obj.setProperty("occlusionMap", material.occlusionMap); | ||
| 429 | } | ||
| 430 | |||
| 431 | if (material.propertyFallthroughs.at(graphics::MaterialKey::LIGHTMAP_MAP_BIT)) { | ||
| 432 | obj.setProperty("lightmapMap", FALLTHROUGH); | ||
| 433 | } else if (!material.lightmapMap.isEmpty()) { | ||
| 434 | obj.setProperty("lightmapMap", material.lightmapMap); | ||
| 435 | } | ||
| 436 | |||
| 437 | if (material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_MAP_BIT)) { | ||
| 438 | obj.setProperty("scatteringMap", FALLTHROUGH); | ||
| 439 | } else if (!material.scatteringMap.isEmpty()) { | ||
| 440 | obj.setProperty("scatteringMap", material.scatteringMap); | ||
| 441 | } | ||
| 381 | 442 | ||
| 382 | // Only set one of each of these | 443 | // Only set one of each of these |
| 383 | if (material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_MAP_BIT)) { | 444 | if (material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_MAP_BIT)) { |
| @@ -45,75 +45,80 @@ scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const · 44 unmodified lines | |||
| 45 | defaultFallthrough = material.defaultFallthrough; | 45 | defaultFallthrough = material.defaultFallthrough; |
| 46 | propertyFallthroughs = material.propertyFallthroughs; | 46 | propertyFallthroughs = material.propertyFallthroughs; |
| 47 | 47 | ||
| 48 | key = material.key; | ||
| 49 | |||
| 48 | return *this; | 50 | return *this; |
| 49 | } | 51 | } |
| 50 | 52 | ||
| 51 | scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPointer& material) : | 53 | scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPointer& material) { |
| 52 | name(material->getName().c_str()), | 54 | if (material) { |
| 53 | model(material->getModel().c_str()), | 55 | name = material->getName().c_str(); |
| 54 | opacity(material->getOpacity()), | 56 | model = material->getModel().c_str(); |
| 55 | roughness(material->getRoughness()), | 57 | opacity = material->getOpacity(); |
| 56 | metallic(material->getMetallic()), | 58 | roughness = material->getRoughness(); |
| 57 | scattering(material->getScattering()), | 59 | metallic = material->getMetallic(); |
| 58 | unlit(material->isUnlit()), | 60 | scattering = material->getScattering(); |
| 59 | emissive(material->getEmissive()), | 61 | unlit = material->isUnlit(); |
| 60 | albedo(material->getAlbedo()), | 62 | emissive = material->getEmissive(); |
| 61 | defaultFallthrough(material->getDefaultFallthrough()), | 63 | albedo = material->getAlbedo(); |
| 62 | propertyFallthroughs(material->getPropertyFallthroughs()) | 64 | defaultFallthrough = material->getDefaultFallthrough(); |
| 63 | { | 65 | propertyFallthroughs = material->getPropertyFallthroughs(); |
| 64 | auto map = material->getTextureMap(graphics::Material::MapChannel::EMISSIVE_MAP); | 66 | key = material->getKey(); |
| 65 | if (map && map->getTextureSource()) { | 67 | |
| 66 | emissiveMap = map->getTextureSource()->getUrl().toString(); | 68 | auto map = material->getTextureMap(graphics::Material::MapChannel::EMISSIVE_MAP); |
| 67 | } | 69 | if (map && map->getTextureSource()) { |
| 70 | emissiveMap = map->getTextureSource()->getUrl().toString(); | ||
| 71 | } | ||
| 68 | 72 | ||
| 69 | map = material->getTextureMap(graphics::Material::MapChannel::ALBEDO_MAP); | 73 | map = material->getTextureMap(graphics::Material::MapChannel::ALBEDO_MAP); |
| 70 | if (map && map->getTextureSource()) { | 74 | if (map && map->getTextureSource()) { |
| 71 | albedoMap = map->getTextureSource()->getUrl().toString(); | 75 | albedoMap = map->getTextureSource()->getUrl().toString(); |
| 72 | if (map->useAlphaChannel()) { | 76 | if (map->useAlphaChannel()) { |
| 73 | opacityMap = albedoMap; | 77 | opacityMap = albedoMap; |
| 78 | } | ||
| 74 | } | 79 | } |
| 75 | } | ||
| 76 | 80 | ||
| 77 | map = material->getTextureMap(graphics::Material::MapChannel::METALLIC_MAP); | 81 | map = material->getTextureMap(graphics::Material::MapChannel::METALLIC_MAP); |
| 78 | if (map && map->getTextureSource()) { | 82 | if (map && map->getTextureSource()) { |
| 79 | if (map->getTextureSource()->getType() == image::TextureUsage::Type::METALLIC_TEXTURE) { | 83 | if (map->getTextureSource()->getType() == image::TextureUsage::Type::METALLIC_TEXTURE) { |
| 80 | metallicMap = map->getTextureSource()->getUrl().toString(); | 84 | metallicMap = map->getTextureSource()->getUrl().toString(); |
| 81 | } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::SPECULAR_TEXTURE) { | 85 | } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::SPECULAR_TEXTURE) { |
| 82 | specularMap = map->getTextureSource()->getUrl().toString(); | 86 | specularMap = map->getTextureSource()->getUrl().toString(); |
| 87 | } | ||
| 83 | } | 88 | } |
| 84 | } | ||
| 85 | 89 | ||
| 86 | map = material->getTextureMap(graphics::Material::MapChannel::ROUGHNESS_MAP); | 90 | map = material->getTextureMap(graphics::Material::MapChannel::ROUGHNESS_MAP); |
| 87 | if (map && map->getTextureSource()) { | 91 | if (map && map->getTextureSource()) { |
| 88 | if (map->getTextureSource()->getType() == image::TextureUsage::Type::ROUGHNESS_TEXTURE) { | 92 | if (map->getTextureSource()->getType() == image::TextureUsage::Type::ROUGHNESS_TEXTURE) { |
| 89 | roughnessMap = map->getTextureSource()->getUrl().toString(); | 93 | roughnessMap = map->getTextureSource()->getUrl().toString(); |
| 90 | } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::GLOSS_TEXTURE) { | 94 | } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::GLOSS_TEXTURE) { |
| 91 | glossMap = map->getTextureSource()->getUrl().toString(); | 95 | glossMap = map->getTextureSource()->getUrl().toString(); |
| 96 | } | ||
| 92 | } | 97 | } |
| 93 | } | ||
| 94 | 98 | ||
| 95 | map = material->getTextureMap(graphics::Material::MapChannel::NORMAL_MAP); | 99 | map = material->getTextureMap(graphics::Material::MapChannel::NORMAL_MAP); |
| 96 | if (map && map->getTextureSource()) { | 100 | if (map && map->getTextureSource()) { |
| 97 | if (map->getTextureSource()->getType() == image::TextureUsage::Type::NORMAL_TEXTURE) { | 101 | if (map->getTextureSource()->getType() == image::TextureUsage::Type::NORMAL_TEXTURE) { |
| 98 | normalMap = map->getTextureSource()->getUrl().toString(); | 102 | normalMap = map->getTextureSource()->getUrl().toString(); |
| 99 | } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::BUMP_TEXTURE) { | 103 | } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::BUMP_TEXTURE) { |
| 100 | bumpMap = map->getTextureSource()->getUrl().toString(); | 104 | bumpMap = map->getTextureSource()->getUrl().toString(); |
| 105 | } | ||
| 101 | } | 106 | } |
| 102 | } | ||
| 103 | 107 | ||
| 104 | map = material->getTextureMap(graphics::Material::MapChannel::OCCLUSION_MAP); | 108 | map = material->getTextureMap(graphics::Material::MapChannel::OCCLUSION_MAP); |
| 105 | if (map && map->getTextureSource()) { | 109 | if (map && map->getTextureSource()) { |
| 106 | occlusionMap = map->getTextureSource()->getUrl().toString(); | 110 | occlusionMap = map->getTextureSource()->getUrl().toString(); |
| 107 | } | 111 | } |
| 108 | 112 | ||
| 109 | map = material->getTextureMap(graphics::Material::MapChannel::LIGHTMAP_MAP); | 113 | map = material->getTextureMap(graphics::Material::MapChannel::LIGHTMAP_MAP); |
| 110 | if (map && map->getTextureSource()) { | 114 | if (map && map->getTextureSource()) { |
| 111 | lightmapMap = map->getTextureSource()->getUrl().toString(); | 115 | lightmapMap = map->getTextureSource()->getUrl().toString(); |
| 112 | } | 116 | } |
| 113 | 117 | ||
| 114 | map = material->getTextureMap(graphics::Material::MapChannel::SCATTERING_MAP); | 118 | map = material->getTextureMap(graphics::Material::MapChannel::SCATTERING_MAP); |
| 115 | if (map && map->getTextureSource()) { | 119 | if (map && map->getTextureSource()) { |
| 116 | scatteringMap = map->getTextureSource()->getUrl().toString(); | 120 | scatteringMap = map->getTextureSource()->getUrl().toString(); |
| 121 | } | ||
| 117 | } | 122 | } |
| 118 | } | 123 | } |
| 119 | 124 | ||
| @@ -2,7 +2,7 @@ set(TARGET_NAME oven) · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | setup_hifi_project(Widgets Gui Concurrent) | 3 | setup_hifi_project(Widgets Gui Concurrent) |
| 4 | 4 | ||
| 5 | link_hifi_libraries(networking shared image gpu ktx fbx hfm baking graphics) | 5 | link_hifi_libraries(shared shaders image gpu ktx fbx hfm baking graphics networking material-networking) |
| 6 | 6 | ||
| 7 | setup_memory_debugger() | 7 | setup_memory_debugger() |
| 8 | 8 | ||
| @@ -23,6 +23,7 @@ · 22 unmodified lines | |||
| 23 | #include "baking/BakerLibrary.h" | 23 | #include "baking/BakerLibrary.h" |
| 24 | #include "JSBaker.h" | 24 | #include "JSBaker.h" |
| 25 | #include "TextureBaker.h" | 25 | #include "TextureBaker.h" |
| 26 | #include "MaterialBaker.h" | ||
| 26 | 27 | ||
| 27 | BakerCLI::BakerCLI(OvenCLIApplication* parent) : QObject(parent) { | 28 | BakerCLI::BakerCLI(OvenCLIApplication* parent) : QObject(parent) { |
| 28 | 29 | ||
| @@ -60,8 +61,8 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& · 31 unmodified lines | |||
| 60 | _baker = std::unique_ptr<Baker> { new JSBaker(inputUrl, outputPath) }; | 61 | _baker = std::unique_ptr<Baker> { new JSBaker(inputUrl, outputPath) }; |
| 61 | _baker->moveToThread(Oven::instance().getNextWorkerThread()); | 62 | _baker->moveToThread(Oven::instance().getNextWorkerThread()); |
| 62 | } else if (type == MATERIAL_EXTENSION) { | 63 | } else if (type == MATERIAL_EXTENSION) { |
| 63 | //_baker = std::unique_ptr<Baker> { new MaterialBaker(inputUrl, outputPath) }; | 64 | _baker = std::unique_ptr<Baker> { new MaterialBaker(inputUrl.toDisplayString(), true, outputPath) }; |
| 64 | //_baker->moveToThread(Oven::instance().getNextWorkerThread()); | 65 | _baker->moveToThread(Oven::instance().getNextWorkerThread()); |
| 65 | } else { | 66 | } else { |
| 66 | // If the type doesn't match the above, we assume we have a texture, and the type specified is the | 67 | // If the type doesn't match the above, we assume we have a texture, and the type specified is the |
| 67 | // texture usage type (albedo, cubemap, normals, etc.) | 68 | // texture usage type (albedo, cubemap, normals, etc.) |
| @@ -187,8 +187,8 @@ void DomainBaker::addTextureBaker(const QString& property, const QString& url, i · 186 unmodified lines | |||
| 187 | 187 | ||
| 188 | // setup a texture baker for this URL, as long as we aren't baking a texture already | 188 | // setup a texture baker for this URL, as long as we aren't baking a texture already |
| 189 | if (!_textureBakers.contains(textureURL)) { | 189 | if (!_textureBakers.contains(textureURL)) { |
| 190 | // setup a baker for this texture | ||
| 191 | 190 | ||
| 191 | // setup a baker for this texture | ||
| 192 | QSharedPointer<TextureBaker> textureBaker { | 192 | QSharedPointer<TextureBaker> textureBaker { |
| 193 | new TextureBaker(textureURL, type, _contentOutputPath), | 193 | new TextureBaker(textureURL, type, _contentOutputPath), |
| 194 | &TextureBaker::deleteLater | 194 | &TextureBaker::deleteLater |
| @@ -220,16 +220,16 @@ void DomainBaker::addScriptBaker(const QString& property, const QString& url, QJ · 25 unmodified lines | |||
| 220 | // grab a clean version of the URL without a query or fragment | 220 | // grab a clean version of the URL without a query or fragment |
| 221 | QUrl scriptURL = QUrl(url).adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); | 221 | QUrl scriptURL = QUrl(url).adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); |
| 222 | 222 | ||
| 223 | // setup a texture baker for this URL, as long as we aren't baking a texture already | 223 | // setup a script baker for this URL, as long as we aren't baking a texture already |
| 224 | if (!_scriptBakers.contains(scriptURL)) { | 224 | if (!_scriptBakers.contains(scriptURL)) { |
| 225 | // setup a baker for this texture | ||
| 226 | 225 | ||
| 226 | // setup a baker for this script | ||
| 227 | QSharedPointer<JSBaker> scriptBaker { | 227 | QSharedPointer<JSBaker> scriptBaker { |
| 228 | new JSBaker(scriptURL, _contentOutputPath), | 228 | new JSBaker(scriptURL, _contentOutputPath), |
| 229 | &JSBaker::deleteLater | 229 | &JSBaker::deleteLater |
| 230 | }; | 230 | }; |
| 231 | 231 | ||
| 232 | // make sure our handler is called when the texture baker is done | 232 | // make sure our handler is called when the script baker is done |
| 233 | connect(scriptBaker.data(), &JSBaker::finished, this, &DomainBaker::handleFinishedScriptBaker); | 233 | connect(scriptBaker.data(), &JSBaker::finished, this, &DomainBaker::handleFinishedScriptBaker); |
| 234 | 234 | ||
| 235 | // insert it into our bakers hash so we hold a strong pointer to it | 235 | // 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 · 7 unmodified lines | |||
| 243 | ++_totalNumberOfSubBakes; | 243 | ++_totalNumberOfSubBakes; |
| 244 | } | 244 | } |
| 245 | 245 | ||
| 246 | // add this QJsonValueRef to our multi hash so that it can re-write the texture URL | 246 | // add this QJsonValueRef to our multi hash so that it can re-write the script URL |
| 247 | // to the baked version once the baker is complete | 247 | // to the baked version once the baker is complete |
| 248 | _entitiesNeedingRewrite.insert(scriptURL, { property, jsonRef }); | 248 | _entitiesNeedingRewrite.insert(scriptURL, { property, jsonRef }); |
| 249 | } | 249 | } |
| 250 | 250 | ||
| 251 | void DomainBaker::addMaterialBaker(const QString& property, const QString& data, bool isURL, QJsonValueRef& jsonRef) { | ||
| 252 | // grab a clean version of the URL without a query or fragment | ||
| 253 | QString materialData; | ||
| 254 | if (isURL) { | ||
| 255 | materialData = QUrl(data).adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment).toDisplayString(); | ||
| 256 | } else { | ||
| 257 | materialData = data; | ||
| 258 | } | ||
| 259 | |||
| 260 | // setup a material baker for this URL, as long as we aren't baking a material already | ||
| 261 | if (!_materialBakers.contains(materialData)) { | ||
| 262 | |||
| 263 | // setup a baker for this material | ||
| 264 | QSharedPointer<MaterialBaker> materialBaker { | ||
| 265 | new MaterialBaker(data, isURL, _contentOutputPath), | ||
| 266 | &MaterialBaker::deleteLater | ||
| 267 | }; | ||
| 268 | |||
| 269 | // make sure our handler is called when the material baker is done | ||
| 270 | connect(materialBaker.data(), &MaterialBaker::finished, this, &DomainBaker::handleFinishedMaterialBaker); | ||
| 271 | |||
| 272 | // insert it into our bakers hash so we hold a strong pointer to it | ||
| 273 | _materialBakers.insert(materialData, materialBaker); | ||
| 274 | |||
| 275 | // move the baker to a worker thread and kickoff the bake | ||
| 276 | materialBaker->moveToThread(Oven::instance().getNextWorkerThread()); | ||
| 277 | QMetaObject::invokeMethod(materialBaker.data(), "bake"); | ||
| 278 | |||
| 279 | // keep track of the total number of baking entities | ||
| 280 | ++_totalNumberOfSubBakes; | ||
| 281 | } | ||
| 282 | |||
| 283 | // add this QJsonValueRef to our multi hash so that it can re-write the texture URL | ||
| 284 | // to the baked version once the baker is complete | ||
| 285 | _entitiesNeedingRewrite.insert(materialData, { property, jsonRef }); | ||
| 286 | } | ||
| 287 | |||
| 251 | // All the Entity Properties that can be baked | 288 | // All the Entity Properties that can be baked |
| 252 | // *************************************************************************************** | 289 | // *************************************************************************************** |
| 253 | 290 | ||
| @@ -348,7 +385,12 @@ void DomainBaker::enumerateEntities() { · 94 unmodified lines | |||
| 348 | } | 385 | } |
| 349 | 386 | ||
| 350 | // Materials | 387 | // Materials |
| 351 | // TODO | 388 | if (entity.contains(MATERIAL_URL_KEY)) { |
| 389 | addMaterialBaker(MATERIAL_URL_KEY, entity[MATERIAL_URL_KEY].toString(), true, *it); | ||
| 390 | } | ||
| 391 | if (entity.contains(MATERIAL_DATA_KEY)) { | ||
| 392 | addMaterialBaker(MATERIAL_DATA_KEY, entity[MATERIAL_URL_KEY].toString(), false, *it); | ||
| 393 | } | ||
| 352 | } | 394 | } |
| 353 | } | 395 | } |
| 354 | 396 | ||
| @@ -568,6 +610,91 @@ void DomainBaker::handleFinishedScriptBaker() { · 213 unmodified lines | |||
| 568 | } | 610 | } |
| 569 | } | 611 | } |
| 570 | 612 | ||
| 613 | void DomainBaker::handleFinishedMaterialBaker() { | ||
| 614 | auto baker = qobject_cast<MaterialBaker*>(sender()); | ||
| 615 | |||
| 616 | if (baker) { | ||
| 617 | if (!baker->hasErrors()) { | ||
| 618 | // this MaterialBaker is done and everything went according to plan | ||
| 619 | qDebug() << "Re-writing entity references to" << baker->getMaterialData(); | ||
| 620 | |||
| 621 | QString newDataOrURL; | ||
| 622 | if (baker->isURL()) { | ||
| 623 | newDataOrURL = _destinationPath.resolved(baker->getBakedMaterialData()).toDisplayString(); | ||
| 624 | } else { | ||
| 625 | newDataOrURL = baker->getBakedMaterialData(); | ||
| 626 | } | ||
| 627 | |||
| 628 | // enumerate the QJsonRef values for the URL of this material from our multi hash of | ||
| 629 | // entity objects needing a URL re-write | ||
| 630 | for (auto propertyEntityPair : _entitiesNeedingRewrite.values(baker->getMaterialData())) { | ||
| 631 | QString property = propertyEntityPair.first; | ||
| 632 | // convert the entity QJsonValueRef to a QJsonObject so we can modify its URL | ||
| 633 | auto entity = propertyEntityPair.second.toObject(); | ||
| 634 | |||
| 635 | if (!property.contains(".")) { | ||
| 636 | // grab the old URL | ||
| 637 | QUrl oldURL = entity[property].toString(); | ||
| 638 | |||
| 639 | // copy the fragment and query, and user info from the old material data | ||
| 640 | if (baker->isURL()) { | ||
| 641 | QUrl newURL = newDataOrURL; | ||
| 642 | newURL.setQuery(oldURL.query()); | ||
| 643 | newURL.setFragment(oldURL.fragment()); | ||
| 644 | newURL.setUserInfo(oldURL.userInfo()); | ||
| 645 | |||
| 646 | // set the new URL as the value in our temp QJsonObject | ||
| 647 | entity[property] = newURL.toString(); | ||
| 648 | } else { | ||
| 649 | entity[property] = newDataOrURL; | ||
| 650 | } | ||
| 651 | } else { | ||
| 652 | // Group property | ||
| 653 | QStringList propertySplit = property.split("."); | ||
| 654 | assert(propertySplit.length() == 2); | ||
| 655 | // grab the old URL | ||
| 656 | auto oldObject = entity[propertySplit[0]].toObject(); | ||
| 657 | QUrl oldURL = oldObject[propertySplit[1]].toString(); | ||
| 658 | |||
| 659 | // copy the fragment and query, and user info from the old material data | ||
| 660 | if (baker->isURL()) { | ||
| 661 | QUrl newURL = newDataOrURL; | ||
| 662 | newURL.setQuery(oldURL.query()); | ||
| 663 | newURL.setFragment(oldURL.fragment()); | ||
| 664 | newURL.setUserInfo(oldURL.userInfo()); | ||
| 665 | |||
| 666 | // set the new URL as the value in our temp QJsonObject | ||
| 667 | oldObject[propertySplit[1]] = newURL.toString(); | ||
| 668 | entity[propertySplit[0]] = oldObject; | ||
| 669 | } else { | ||
| 670 | oldObject[propertySplit[1]] = newDataOrURL; | ||
| 671 | entity[propertySplit[0]] = oldObject; | ||
| 672 | } | ||
| 673 | } | ||
| 674 | |||
| 675 | // replace our temp object with the value referenced by our QJsonValueRef | ||
| 676 | propertyEntityPair.second = entity; | ||
| 677 | } | ||
| 678 | } else { | ||
| 679 | // this material failed to bake - this doesn't fail the entire bake but we need to add | ||
| 680 | // the errors from the material to our warnings | ||
| 681 | _warningList << baker->getErrors(); | ||
| 682 | } | ||
| 683 | |||
| 684 | // remove the baked URL from the multi hash of entities needing a re-write | ||
| 685 | _entitiesNeedingRewrite.remove(baker->getMaterialData()); | ||
| 686 | |||
| 687 | // drop our shared pointer to this baker so that it gets cleaned up | ||
| 688 | _materialBakers.remove(baker->getMaterialData()); | ||
| 689 | |||
| 690 | // emit progress to tell listeners how many materials we have baked | ||
| 691 | emit bakeProgress(++_completedSubBakes, _totalNumberOfSubBakes); | ||
| 692 | |||
| 693 | // check if this was the last material we needed to re-write and if we are done now | ||
| 694 | checkIfRewritingComplete(); | ||
| 695 | } | ||
| 696 | } | ||
| 697 | |||
| 571 | void DomainBaker::checkIfRewritingComplete() { | 698 | void DomainBaker::checkIfRewritingComplete() { |
| 572 | if (_entitiesNeedingRewrite.isEmpty()) { | 699 | if (_entitiesNeedingRewrite.isEmpty()) { |
| 573 | writeNewEntitiesFile(); | 700 | writeNewEntitiesFile(); |
| @@ -21,6 +21,7 @@ · 20 unmodified lines | |||
| 21 | #include "ModelBaker.h" | 21 | #include "ModelBaker.h" |
| 22 | #include "TextureBaker.h" | 22 | #include "TextureBaker.h" |
| 23 | #include "JSBaker.h" | 23 | #include "JSBaker.h" |
| 24 | #include "MaterialBaker.h" | ||
| 24 | 25 | ||
| 25 | class DomainBaker : public Baker { | 26 | class DomainBaker : public Baker { |
| 26 | Q_OBJECT | 27 | Q_OBJECT |
| @@ -40,6 +41,7 @@ private slots: · 13 unmodified lines | |||
| 40 | void handleFinishedModelBaker(); | 41 | void handleFinishedModelBaker(); |
| 41 | void handleFinishedTextureBaker(); | 42 | void handleFinishedTextureBaker(); |
| 42 | void handleFinishedScriptBaker(); | 43 | void handleFinishedScriptBaker(); |
| 44 | void handleFinishedMaterialBaker(); | ||
| 43 | 45 | ||
| 44 | private: | 46 | private: |
| 45 | void setupOutputFolder(); | 47 | void setupOutputFolder(); |
| @@ -62,6 +64,7 @@ private: · 16 unmodified lines | |||
| 62 | QHash<QUrl, QSharedPointer<ModelBaker>> _modelBakers; | 64 | QHash<QUrl, QSharedPointer<ModelBaker>> _modelBakers; |
| 63 | QHash<QUrl, QSharedPointer<TextureBaker>> _textureBakers; | 65 | QHash<QUrl, QSharedPointer<TextureBaker>> _textureBakers; |
| 64 | QHash<QUrl, QSharedPointer<JSBaker>> _scriptBakers; | 66 | QHash<QUrl, QSharedPointer<JSBaker>> _scriptBakers; |
| 67 | QHash<QUrl, QSharedPointer<MaterialBaker>> _materialBakers; | ||
| 65 | 68 | ||
| 66 | QMultiHash<QUrl, std::pair<QString, QJsonValueRef>> _entitiesNeedingRewrite; | 69 | QMultiHash<QUrl, std::pair<QString, QJsonValueRef>> _entitiesNeedingRewrite; |
| 67 | 70 | ||
| @@ -71,6 +74,7 @@ private: · 3 unmodified lines | |||
| 71 | void addModelBaker(const QString& property, const QString& url, QJsonValueRef& jsonRef); | 74 | void addModelBaker(const QString& property, const QString& url, QJsonValueRef& jsonRef); |
| 72 | void addTextureBaker(const QString& property, const QString& url, image::TextureUsage::Type type, QJsonValueRef& jsonRef); | 75 | void addTextureBaker(const QString& property, const QString& url, image::TextureUsage::Type type, QJsonValueRef& jsonRef); |
| 73 | void addScriptBaker(const QString& property, const QString& url, QJsonValueRef& jsonRef); | 76 | void addScriptBaker(const QString& property, const QString& url, QJsonValueRef& jsonRef); |
| 77 | void addMaterialBaker(const QString& property, const QString& data, bool isURL, QJsonValueRef& jsonRef); | ||
| 74 | }; | 78 | }; |
| 75 | 79 | ||
| 76 | #endif // hifi_DomainBaker_h | 80 | #endif // hifi_DomainBaker_h |
| @@ -63,6 +63,10 @@ void Oven::setupWorkerThreads(int numWorkerThreads) { · 62 unmodified lines | |||
| 63 | } | 63 | } |
| 64 | 64 | ||
| 65 | QThread* Oven::getNextWorkerThread() { | 65 | QThread* Oven::getNextWorkerThread() { |
| 66 | // FIXME: we assign these threads when we make the bakers, but if certain bakers finish quickly, we could end up | ||
| 67 | // in a situation where threads have finished and others have tons of work queued. Instead of assigning them at initialization, | ||
| 68 | // we should build a queue of bakers, and when threads finish, they can take the next available baker. | ||
| 69 | |||
| 66 | // Here we replicate some of the functionality of QThreadPool by giving callers an available worker thread to use. | 70 | // Here we replicate some of the functionality of QThreadPool by giving callers an available worker thread to use. |
| 67 | // We can't use QThreadPool because we want to put QObjects with signals/slots on these threads. | 71 | // We can't use QThreadPool because we want to put QObjects with signals/slots on these threads. |
| 68 | // So instead we setup our own list of threads, up to one less than the ideal thread count | 72 | // So instead we setup our own list of threads, up to one less than the ideal thread count |