working on material baker

This commit is contained in:
SamGondelman 2019-02-26 15:02:13 -08:00 committed by sabrina-shanman
parent 4965adbc2f
commit 94de0c12bc
13 changed files with 491 additions and 78 deletions

View file

@ -1,7 +1,7 @@
set(TARGET_NAME baking) set(TARGET_NAME baking)
setup_hifi_library(Concurrent) 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(gpu)
include_hifi_library_headers(hfm) include_hifi_library_headers(hfm)

View file

@ -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 <SharedUtil.h>
#include <PathUtils.h>
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();
}

View file

@ -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 <material-networking/MaterialCache.h>
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<QUrl, QSharedPointer<TextureBaker>> _textureBakers;
QString _bakedOutputDir;
QString _bakedMaterialData;
};
#endif // !hifi_MaterialBaker_h

View file

@ -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");

View file

@ -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 <QtCore/QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(material_baking)
#endif // hifi_MaterialBakingLoggingCategory_h

View file

@ -96,6 +96,8 @@ namespace scriptable {
bool defaultFallthrough; bool defaultFallthrough;
std::unordered_map<uint, bool> propertyFallthroughs; // not actually exposed to script std::unordered_map<uint, bool> propertyFallthroughs; // not actually exposed to script
graphics::MaterialKey key { 0 };
}; };
/**jsdoc /**jsdoc

View file

@ -364,20 +364,81 @@ namespace scriptable {
obj.setProperty("model", material.model); obj.setProperty("model", material.model);
const QScriptValue FALLTHROUGH("fallthrough"); const QScriptValue FALLTHROUGH("fallthrough");
obj.setProperty("opacity", material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_VAL_BIT) ? FALLTHROUGH : material.opacity); if (material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_VAL_BIT)) {
obj.setProperty("roughness", material.propertyFallthroughs.at(graphics::MaterialKey::GLOSSY_VAL_BIT) ? FALLTHROUGH : material.roughness); obj.setProperty("opacity", FALLTHROUGH);
obj.setProperty("metallic", material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_VAL_BIT) ? FALLTHROUGH : material.metallic); } else if (material.key.isTranslucentFactor()) {
obj.setProperty("scattering", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_VAL_BIT) ? FALLTHROUGH : material.scattering); obj.setProperty("opacity", material.opacity);
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));
obj.setProperty("emissiveMap", material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_MAP_BIT) ? FALLTHROUGH : material.emissiveMap); if (material.propertyFallthroughs.at(graphics::MaterialKey::GLOSSY_VAL_BIT)) {
obj.setProperty("albedoMap", material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_MAP_BIT) ? FALLTHROUGH : material.albedoMap); obj.setProperty("roughness", FALLTHROUGH);
obj.setProperty("opacityMap", material.opacityMap); } else if (material.key.isGlossy()) {
obj.setProperty("occlusionMap", material.propertyFallthroughs.at(graphics::MaterialKey::OCCLUSION_MAP_BIT) ? FALLTHROUGH : material.occlusionMap); obj.setProperty("roughness", material.roughness);
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::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 // Only set one of each of these
if (material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_MAP_BIT)) { if (material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_MAP_BIT)) {

View file

@ -45,75 +45,80 @@ scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const
defaultFallthrough = material.defaultFallthrough; defaultFallthrough = material.defaultFallthrough;
propertyFallthroughs = material.propertyFallthroughs; propertyFallthroughs = material.propertyFallthroughs;
key = material.key;
return *this; return *this;
} }
scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPointer& material) : scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPointer& material) {
name(material->getName().c_str()), if (material) {
model(material->getModel().c_str()), name = material->getName().c_str();
opacity(material->getOpacity()), model = material->getModel().c_str();
roughness(material->getRoughness()), opacity = material->getOpacity();
metallic(material->getMetallic()), roughness = material->getRoughness();
scattering(material->getScattering()), metallic = material->getMetallic();
unlit(material->isUnlit()), scattering = material->getScattering();
emissive(material->getEmissive()), unlit = material->isUnlit();
albedo(material->getAlbedo()), emissive = material->getEmissive();
defaultFallthrough(material->getDefaultFallthrough()), albedo = material->getAlbedo();
propertyFallthroughs(material->getPropertyFallthroughs()) defaultFallthrough = material->getDefaultFallthrough();
{ propertyFallthroughs = material->getPropertyFallthroughs();
auto map = material->getTextureMap(graphics::Material::MapChannel::EMISSIVE_MAP); key = material->getKey();
if (map && map->getTextureSource()) {
emissiveMap = map->getTextureSource()->getUrl().toString();
}
map = material->getTextureMap(graphics::Material::MapChannel::ALBEDO_MAP); auto map = material->getTextureMap(graphics::Material::MapChannel::EMISSIVE_MAP);
if (map && map->getTextureSource()) { if (map && map->getTextureSource()) {
albedoMap = map->getTextureSource()->getUrl().toString(); emissiveMap = map->getTextureSource()->getUrl().toString();
if (map->useAlphaChannel()) {
opacityMap = albedoMap;
} }
}
map = material->getTextureMap(graphics::Material::MapChannel::METALLIC_MAP); map = material->getTextureMap(graphics::Material::MapChannel::ALBEDO_MAP);
if (map && map->getTextureSource()) { if (map && map->getTextureSource()) {
if (map->getTextureSource()->getType() == image::TextureUsage::Type::METALLIC_TEXTURE) { albedoMap = map->getTextureSource()->getUrl().toString();
metallicMap = map->getTextureSource()->getUrl().toString(); if (map->useAlphaChannel()) {
} else if (map->getTextureSource()->getType() == image::TextureUsage::Type::SPECULAR_TEXTURE) { opacityMap = albedoMap;
specularMap = map->getTextureSource()->getUrl().toString(); }
} }
}
map = material->getTextureMap(graphics::Material::MapChannel::ROUGHNESS_MAP); map = material->getTextureMap(graphics::Material::MapChannel::METALLIC_MAP);
if (map && map->getTextureSource()) { if (map && map->getTextureSource()) {
if (map->getTextureSource()->getType() == image::TextureUsage::Type::ROUGHNESS_TEXTURE) { if (map->getTextureSource()->getType() == image::TextureUsage::Type::METALLIC_TEXTURE) {
roughnessMap = map->getTextureSource()->getUrl().toString(); metallicMap = map->getTextureSource()->getUrl().toString();
} else if (map->getTextureSource()->getType() == image::TextureUsage::Type::GLOSS_TEXTURE) { } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::SPECULAR_TEXTURE) {
glossMap = map->getTextureSource()->getUrl().toString(); specularMap = map->getTextureSource()->getUrl().toString();
}
} }
}
map = material->getTextureMap(graphics::Material::MapChannel::NORMAL_MAP); map = material->getTextureMap(graphics::Material::MapChannel::ROUGHNESS_MAP);
if (map && map->getTextureSource()) { if (map && map->getTextureSource()) {
if (map->getTextureSource()->getType() == image::TextureUsage::Type::NORMAL_TEXTURE) { if (map->getTextureSource()->getType() == image::TextureUsage::Type::ROUGHNESS_TEXTURE) {
normalMap = map->getTextureSource()->getUrl().toString(); roughnessMap = map->getTextureSource()->getUrl().toString();
} else if (map->getTextureSource()->getType() == image::TextureUsage::Type::BUMP_TEXTURE) { } else if (map->getTextureSource()->getType() == image::TextureUsage::Type::GLOSS_TEXTURE) {
bumpMap = map->getTextureSource()->getUrl().toString(); glossMap = map->getTextureSource()->getUrl().toString();
}
} }
}
map = material->getTextureMap(graphics::Material::MapChannel::OCCLUSION_MAP); map = material->getTextureMap(graphics::Material::MapChannel::NORMAL_MAP);
if (map && map->getTextureSource()) { if (map && map->getTextureSource()) {
occlusionMap = map->getTextureSource()->getUrl().toString(); 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); map = material->getTextureMap(graphics::Material::MapChannel::OCCLUSION_MAP);
if (map && map->getTextureSource()) { if (map && map->getTextureSource()) {
lightmapMap = map->getTextureSource()->getUrl().toString(); occlusionMap = map->getTextureSource()->getUrl().toString();
} }
map = material->getTextureMap(graphics::Material::MapChannel::SCATTERING_MAP); map = material->getTextureMap(graphics::Material::MapChannel::LIGHTMAP_MAP);
if (map && map->getTextureSource()) { if (map && map->getTextureSource()) {
scatteringMap = map->getTextureSource()->getUrl().toString(); lightmapMap = map->getTextureSource()->getUrl().toString();
}
map = material->getTextureMap(graphics::Material::MapChannel::SCATTERING_MAP);
if (map && map->getTextureSource()) {
scatteringMap = map->getTextureSource()->getUrl().toString();
}
} }
} }

View file

@ -2,7 +2,7 @@ set(TARGET_NAME oven)
setup_hifi_project(Widgets Gui Concurrent) 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() setup_memory_debugger()

View file

@ -23,6 +23,7 @@
#include "baking/BakerLibrary.h" #include "baking/BakerLibrary.h"
#include "JSBaker.h" #include "JSBaker.h"
#include "TextureBaker.h" #include "TextureBaker.h"
#include "MaterialBaker.h"
BakerCLI::BakerCLI(OvenCLIApplication* parent) : QObject(parent) { BakerCLI::BakerCLI(OvenCLIApplication* parent) : QObject(parent) {
@ -60,8 +61,8 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString&
_baker = std::unique_ptr<Baker> { new JSBaker(inputUrl, outputPath) }; _baker = std::unique_ptr<Baker> { new JSBaker(inputUrl, outputPath) };
_baker->moveToThread(Oven::instance().getNextWorkerThread()); _baker->moveToThread(Oven::instance().getNextWorkerThread());
} else if (type == MATERIAL_EXTENSION) { } else if (type == MATERIAL_EXTENSION) {
//_baker = std::unique_ptr<Baker> { new MaterialBaker(inputUrl, outputPath) }; _baker = std::unique_ptr<Baker> { new MaterialBaker(inputUrl.toDisplayString(), true, outputPath) };
//_baker->moveToThread(Oven::instance().getNextWorkerThread()); _baker->moveToThread(Oven::instance().getNextWorkerThread());
} else { } else {
// If the type doesn't match the above, we assume we have a texture, and the type specified is the // 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.) // texture usage type (albedo, cubemap, normals, etc.)

View file

@ -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 // setup a texture baker for this URL, as long as we aren't baking a texture already
if (!_textureBakers.contains(textureURL)) { if (!_textureBakers.contains(textureURL)) {
// setup a baker for this texture
// setup a baker for this texture
QSharedPointer<TextureBaker> textureBaker { QSharedPointer<TextureBaker> textureBaker {
new TextureBaker(textureURL, type, _contentOutputPath), new TextureBaker(textureURL, type, _contentOutputPath),
&TextureBaker::deleteLater &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 // grab a clean version of the URL without a query or fragment
QUrl scriptURL = QUrl(url).adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); 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)) { if (!_scriptBakers.contains(scriptURL)) {
// setup a baker for this texture
// setup a baker for this script
QSharedPointer<JSBaker> scriptBaker { QSharedPointer<JSBaker> scriptBaker {
new JSBaker(scriptURL, _contentOutputPath), new JSBaker(scriptURL, _contentOutputPath),
&JSBaker::deleteLater &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); connect(scriptBaker.data(), &JSBaker::finished, this, &DomainBaker::handleFinishedScriptBaker);
// insert it into our bakers hash so we hold a strong pointer to it // 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; ++_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 // to the baked version once the baker is complete
_entitiesNeedingRewrite.insert(scriptURL, { property, jsonRef }); _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> 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 // All the Entity Properties that can be baked
// *************************************************************************************** // ***************************************************************************************
@ -348,7 +385,12 @@ void DomainBaker::enumerateEntities() {
} }
// Materials // 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<MaterialBaker*>(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() { void DomainBaker::checkIfRewritingComplete() {
if (_entitiesNeedingRewrite.isEmpty()) { if (_entitiesNeedingRewrite.isEmpty()) {
writeNewEntitiesFile(); writeNewEntitiesFile();

View file

@ -21,6 +21,7 @@
#include "ModelBaker.h" #include "ModelBaker.h"
#include "TextureBaker.h" #include "TextureBaker.h"
#include "JSBaker.h" #include "JSBaker.h"
#include "MaterialBaker.h"
class DomainBaker : public Baker { class DomainBaker : public Baker {
Q_OBJECT Q_OBJECT
@ -40,6 +41,7 @@ private slots:
void handleFinishedModelBaker(); void handleFinishedModelBaker();
void handleFinishedTextureBaker(); void handleFinishedTextureBaker();
void handleFinishedScriptBaker(); void handleFinishedScriptBaker();
void handleFinishedMaterialBaker();
private: private:
void setupOutputFolder(); void setupOutputFolder();
@ -62,6 +64,7 @@ private:
QHash<QUrl, QSharedPointer<ModelBaker>> _modelBakers; QHash<QUrl, QSharedPointer<ModelBaker>> _modelBakers;
QHash<QUrl, QSharedPointer<TextureBaker>> _textureBakers; QHash<QUrl, QSharedPointer<TextureBaker>> _textureBakers;
QHash<QUrl, QSharedPointer<JSBaker>> _scriptBakers; QHash<QUrl, QSharedPointer<JSBaker>> _scriptBakers;
QHash<QUrl, QSharedPointer<MaterialBaker>> _materialBakers;
QMultiHash<QUrl, std::pair<QString, QJsonValueRef>> _entitiesNeedingRewrite; QMultiHash<QUrl, std::pair<QString, QJsonValueRef>> _entitiesNeedingRewrite;
@ -71,6 +74,7 @@ private:
void addModelBaker(const QString& property, const QString& url, QJsonValueRef& jsonRef); 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 addTextureBaker(const QString& property, const QString& url, image::TextureUsage::Type type, QJsonValueRef& jsonRef);
void addScriptBaker(const QString& property, const QString& url, 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 #endif // hifi_DomainBaker_h

View file

@ -63,6 +63,10 @@ void Oven::setupWorkerThreads(int numWorkerThreads) {
} }
QThread* Oven::getNextWorkerThread() { 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. // 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. // 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 // So instead we setup our own list of threads, up to one less than the ideal thread count