mirror of
https://github.com/overte-org/overte.git
synced 2025-08-04 04:43:31 +02:00
Add support for client texture selection
This commit is contained in:
parent
e4f5eb9cca
commit
24ac342c6b
26 changed files with 614 additions and 175 deletions
|
@ -1296,6 +1296,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
|
|
||||||
// Create the main thread context, the GPU backend, and the display plugins
|
// Create the main thread context, the GPU backend, and the display plugins
|
||||||
initializeGL();
|
initializeGL();
|
||||||
|
DependencyManager::get<TextureCache>()->setGPUContext(_gpuContext);
|
||||||
qCDebug(interfaceapp, "Initialized Display.");
|
qCDebug(interfaceapp, "Initialized Display.");
|
||||||
// Create the rendering engine. This can be slow on some machines due to lots of
|
// Create the rendering engine. This can be slow on some machines due to lots of
|
||||||
// GPU pipeline creation.
|
// GPU pipeline creation.
|
||||||
|
|
|
@ -70,17 +70,66 @@ void FBXBaker::bakeSourceCopy() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// export the FBX with re-written texture references
|
|
||||||
exportScene();
|
|
||||||
|
|
||||||
if (shouldStop()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if we're already done with textures (in case we had none to re-write)
|
// check if we're already done with textures (in case we had none to re-write)
|
||||||
checkIfTexturesFinished();
|
checkIfTexturesFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FBXBaker::embedTextureMetaData() {
|
||||||
|
std::vector<FBXNode> embeddedTextureNodes;
|
||||||
|
|
||||||
|
for (FBXNode& rootChild : _rootNode.children) {
|
||||||
|
if (rootChild.name == "Objects") {
|
||||||
|
qlonglong maxId = 0;
|
||||||
|
for (auto &child : rootChild.children) {
|
||||||
|
if (child.properties.length() == 3) {
|
||||||
|
maxId = std::max(maxId, child.properties[0].toLongLong());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& object : rootChild.children) {
|
||||||
|
if (object.name == "Texture") {
|
||||||
|
QVariant relativeFilename;
|
||||||
|
for (auto& child : object.children) {
|
||||||
|
if (child.name == "RelativeFilename") {
|
||||||
|
relativeFilename = child.properties[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relativeFilename.isNull() || !relativeFilename.toString().endsWith(BAKED_META_TEXTURE_SUFFIX)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FBXNode videoNode;
|
||||||
|
videoNode.name = "Video";
|
||||||
|
videoNode.properties.append(++maxId);
|
||||||
|
videoNode.properties.append(object.properties[1]);
|
||||||
|
videoNode.properties.append("Clip");
|
||||||
|
|
||||||
|
QString bakedTextureFilePath {
|
||||||
|
_bakedOutputDir + "/" + relativeFilename.toString()
|
||||||
|
};
|
||||||
|
qDebug() << "Location of texture: " << bakedTextureFilePath;
|
||||||
|
|
||||||
|
QFile textureFile { bakedTextureFilePath };
|
||||||
|
if (!textureFile.open(QIODevice::ReadOnly)) {
|
||||||
|
qWarning() << "Failed to open: " << bakedTextureFilePath;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
videoNode.children.append({ "RelativeFilename", { relativeFilename }, { } });
|
||||||
|
videoNode.children.append({ "Content", { textureFile.readAll() }, { } });
|
||||||
|
|
||||||
|
rootChild.children.append(videoNode);
|
||||||
|
|
||||||
|
textureFile.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void FBXBaker::setupOutputFolder() {
|
void FBXBaker::setupOutputFolder() {
|
||||||
// make sure there isn't already an output directory using the same name
|
// make sure there isn't already an output directory using the same name
|
||||||
if (QDir(_bakedOutputDir).exists()) {
|
if (QDir(_bakedOutputDir).exists()) {
|
||||||
|
@ -352,27 +401,3 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FBXBaker::exportScene() {
|
|
||||||
// save the relative path to this FBX inside our passed output folder
|
|
||||||
auto fileName = _modelURL.fileName();
|
|
||||||
auto baseName = fileName.left(fileName.lastIndexOf('.'));
|
|
||||||
auto bakedFilename = baseName + BAKED_FBX_EXTENSION;
|
|
||||||
|
|
||||||
_bakedModelFilePath = _bakedOutputDir + "/" + bakedFilename;
|
|
||||||
|
|
||||||
auto fbxData = FBXWriter::encodeFBX(_rootNode);
|
|
||||||
|
|
||||||
QFile bakedFile(_bakedModelFilePath);
|
|
||||||
|
|
||||||
if (!bakedFile.open(QIODevice::WriteOnly)) {
|
|
||||||
handleError("Error opening " + _bakedModelFilePath + " for writing");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bakedFile.write(fbxData);
|
|
||||||
|
|
||||||
_outputFiles.push_back(_bakedModelFilePath);
|
|
||||||
|
|
||||||
qCDebug(model_baking) << "Exported" << _modelURL << "with re-written paths to" << _bakedModelFilePath;
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,8 +26,6 @@
|
||||||
|
|
||||||
#include <FBX.h>
|
#include <FBX.h>
|
||||||
|
|
||||||
static const QString BAKED_FBX_EXTENSION = ".baked.fbx";
|
|
||||||
|
|
||||||
using TextureBakerThreadGetter = std::function<QThread*()>;
|
using TextureBakerThreadGetter = std::function<QThread*()>;
|
||||||
|
|
||||||
class FBXBaker : public ModelBaker {
|
class FBXBaker : public ModelBaker {
|
||||||
|
@ -51,11 +49,11 @@ private:
|
||||||
void loadSourceFBX();
|
void loadSourceFBX();
|
||||||
|
|
||||||
void importScene();
|
void importScene();
|
||||||
|
void embedTextureMetaData();
|
||||||
void rewriteAndBakeSceneModels();
|
void rewriteAndBakeSceneModels();
|
||||||
void rewriteAndBakeSceneTextures();
|
void rewriteAndBakeSceneTextures();
|
||||||
void exportScene();
|
void exportScene();
|
||||||
|
|
||||||
FBXNode _rootNode;
|
|
||||||
FBXGeometry* _geometry;
|
FBXGeometry* _geometry;
|
||||||
QHash<QString, int> _textureNameMatchCount;
|
QHash<QString, int> _textureNameMatchCount;
|
||||||
QHash<QUrl, QString> _remappedTexturePaths;
|
QHash<QUrl, QString> _remappedTexturePaths;
|
||||||
|
|
|
@ -248,7 +248,7 @@ QString ModelBaker::compressTexture(QString modelTextureFileName, image::Texture
|
||||||
|
|
||||||
QFileInfo modelTextureFileInfo{ modelTextureFileName.replace("\\", "/") };
|
QFileInfo modelTextureFileInfo{ modelTextureFileName.replace("\\", "/") };
|
||||||
|
|
||||||
if (modelTextureFileInfo.suffix() == BAKED_TEXTURE_EXT.mid(1)) {
|
if (modelTextureFileInfo.suffix() == BAKED_TEXTURE_KTX_EXT.mid(1)) {
|
||||||
// re-baking a model that already references baked textures
|
// re-baking a model that already references baked textures
|
||||||
// this is an error - return from here
|
// this is an error - return from here
|
||||||
handleError("Cannot re-bake a file that already references compressed textures");
|
handleError("Cannot re-bake a file that already references compressed textures");
|
||||||
|
@ -273,31 +273,31 @@ QString ModelBaker::compressTexture(QString modelTextureFileName, image::Texture
|
||||||
}
|
}
|
||||||
auto urlToTexture = getTextureURL(modelTextureFileInfo, modelTextureFileName, !textureContent.isNull());
|
auto urlToTexture = getTextureURL(modelTextureFileInfo, modelTextureFileName, !textureContent.isNull());
|
||||||
|
|
||||||
QString bakedTextureFileName;
|
QString baseTextureFileName;
|
||||||
if (_remappedTexturePaths.contains(urlToTexture)) {
|
if (_remappedTexturePaths.contains(urlToTexture)) {
|
||||||
bakedTextureFileName = _remappedTexturePaths[urlToTexture];
|
baseTextureFileName = _remappedTexturePaths[urlToTexture];
|
||||||
} else {
|
} else {
|
||||||
// construct the new baked texture file name and file path
|
// construct the new baked texture file name and file path
|
||||||
// ensuring that the baked texture will have a unique name
|
// ensuring that the baked texture will have a unique name
|
||||||
// even if there was another texture with the same name at a different path
|
// even if there was another texture with the same name at a different path
|
||||||
bakedTextureFileName = createBakedTextureFileName(modelTextureFileInfo);
|
baseTextureFileName = createBaseTextureFileName(modelTextureFileInfo);
|
||||||
_remappedTexturePaths[urlToTexture] = bakedTextureFileName;
|
_remappedTexturePaths[urlToTexture] = baseTextureFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName
|
qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName
|
||||||
<< "to" << bakedTextureFileName;
|
<< "to" << baseTextureFileName;
|
||||||
|
|
||||||
QString bakedTextureFilePath{
|
QString bakedTextureFilePath {
|
||||||
_bakedOutputDir + "/" + bakedTextureFileName
|
_bakedOutputDir + "/" + baseTextureFileName + BAKED_META_TEXTURE_SUFFIX
|
||||||
};
|
};
|
||||||
|
|
||||||
textureChild = bakedTextureFileName;
|
textureChild = baseTextureFileName + BAKED_META_TEXTURE_SUFFIX;
|
||||||
|
|
||||||
if (!_bakingTextures.contains(urlToTexture)) {
|
if (!_bakingTextures.contains(urlToTexture)) {
|
||||||
_outputFiles.push_back(bakedTextureFilePath);
|
_outputFiles.push_back(bakedTextureFilePath);
|
||||||
|
|
||||||
// bake this texture asynchronously
|
// bake this texture asynchronously
|
||||||
bakeTexture(urlToTexture, textureType, _bakedOutputDir, bakedTextureFileName, textureContent);
|
bakeTexture(urlToTexture, textureType, _bakedOutputDir, baseTextureFileName, textureContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,7 +309,7 @@ void ModelBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type t
|
||||||
|
|
||||||
// start a bake for this texture and add it to our list to keep track of
|
// start a bake for this texture and add it to our list to keep track of
|
||||||
QSharedPointer<TextureBaker> bakingTexture{
|
QSharedPointer<TextureBaker> bakingTexture{
|
||||||
new TextureBaker(textureURL, textureType, outputDir, bakedFilename, textureContent),
|
new TextureBaker(textureURL, textureType, outputDir, "../", bakedFilename, textureContent),
|
||||||
&TextureBaker::deleteLater
|
&TextureBaker::deleteLater
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -484,30 +484,30 @@ void ModelBaker::checkIfTexturesFinished() {
|
||||||
} else {
|
} else {
|
||||||
qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL;
|
qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL;
|
||||||
|
|
||||||
|
texturesFinished();
|
||||||
|
|
||||||
setIsFinished(true);
|
setIsFinished(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ModelBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) {
|
QString ModelBaker::createBaseTextureFileName(const QFileInfo& textureFileInfo) {
|
||||||
// first make sure we have a unique base name for this texture
|
// first make sure we have a unique base name for this texture
|
||||||
// in case another texture referenced by this model has the same base name
|
// in case another texture referenced by this model has the same base name
|
||||||
auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()];
|
auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()];
|
||||||
|
|
||||||
QString bakedTextureFileName{ textureFileInfo.completeBaseName() };
|
QString baseTextureFileName{ textureFileInfo.completeBaseName() };
|
||||||
|
|
||||||
if (nameMatches > 0) {
|
if (nameMatches > 0) {
|
||||||
// there are already nameMatches texture with this name
|
// there are already nameMatches texture with this name
|
||||||
// append - and that number to our baked texture file name so that it is unique
|
// append - and that number to our baked texture file name so that it is unique
|
||||||
bakedTextureFileName += "-" + QString::number(nameMatches);
|
baseTextureFileName += "-" + QString::number(nameMatches);
|
||||||
}
|
}
|
||||||
|
|
||||||
bakedTextureFileName += BAKED_TEXTURE_EXT;
|
|
||||||
|
|
||||||
// increment the number of name matches
|
// increment the number of name matches
|
||||||
++nameMatches;
|
++nameMatches;
|
||||||
|
|
||||||
return bakedTextureFileName;
|
return baseTextureFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelBaker::setWasAborted(bool wasAborted) {
|
void ModelBaker::setWasAborted(bool wasAborted) {
|
||||||
|
@ -519,3 +519,95 @@ void ModelBaker::setWasAborted(bool wasAborted) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModelBaker::texturesFinished() {
|
||||||
|
embedTextureMetaData();
|
||||||
|
exportScene();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelBaker::embedTextureMetaData() {
|
||||||
|
std::vector<FBXNode> embeddedTextureNodes;
|
||||||
|
|
||||||
|
for (FBXNode& rootChild : _rootNode.children) {
|
||||||
|
if (rootChild.name == "Objects") {
|
||||||
|
qlonglong maxId = 0;
|
||||||
|
for (auto &child : rootChild.children) {
|
||||||
|
if (child.properties.length() == 3) {
|
||||||
|
maxId = std::max(maxId, child.properties[0].toLongLong());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Max id found was: " << maxId;
|
||||||
|
|
||||||
|
for (auto& object : rootChild.children) {
|
||||||
|
if (object.name == "Texture") {
|
||||||
|
QVariant relativeFilename;
|
||||||
|
for (auto& child : object.children) {
|
||||||
|
if (child.name == "RelativeFilename") {
|
||||||
|
relativeFilename = child.properties[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relativeFilename.isNull()
|
||||||
|
|| !relativeFilename.toString().endsWith(BAKED_META_TEXTURE_SUFFIX)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (object.properties.length() < 2) {
|
||||||
|
qWarning() << "Found texture with unexpected number of properties: " << object.name;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FBXNode videoNode;
|
||||||
|
videoNode.name = "Video";
|
||||||
|
videoNode.properties.append(++maxId);
|
||||||
|
videoNode.properties.append(object.properties[1]);
|
||||||
|
videoNode.properties.append("Clip");
|
||||||
|
|
||||||
|
QString bakedTextureFilePath {
|
||||||
|
_bakedOutputDir + "/" + relativeFilename.toString()
|
||||||
|
};
|
||||||
|
qDebug() << "Location of texture: " << bakedTextureFilePath;
|
||||||
|
|
||||||
|
QFile textureFile { bakedTextureFilePath };
|
||||||
|
if (!textureFile.open(QIODevice::ReadOnly)) {
|
||||||
|
qWarning() << "Failed to open: " << bakedTextureFilePath;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
videoNode.children.append({ "RelativeFilename", { relativeFilename }, { } });
|
||||||
|
videoNode.children.append({ "Content", { textureFile.readAll() }, { } });
|
||||||
|
|
||||||
|
rootChild.children.append(videoNode);
|
||||||
|
|
||||||
|
textureFile.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelBaker::exportScene() {
|
||||||
|
// save the relative path to this FBX inside our passed output folder
|
||||||
|
auto fileName = _modelURL.fileName();
|
||||||
|
auto baseName = fileName.left(fileName.lastIndexOf('.'));
|
||||||
|
auto bakedFilename = baseName + BAKED_FBX_EXTENSION;
|
||||||
|
|
||||||
|
_bakedModelFilePath = _bakedOutputDir + "/" + bakedFilename;
|
||||||
|
|
||||||
|
auto fbxData = FBXWriter::encodeFBX(_rootNode);
|
||||||
|
|
||||||
|
QFile bakedFile(_bakedModelFilePath);
|
||||||
|
|
||||||
|
if (!bakedFile.open(QIODevice::WriteOnly)) {
|
||||||
|
handleError("Error opening " + _bakedModelFilePath + " for writing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bakedFile.write(fbxData);
|
||||||
|
|
||||||
|
_outputFiles.push_back(_bakedModelFilePath);
|
||||||
|
|
||||||
|
qCDebug(model_baking) << "Exported" << _modelURL << "with re-written paths to" << _bakedModelFilePath;
|
||||||
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
using TextureBakerThreadGetter = std::function<QThread*()>;
|
using TextureBakerThreadGetter = std::function<QThread*()>;
|
||||||
using GetMaterialIDCallback = std::function <int(int)>;
|
using GetMaterialIDCallback = std::function <int(int)>;
|
||||||
|
|
||||||
|
static const QString BAKED_FBX_EXTENSION = ".baked.fbx";
|
||||||
|
|
||||||
class ModelBaker : public Baker {
|
class ModelBaker : public Baker {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
@ -49,7 +51,11 @@ public slots:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void checkIfTexturesFinished();
|
void checkIfTexturesFinished();
|
||||||
|
void texturesFinished();
|
||||||
|
void embedTextureMetaData();
|
||||||
|
void exportScene();
|
||||||
|
|
||||||
|
FBXNode _rootNode;
|
||||||
QHash<QByteArray, QByteArray> _textureContentMap;
|
QHash<QByteArray, QByteArray> _textureContentMap;
|
||||||
QUrl _modelURL;
|
QUrl _modelURL;
|
||||||
QString _bakedOutputDir;
|
QString _bakedOutputDir;
|
||||||
|
@ -63,7 +69,7 @@ private slots:
|
||||||
void handleAbortedTexture();
|
void handleAbortedTexture();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString createBakedTextureFileName(const QFileInfo & textureFileInfo);
|
QString createBaseTextureFileName(const QFileInfo & textureFileInfo);
|
||||||
QUrl getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded = false);
|
QUrl getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded = false);
|
||||||
void bakeTexture(const QUrl & textureURL, image::TextureUsage::Type textureType, const QDir & outputDir,
|
void bakeTexture(const QUrl & textureURL, image::TextureUsage::Type textureType, const QDir & outputDir,
|
||||||
const QString & bakedFilename, const QByteArray & textureContent);
|
const QString & bakedFilename, const QByteArray & textureContent);
|
||||||
|
|
|
@ -147,31 +147,7 @@ void OBJBaker::bakeOBJ() {
|
||||||
auto geometry = reader.readOBJ(objData, QVariantHash(), combineParts, _modelURL);
|
auto geometry = reader.readOBJ(objData, QVariantHash(), combineParts, _modelURL);
|
||||||
|
|
||||||
// Write OBJ Data as FBX tree nodes
|
// Write OBJ Data as FBX tree nodes
|
||||||
FBXNode rootNode;
|
createFBXNodeTree(_rootNode, *geometry);
|
||||||
createFBXNodeTree(rootNode, *geometry);
|
|
||||||
|
|
||||||
// Serialize the resultant FBX tree
|
|
||||||
auto encodedFBX = FBXWriter::encodeFBX(rootNode);
|
|
||||||
|
|
||||||
// Export as baked FBX
|
|
||||||
auto fileName = _modelURL.fileName();
|
|
||||||
auto baseName = fileName.left(fileName.lastIndexOf('.'));
|
|
||||||
auto bakedFilename = baseName + ".baked.fbx";
|
|
||||||
|
|
||||||
_bakedModelFilePath = _bakedOutputDir + "/" + bakedFilename;
|
|
||||||
|
|
||||||
QFile bakedFile;
|
|
||||||
bakedFile.setFileName(_bakedModelFilePath);
|
|
||||||
if (!bakedFile.open(QIODevice::WriteOnly)) {
|
|
||||||
handleError("Error opening " + _bakedModelFilePath + " for writing");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bakedFile.write(encodedFBX);
|
|
||||||
|
|
||||||
// Export successful
|
|
||||||
_outputFiles.push_back(_bakedModelFilePath);
|
|
||||||
qCDebug(model_baking) << "Exported" << _modelURL << "to" << _bakedModelFilePath;
|
|
||||||
|
|
||||||
checkIfTexturesFinished();
|
checkIfTexturesFinished();
|
||||||
}
|
}
|
||||||
|
@ -203,15 +179,17 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
||||||
globalSettingsNode.children = { properties70Node };
|
globalSettingsNode.children = { properties70Node };
|
||||||
|
|
||||||
// Generating Object node
|
// Generating Object node
|
||||||
_objectNode.name = OBJECTS_NODE_NAME;
|
FBXNode objectNode;
|
||||||
|
objectNode.name = OBJECTS_NODE_NAME;
|
||||||
|
|
||||||
// Generating Object node's child - Geometry node
|
// Generating Object node's child - Geometry node
|
||||||
FBXNode geometryNode;
|
FBXNode geometryNode;
|
||||||
geometryNode.name = GEOMETRY_NODE_NAME;
|
geometryNode.name = GEOMETRY_NODE_NAME;
|
||||||
|
NodeID geometryID;
|
||||||
{
|
{
|
||||||
_geometryID = nextNodeID();
|
geometryID = nextNodeID();
|
||||||
geometryNode.properties = {
|
geometryNode.properties = {
|
||||||
_geometryID,
|
geometryID,
|
||||||
GEOMETRY_NODE_NAME,
|
GEOMETRY_NODE_NAME,
|
||||||
MESH
|
MESH
|
||||||
};
|
};
|
||||||
|
@ -226,12 +204,13 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
||||||
// Generating Object node's child - Model node
|
// Generating Object node's child - Model node
|
||||||
FBXNode modelNode;
|
FBXNode modelNode;
|
||||||
modelNode.name = MODEL_NODE_NAME;
|
modelNode.name = MODEL_NODE_NAME;
|
||||||
|
NodeID modelID;
|
||||||
{
|
{
|
||||||
_modelID = nextNodeID();
|
modelID = nextNodeID();
|
||||||
modelNode.properties = { _modelID, MODEL_NODE_NAME, MESH };
|
modelNode.properties = { modelID, MODEL_NODE_NAME, MESH };
|
||||||
}
|
}
|
||||||
|
|
||||||
_objectNode.children = { geometryNode, modelNode };
|
objectNode.children = { geometryNode, modelNode };
|
||||||
|
|
||||||
// Generating Objects node's child - Material node
|
// Generating Objects node's child - Material node
|
||||||
auto& meshParts = geometry.meshes[0].parts;
|
auto& meshParts = geometry.meshes[0].parts;
|
||||||
|
@ -247,7 +226,7 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
||||||
setMaterialNodeProperties(materialNode, meshPart.materialID, geometry);
|
setMaterialNodeProperties(materialNode, meshPart.materialID, geometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
_objectNode.children.append(materialNode);
|
objectNode.children.append(materialNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generating Texture Node
|
// Generating Texture Node
|
||||||
|
@ -257,13 +236,13 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
||||||
QString material = meshParts[i].materialID;
|
QString material = meshParts[i].materialID;
|
||||||
FBXMaterial currentMaterial = geometry.materials[material];
|
FBXMaterial currentMaterial = geometry.materials[material];
|
||||||
if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) {
|
if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) {
|
||||||
_textureID = nextNodeID();
|
auto textureID = nextNodeID();
|
||||||
_mapTextureMaterial.emplace_back(_textureID, i);
|
_mapTextureMaterial.emplace_back(textureID, i);
|
||||||
|
|
||||||
FBXNode textureNode;
|
FBXNode textureNode;
|
||||||
{
|
{
|
||||||
textureNode.name = TEXTURE_NODE_NAME;
|
textureNode.name = TEXTURE_NODE_NAME;
|
||||||
textureNode.properties = { _textureID };
|
textureNode.properties = { textureID, "texture" + QString::number(textureID) };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Texture node child - TextureName node
|
// Texture node child - TextureName node
|
||||||
|
@ -295,7 +274,7 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
||||||
|
|
||||||
textureNode.children = { textureNameNode, relativeFilenameNode };
|
textureNode.children = { textureNameNode, relativeFilenameNode };
|
||||||
|
|
||||||
_objectNode.children.append(textureNode);
|
objectNode.children.append(textureNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,14 +285,14 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
||||||
// connect Geometry to Model
|
// connect Geometry to Model
|
||||||
FBXNode cNode;
|
FBXNode cNode;
|
||||||
cNode.name = C_NODE_NAME;
|
cNode.name = C_NODE_NAME;
|
||||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, _geometryID, _modelID };
|
cNode.properties = { CONNECTIONS_NODE_PROPERTY, geometryID, modelID };
|
||||||
connectionsNode.children = { cNode };
|
connectionsNode.children = { cNode };
|
||||||
|
|
||||||
// connect all materials to model
|
// connect all materials to model
|
||||||
for (auto& materialID : _materialIDs) {
|
for (auto& materialID : _materialIDs) {
|
||||||
FBXNode cNode;
|
FBXNode cNode;
|
||||||
cNode.name = C_NODE_NAME;
|
cNode.name = C_NODE_NAME;
|
||||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, materialID, _modelID };
|
cNode.properties = { CONNECTIONS_NODE_PROPERTY, materialID, modelID };
|
||||||
connectionsNode.children.append(cNode);
|
connectionsNode.children.append(cNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,7 +320,7 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make all generated nodes children of rootNode
|
// Make all generated nodes children of rootNode
|
||||||
rootNode.children = { globalSettingsNode, _objectNode, connectionsNode };
|
rootNode.children = { globalSettingsNode, objectNode, connectionsNode };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set properties for material nodes
|
// Set properties for material nodes
|
||||||
|
|
|
@ -43,12 +43,9 @@ private:
|
||||||
void setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry);
|
void setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry);
|
||||||
NodeID nextNodeID() { return _nodeID++; }
|
NodeID nextNodeID() { return _nodeID++; }
|
||||||
|
|
||||||
|
|
||||||
NodeID _nodeID { 0 };
|
NodeID _nodeID { 0 };
|
||||||
NodeID _geometryID;
|
|
||||||
NodeID _modelID;
|
|
||||||
std::vector<NodeID> _materialIDs;
|
std::vector<NodeID> _materialIDs;
|
||||||
NodeID _textureID;
|
|
||||||
std::vector<std::pair<NodeID, int>> _mapTextureMaterial;
|
std::vector<std::pair<NodeID, int>> _mapTextureMaterial;
|
||||||
FBXNode _objectNode;
|
|
||||||
};
|
};
|
||||||
#endif // hifi_OBJBaker_h
|
#endif // hifi_OBJBaker_h
|
||||||
|
|
|
@ -18,26 +18,30 @@
|
||||||
#include <ktx/KTX.h>
|
#include <ktx/KTX.h>
|
||||||
#include <NetworkAccessManager.h>
|
#include <NetworkAccessManager.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
#include <TextureMeta.h>
|
||||||
|
|
||||||
#include "ModelBakingLoggingCategory.h"
|
#include "ModelBakingLoggingCategory.h"
|
||||||
|
|
||||||
#include "TextureBaker.h"
|
#include "TextureBaker.h"
|
||||||
|
|
||||||
const QString BAKED_TEXTURE_EXT = ".ktx";
|
const QString BAKED_TEXTURE_KTX_EXT = ".ktx";
|
||||||
|
const QString BAKED_TEXTURE_BCN_SUFFIX = "_bcn.ktx";
|
||||||
|
const QString BAKED_META_TEXTURE_SUFFIX = ".texmeta.json";
|
||||||
|
|
||||||
TextureBaker::TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType,
|
TextureBaker::TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType,
|
||||||
const QDir& outputDirectory, const QString& bakedFilename,
|
const QDir& outputDirectory, const QString& metaTexturePathPrefix,
|
||||||
const QByteArray& textureContent) :
|
const QString& baseFilename, const QByteArray& textureContent) :
|
||||||
_textureURL(textureURL),
|
_textureURL(textureURL),
|
||||||
_originalTexture(textureContent),
|
_originalTexture(textureContent),
|
||||||
_textureType(textureType),
|
_textureType(textureType),
|
||||||
|
_baseFilename(baseFilename),
|
||||||
_outputDirectory(outputDirectory),
|
_outputDirectory(outputDirectory),
|
||||||
_bakedTextureFileName(bakedFilename)
|
_metaTexturePathPrefix(metaTexturePathPrefix)
|
||||||
{
|
{
|
||||||
if (bakedFilename.isEmpty()) {
|
if (baseFilename.isEmpty()) {
|
||||||
// figure out the baked texture filename
|
// figure out the baked texture filename
|
||||||
auto originalFilename = textureURL.fileName();
|
auto originalFilename = textureURL.fileName();
|
||||||
_bakedTextureFileName = originalFilename.left(originalFilename.lastIndexOf('.')) + BAKED_TEXTURE_EXT;
|
_baseFilename = originalFilename.left(originalFilename.lastIndexOf('.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +122,19 @@ void TextureBaker::processTexture() {
|
||||||
auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5);
|
auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5);
|
||||||
std::string hash = hashData.toHex().toStdString();
|
std::string hash = hashData.toHex().toStdString();
|
||||||
|
|
||||||
|
TextureMeta meta;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto filePath = _outputDirectory.absoluteFilePath(_textureURL.fileName());
|
||||||
|
QFile file { filePath };
|
||||||
|
if (!file.open(QIODevice::WriteOnly) || file.write(_originalTexture) == -1) {
|
||||||
|
handleError("Could not write meta texture for " + _textureURL.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_outputFiles.push_back(filePath);
|
||||||
|
meta.original =_metaTexturePathPrefix +_textureURL.fileName();
|
||||||
|
}
|
||||||
|
|
||||||
// IMPORTANT: _originalTexture is empty past this point
|
// IMPORTANT: _originalTexture is empty past this point
|
||||||
auto processedTexture = image::processImage(std::move(_originalTexture), _textureURL.toString().toStdString(),
|
auto processedTexture = image::processImage(std::move(_originalTexture), _textureURL.toString().toStdString(),
|
||||||
ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, _abortProcessing);
|
ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, _abortProcessing);
|
||||||
|
@ -142,15 +159,37 @@ void TextureBaker::processTexture() {
|
||||||
|
|
||||||
const char* data = reinterpret_cast<const char*>(memKTX->_storage->data());
|
const char* data = reinterpret_cast<const char*>(memKTX->_storage->data());
|
||||||
const size_t length = memKTX->_storage->size();
|
const size_t length = memKTX->_storage->size();
|
||||||
|
const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat());
|
||||||
|
if (name == nullptr) {
|
||||||
|
handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Found type: " << name;
|
||||||
|
|
||||||
// attempt to write the baked texture to the destination file path
|
// attempt to write the baked texture to the destination file path
|
||||||
auto filePath = _outputDirectory.absoluteFilePath(_bakedTextureFileName);
|
{
|
||||||
QFile bakedTextureFile { filePath };
|
auto fileName = _baseFilename + BAKED_TEXTURE_BCN_SUFFIX;
|
||||||
|
auto filePath = _outputDirectory.absoluteFilePath(fileName);
|
||||||
if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) {
|
QFile bakedTextureFile { filePath };
|
||||||
handleError("Could not write baked texture for " + _textureURL.toString());
|
if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) {
|
||||||
} else {
|
handleError("Could not write baked texture for " + _textureURL.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
_outputFiles.push_back(filePath);
|
_outputFiles.push_back(filePath);
|
||||||
|
meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = _metaTexturePathPrefix + fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
auto data = meta.serialize();
|
||||||
|
_metaTextureFileName = _outputDirectory.absoluteFilePath(_baseFilename + BAKED_META_TEXTURE_SUFFIX);
|
||||||
|
QFile file { _metaTextureFileName };
|
||||||
|
if (!file.open(QIODevice::WriteOnly) || file.write(data) == -1) {
|
||||||
|
handleError("Could not write meta texture for " + _textureURL.toString());
|
||||||
|
} else {
|
||||||
|
_outputFiles.push_back(_metaTextureFileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(model_baking) << "Baked texture" << _textureURL;
|
qCDebug(model_baking) << "Baked texture" << _textureURL;
|
||||||
|
|
|
@ -21,22 +21,22 @@
|
||||||
|
|
||||||
#include "Baker.h"
|
#include "Baker.h"
|
||||||
|
|
||||||
extern const QString BAKED_TEXTURE_EXT;
|
extern const QString BAKED_TEXTURE_KTX_EXT;
|
||||||
|
extern const QString BAKED_META_TEXTURE_SUFFIX;
|
||||||
|
|
||||||
class TextureBaker : public Baker {
|
class TextureBaker : public Baker {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType,
|
TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType,
|
||||||
const QDir& outputDirectory, const QString& bakedFilename = QString(),
|
const QDir& outputDirectory, const QString& metaTexturePathPrefix = "",
|
||||||
const QByteArray& textureContent = QByteArray());
|
const QString& baseFilename = QString(), const QByteArray& textureContent = QByteArray());
|
||||||
|
|
||||||
const QByteArray& getOriginalTexture() const { return _originalTexture; }
|
const QByteArray& getOriginalTexture() const { return _originalTexture; }
|
||||||
|
|
||||||
QUrl getTextureURL() const { return _textureURL; }
|
QUrl getTextureURL() const { return _textureURL; }
|
||||||
|
|
||||||
QString getDestinationFilePath() const { return _outputDirectory.absoluteFilePath(_bakedTextureFileName); }
|
QString getMetaTextureFileName() const { return _metaTextureFileName; }
|
||||||
QString getBakedTextureFileName() const { return _bakedTextureFileName; }
|
|
||||||
|
|
||||||
virtual void setWasAborted(bool wasAborted) override;
|
virtual void setWasAborted(bool wasAborted) override;
|
||||||
|
|
||||||
|
@ -58,8 +58,10 @@ private:
|
||||||
QByteArray _originalTexture;
|
QByteArray _originalTexture;
|
||||||
image::TextureUsage::Type _textureType;
|
image::TextureUsage::Type _textureType;
|
||||||
|
|
||||||
|
QString _baseFilename;
|
||||||
QDir _outputDirectory;
|
QDir _outputDirectory;
|
||||||
QString _bakedTextureFileName;
|
QString _metaTextureFileName;
|
||||||
|
QString _metaTexturePathPrefix;
|
||||||
|
|
||||||
std::atomic<bool> _abortProcessing { false };
|
std::atomic<bool> _abortProcessing { false };
|
||||||
};
|
};
|
||||||
|
|
|
@ -1101,6 +1101,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
||||||
}
|
}
|
||||||
if (!content.isEmpty()) {
|
if (!content.isEmpty()) {
|
||||||
_textureContent.insert(filepath, content);
|
_textureContent.insert(filepath, content);
|
||||||
|
qDebug() << "Adding content: " << filepath << content.length();
|
||||||
}
|
}
|
||||||
} else if (object.name == "Material") {
|
} else if (object.name == "Material") {
|
||||||
FBXMaterial material;
|
FBXMaterial material;
|
||||||
|
|
|
@ -85,12 +85,16 @@ FBXTexture FBXReader::getTexture(const QString& textureID) {
|
||||||
FBXTexture texture;
|
FBXTexture texture;
|
||||||
const QByteArray& filepath = _textureFilepaths.value(textureID);
|
const QByteArray& filepath = _textureFilepaths.value(textureID);
|
||||||
texture.content = _textureContent.value(filepath);
|
texture.content = _textureContent.value(filepath);
|
||||||
|
qDebug() << "Getting texture: " << textureID << filepath << texture.content.length();
|
||||||
|
|
||||||
if (texture.content.isEmpty()) { // the content is not inlined
|
if (texture.content.isEmpty()) { // the content is not inlined
|
||||||
|
qDebug() << "Texture is not inlined";
|
||||||
texture.filename = _textureFilenames.value(textureID);
|
texture.filename = _textureFilenames.value(textureID);
|
||||||
} else { // use supplied filepath for inlined content
|
} else { // use supplied filepath for inlined content
|
||||||
|
qDebug() << "Texture is inlined";
|
||||||
texture.filename = filepath;
|
texture.filename = filepath;
|
||||||
}
|
}
|
||||||
|
qDebug() << "Path: " << texture.filename;
|
||||||
|
|
||||||
texture.id = textureID;
|
texture.id = textureID;
|
||||||
texture.name = _textureNames.value(textureID);
|
texture.name = _textureNames.value(textureID);
|
||||||
|
|
|
@ -52,6 +52,8 @@ public:
|
||||||
static const std::string GL41_VERSION;
|
static const std::string GL41_VERSION;
|
||||||
const std::string& getVersion() const override { return GL41_VERSION; }
|
const std::string& getVersion() const override { return GL41_VERSION; }
|
||||||
|
|
||||||
|
bool supportedTextureFormat(const gpu::Element& format) override;
|
||||||
|
|
||||||
class GL41Texture : public GLTexture {
|
class GL41Texture : public GLTexture {
|
||||||
using Parent = GLTexture;
|
using Parent = GLTexture;
|
||||||
friend class GL41Backend;
|
friend class GL41Backend;
|
||||||
|
@ -173,8 +175,6 @@ protected:
|
||||||
void makeProgramBindings(ShaderObject& shaderObject) override;
|
void makeProgramBindings(ShaderObject& shaderObject) override;
|
||||||
int makeResourceBufferSlots(GLuint glprogram, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) override;
|
int makeResourceBufferSlots(GLuint glprogram, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) override;
|
||||||
|
|
||||||
static bool supportedTextureFormat(const gpu::Element& format);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
|
|
@ -54,6 +54,8 @@ public:
|
||||||
static const std::string GL45_VERSION;
|
static const std::string GL45_VERSION;
|
||||||
const std::string& getVersion() const override { return GL45_VERSION; }
|
const std::string& getVersion() const override { return GL45_VERSION; }
|
||||||
|
|
||||||
|
bool supportedTextureFormat(const gpu::Element& format) override;
|
||||||
|
|
||||||
class GL45Texture : public GLTexture {
|
class GL45Texture : public GLTexture {
|
||||||
using Parent = GLTexture;
|
using Parent = GLTexture;
|
||||||
friend class GL45Backend;
|
friend class GL45Backend;
|
||||||
|
|
|
@ -32,6 +32,24 @@ using namespace gpu::gl45;
|
||||||
#define FORCE_STRICT_TEXTURE 0
|
#define FORCE_STRICT_TEXTURE 0
|
||||||
#define ENABLE_SPARSE_TEXTURE 0
|
#define ENABLE_SPARSE_TEXTURE 0
|
||||||
|
|
||||||
|
bool GL45Backend::supportedTextureFormat(const gpu::Element& format) {
|
||||||
|
switch (format.getSemantic()) {
|
||||||
|
case gpu::Semantic::COMPRESSED_ETC2_RGB:
|
||||||
|
case gpu::Semantic::COMPRESSED_ETC2_SRGB:
|
||||||
|
case gpu::Semantic::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA:
|
||||||
|
case gpu::Semantic::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA:
|
||||||
|
case gpu::Semantic::COMPRESSED_ETC2_RGBA:
|
||||||
|
case gpu::Semantic::COMPRESSED_ETC2_SRGBA:
|
||||||
|
case gpu::Semantic::COMPRESSED_EAC_RED:
|
||||||
|
case gpu::Semantic::COMPRESSED_EAC_RED_SIGNED:
|
||||||
|
case gpu::Semantic::COMPRESSED_EAC_XY:
|
||||||
|
case gpu::Semantic::COMPRESSED_EAC_XY_SIGNED:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
|
GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
|
||||||
if (!texturePointer) {
|
if (!texturePointer) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
|
@ -32,7 +32,6 @@ public:
|
||||||
static const GLint RESOURCE_TRANSFER_EXTRA_TEX_UNIT { 33 };
|
static const GLint RESOURCE_TRANSFER_EXTRA_TEX_UNIT { 33 };
|
||||||
static const GLint RESOURCE_BUFFER_TEXBUF_TEX_UNIT { 34 };
|
static const GLint RESOURCE_BUFFER_TEXBUF_TEX_UNIT { 34 };
|
||||||
static const GLint RESOURCE_BUFFER_SLOT0_TEX_UNIT { 35 };
|
static const GLint RESOURCE_BUFFER_SLOT0_TEX_UNIT { 35 };
|
||||||
static bool supportedTextureFormat(const gpu::Element& format);
|
|
||||||
explicit GLESBackend(bool syncCache) : Parent(syncCache) {}
|
explicit GLESBackend(bool syncCache) : Parent(syncCache) {}
|
||||||
GLESBackend() : Parent() {}
|
GLESBackend() : Parent() {}
|
||||||
virtual ~GLESBackend() {
|
virtual ~GLESBackend() {
|
||||||
|
@ -41,6 +40,8 @@ public:
|
||||||
resetStages();
|
resetStages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool supportedTextureFormat(const gpu::Element& format) override;
|
||||||
|
|
||||||
static const std::string GLES_VERSION;
|
static const std::string GLES_VERSION;
|
||||||
const std::string& getVersion() const override { return GLES_VERSION; }
|
const std::string& getVersion() const override { return GLES_VERSION; }
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,8 @@ public:
|
||||||
virtual void recycle() const = 0;
|
virtual void recycle() const = 0;
|
||||||
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0;
|
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0;
|
||||||
|
|
||||||
|
virtual bool supportedTextureFormat(const gpu::Element& format) = 0;
|
||||||
|
|
||||||
// Shared header between C++ and GLSL
|
// Shared header between C++ and GLSL
|
||||||
#include "TransformCamera_shared.slh"
|
#include "TransformCamera_shared.slh"
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,10 @@ namespace ktx {
|
||||||
using KeyValues = std::list<KeyValue>;
|
using KeyValues = std::list<KeyValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace khronos { namespace gl { namespace texture {
|
||||||
|
enum class InternalFormat: uint32_t;
|
||||||
|
}}}
|
||||||
|
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
|
||||||
|
|
||||||
|
@ -565,6 +569,7 @@ public:
|
||||||
|
|
||||||
static bool evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header);
|
static bool evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header);
|
||||||
static bool evalTextureFormat(const ktx::Header& header, Element& mipFormat, Element& texelFormat);
|
static bool evalTextureFormat(const ktx::Header& header, Element& mipFormat, Element& texelFormat);
|
||||||
|
static bool getCompressedFormat(khronos::gl::texture::InternalFormat format, Element& elFormat);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const TextureUsageType _usageType;
|
const TextureUsageType _usageType;
|
||||||
|
|
|
@ -619,6 +619,47 @@ bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Texture::getCompressedFormat(ktx::GLInternalFormat format, Element& elFormat) {
|
||||||
|
if (format == ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_BCX_SRGB;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_MASK;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_BCX_SRGBA;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_RED_RGTC1) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_BCX_RED;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_RG_RGTC2) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_BCX_XY;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_HIGH;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_BCX_HDR_RGB;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_RGB8_ETC2) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_ETC2_RGB;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_SRGB8_ETC2) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_ETC2_SRGB;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_RGBA8_ETC2_EAC) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_ETC2_RGBA;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_ETC2_SRGBA;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_R11_EAC) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_EAC_RED;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_SIGNED_R11_EAC) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_EAC_RED_SIGNED;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_RG11_EAC) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_EAC_XY;
|
||||||
|
} else if (format == ktx::GLInternalFormat::COMPRESSED_SIGNED_RG11_EAC) {
|
||||||
|
elFormat = Format::COLOR_COMPRESSED_EAC_XY_SIGNED;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, Element& texelFormat) {
|
bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, Element& texelFormat) {
|
||||||
if (header.getGLFormat() == ktx::GLFormat::BGRA && header.getGLType() == ktx::GLType::UNSIGNED_BYTE && header.getTypeSize() == 1) {
|
if (header.getGLFormat() == ktx::GLFormat::BGRA && header.getGLType() == ktx::GLType::UNSIGNED_BYTE && header.getTypeSize() == 1) {
|
||||||
if (header.getGLInternaFormat() == ktx::GLInternalFormat::RGBA8) {
|
if (header.getGLInternaFormat() == ktx::GLInternalFormat::RGBA8) {
|
||||||
|
@ -661,41 +702,7 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E
|
||||||
mipFormat = Format::COLOR_RGB9E5;
|
mipFormat = Format::COLOR_RGB9E5;
|
||||||
texelFormat = Format::COLOR_RGB9E5;
|
texelFormat = Format::COLOR_RGB9E5;
|
||||||
} else if (header.isCompressed()) {
|
} else if (header.isCompressed()) {
|
||||||
if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT) {
|
if (!getCompressedFormat(header.getGLInternaFormat(), texelFormat)) {
|
||||||
texelFormat = Format::COLOR_COMPRESSED_BCX_SRGB;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_MASK;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RED_RGTC1) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_BCX_RED;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RG_RGTC2) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_BCX_XY;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_HIGH;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_BCX_HDR_RGB;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB8_ETC2) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_RGB;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_ETC2) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGB;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGBA8_ETC2_EAC) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_RGBA;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGBA;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_R11_EAC) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_EAC_RED;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SIGNED_R11_EAC) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_EAC_RED_SIGNED;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RG11_EAC) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_EAC_XY;
|
|
||||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SIGNED_RG11_EAC) {
|
|
||||||
texelFormat = Format::COLOR_COMPRESSED_EAC_XY_SIGNED;
|
|
||||||
} else {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
mipFormat = texelFormat;
|
mipFormat = texelFormat;
|
||||||
|
|
58
libraries/ktx/src/TextureMeta.cpp
Normal file
58
libraries/ktx/src/TextureMeta.cpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
//
|
||||||
|
// TextureMeta.cpp
|
||||||
|
// libraries/shared/src
|
||||||
|
//
|
||||||
|
// Created by Ryan Huffman on 04/10/18.
|
||||||
|
// Copyright 2018 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 "TextureMeta.h"
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
const QString TEXTURE_META_EXTENSION = ".texmeta.json";
|
||||||
|
|
||||||
|
bool TextureMeta::deserialize(const QByteArray& data, TextureMeta* meta) {
|
||||||
|
QJsonParseError error;
|
||||||
|
auto doc = QJsonDocument::fromJson(data, &error);
|
||||||
|
if (!doc.isObject()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto root = doc.object();
|
||||||
|
if (root.contains("original")) {
|
||||||
|
meta->original = root["original"].toString();
|
||||||
|
}
|
||||||
|
if (root.contains("compressed")) {
|
||||||
|
auto compressed = root["compressed"].toObject();
|
||||||
|
for (auto it = compressed.constBegin(); it != compressed.constEnd(); it++) {
|
||||||
|
khronos::gl::texture::InternalFormat format;
|
||||||
|
auto formatName = it.key().toLatin1();
|
||||||
|
if (khronos::gl::texture::fromString(formatName.constData(), &format)) {
|
||||||
|
meta->availableTextureTypes[format] = it.value().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray TextureMeta::serialize() {
|
||||||
|
QJsonDocument doc;
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonObject compressed;
|
||||||
|
|
||||||
|
for (auto kv : availableTextureTypes) {
|
||||||
|
const char* name = khronos::gl::texture::toString(kv.first);
|
||||||
|
compressed[name] = kv.second.toString();
|
||||||
|
}
|
||||||
|
root["original"] = original.toString();
|
||||||
|
root["compressed"] = compressed;
|
||||||
|
doc.setObject(root);
|
||||||
|
|
||||||
|
return doc.toJson();
|
||||||
|
}
|
42
libraries/ktx/src/TextureMeta.h
Normal file
42
libraries/ktx/src/TextureMeta.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// TextureMeta.h
|
||||||
|
// libraries/shared/src
|
||||||
|
//
|
||||||
|
// Created by Ryan Huffman on 04/10/18.
|
||||||
|
// Copyright 2018 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_TextureMeta_h
|
||||||
|
#define hifi_TextureMeta_h
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "khronos/KHR.h"
|
||||||
|
|
||||||
|
extern const QString TEXTURE_META_EXTENSION;
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template<> struct hash<khronos::gl::texture::InternalFormat> {
|
||||||
|
using enum_type = std::underlying_type<khronos::gl::texture::InternalFormat>::type;
|
||||||
|
typedef std::size_t result_type;
|
||||||
|
result_type operator()(khronos::gl::texture::InternalFormat const& v) const noexcept {
|
||||||
|
return std::hash<enum_type>()(static_cast<enum_type>(v));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TextureMeta {
|
||||||
|
static bool deserialize(const QByteArray& data, TextureMeta* meta);
|
||||||
|
QByteArray serialize();
|
||||||
|
|
||||||
|
QUrl original;
|
||||||
|
std::unordered_map<khronos::gl::texture::InternalFormat, QUrl> availableTextureTypes;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // hifi_TextureMeta_h
|
|
@ -10,6 +10,8 @@
|
||||||
#ifndef khronos_khr_hpp
|
#ifndef khronos_khr_hpp
|
||||||
#define khronos_khr_hpp
|
#define khronos_khr_hpp
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace khronos {
|
namespace khronos {
|
||||||
|
|
||||||
namespace gl {
|
namespace gl {
|
||||||
|
@ -209,6 +211,63 @@ namespace khronos {
|
||||||
COMPRESSED_SIGNED_RG11_EAC = 0x9273,
|
COMPRESSED_SIGNED_RG11_EAC = 0x9273,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::unordered_map<std::string, InternalFormat> nameToFormat {
|
||||||
|
{ "COMPRESSED_RED", InternalFormat::COMPRESSED_RED },
|
||||||
|
{ "COMPRESSED_RG", InternalFormat::COMPRESSED_RG },
|
||||||
|
{ "COMPRESSED_RGB", InternalFormat::COMPRESSED_RGB },
|
||||||
|
{ "COMPRESSED_RGBA", InternalFormat::COMPRESSED_RGBA },
|
||||||
|
|
||||||
|
{ "COMPRESSED_SRGB", InternalFormat::COMPRESSED_SRGB },
|
||||||
|
{ "COMPRESSED_SRGB_ALPHA", InternalFormat::COMPRESSED_SRGB_ALPHA },
|
||||||
|
|
||||||
|
{ "COMPRESSED_ETC1_RGB8_OES", InternalFormat::COMPRESSED_ETC1_RGB8_OES },
|
||||||
|
|
||||||
|
{ "COMPRESSED_SRGB_S3TC_DXT1_EXT", InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT },
|
||||||
|
{ "COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT", InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT },
|
||||||
|
{ "COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT", InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT },
|
||||||
|
{ "COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT", InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT },
|
||||||
|
|
||||||
|
{ "COMPRESSED_RED_RGTC1", InternalFormat::COMPRESSED_RED_RGTC1 },
|
||||||
|
{ "COMPRESSED_SIGNED_RED_RGTC1", InternalFormat::COMPRESSED_SIGNED_RED_RGTC1 },
|
||||||
|
{ "COMPRESSED_RG_RGTC2", InternalFormat::COMPRESSED_RG_RGTC2 },
|
||||||
|
{ "COMPRESSED_SIGNED_RG_RGTC2", InternalFormat::COMPRESSED_SIGNED_RG_RGTC2 },
|
||||||
|
|
||||||
|
{ "COMPRESSED_RGBA_BPTC_UNORM", InternalFormat::COMPRESSED_RGBA_BPTC_UNORM },
|
||||||
|
{ "COMPRESSED_SRGB_ALPHA_BPTC_UNORM", InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM },
|
||||||
|
{ "COMPRESSED_RGB_BPTC_SIGNED_FLOAT", InternalFormat::COMPRESSED_RGB_BPTC_SIGNED_FLOAT },
|
||||||
|
{ "COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT", InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT },
|
||||||
|
|
||||||
|
{ "COMPRESSED_RGB8_ETC2", InternalFormat::COMPRESSED_RGB8_ETC2 },
|
||||||
|
{ "COMPRESSED_SRGB8_ETC2", InternalFormat::COMPRESSED_SRGB8_ETC2 },
|
||||||
|
{ "COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2", InternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 },
|
||||||
|
{ "COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2", InternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 },
|
||||||
|
{ "COMPRESSED_RGBA8_ETC2_EAC", InternalFormat::COMPRESSED_RGBA8_ETC2_EAC },
|
||||||
|
{ "COMPRESSED_SRGB8_ALPHA8_ETC2_EAC", InternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC },
|
||||||
|
|
||||||
|
{ "COMPRESSED_R11_EAC", InternalFormat::COMPRESSED_R11_EAC },
|
||||||
|
{ "COMPRESSED_SIGNED_R11_EAC", InternalFormat::COMPRESSED_SIGNED_R11_EAC },
|
||||||
|
{ "COMPRESSED_RG11_EAC", InternalFormat::COMPRESSED_RG11_EAC },
|
||||||
|
{ "COMPRESSED_SIGNED_RG11_EAC", InternalFormat::COMPRESSED_SIGNED_RG11_EAC }
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const char* toString(InternalFormat format) {
|
||||||
|
for (auto& pair : nameToFormat) {
|
||||||
|
if (pair.second == format) {
|
||||||
|
return pair.first.data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool fromString(const char* name, InternalFormat* format) {
|
||||||
|
auto it = nameToFormat.find(name);
|
||||||
|
if (it == nameToFormat.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*format = it->second;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
inline uint8_t evalUncompressedBlockBitSize(InternalFormat format) {
|
inline uint8_t evalUncompressedBlockBitSize(InternalFormat format) {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case InternalFormat::R8:
|
case InternalFormat::R8:
|
||||||
|
|
|
@ -517,10 +517,11 @@ QUrl NetworkMaterial::getTextureUrl(const QUrl& baseUrl, const FBXTexture& textu
|
||||||
// Inlined file: cache under the fbx file to avoid namespace clashes
|
// Inlined file: cache under the fbx file to avoid namespace clashes
|
||||||
// NOTE: We cannot resolve the path because filename may be an absolute path
|
// NOTE: We cannot resolve the path because filename may be an absolute path
|
||||||
assert(texture.filename.size() > 0);
|
assert(texture.filename.size() > 0);
|
||||||
|
auto baseUrlStripped = baseUrl.toDisplayString(QUrl::RemoveFragment | QUrl::RemoveQuery | QUrl::RemoveUserInfo);
|
||||||
if (texture.filename.at(0) == '/') {
|
if (texture.filename.at(0) == '/') {
|
||||||
return baseUrl.toString() + texture.filename;
|
return baseUrlStripped + texture.filename;
|
||||||
} else {
|
} else {
|
||||||
return baseUrl.toString() + '/' + texture.filename;
|
return baseUrlStripped + '/' + texture.filename;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,8 @@
|
||||||
#include <Trace.h>
|
#include <Trace.h>
|
||||||
#include <StatTracker.h>
|
#include <StatTracker.h>
|
||||||
|
|
||||||
|
#include <TextureMeta.h>
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(trace_resource_parse_image, "trace.resource.parse.image")
|
Q_LOGGING_CATEGORY(trace_resource_parse_image, "trace.resource.parse.image")
|
||||||
Q_LOGGING_CATEGORY(trace_resource_parse_image_raw, "trace.resource.parse.image.raw")
|
Q_LOGGING_CATEGORY(trace_resource_parse_image_raw, "trace.resource.parse.image.raw")
|
||||||
Q_LOGGING_CATEGORY(trace_resource_parse_image_ktx, "trace.resource.parse.image.ktx")
|
Q_LOGGING_CATEGORY(trace_resource_parse_image_ktx, "trace.resource.parse.image.ktx")
|
||||||
|
@ -293,7 +295,6 @@ int networkTexturePointerMetaTypeId = qRegisterMetaType<QWeakPointer<NetworkText
|
||||||
NetworkTexture::NetworkTexture(const QUrl& url) :
|
NetworkTexture::NetworkTexture(const QUrl& url) :
|
||||||
Resource(url),
|
Resource(url),
|
||||||
_type(),
|
_type(),
|
||||||
_sourceIsKTX(false),
|
|
||||||
_maxNumPixels(100)
|
_maxNumPixels(100)
|
||||||
{
|
{
|
||||||
_textureSource = std::make_shared<gpu::TextureSource>(url);
|
_textureSource = std::make_shared<gpu::TextureSource>(url);
|
||||||
|
@ -309,17 +310,25 @@ static bool isLocalUrl(const QUrl& url) {
|
||||||
NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) :
|
NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) :
|
||||||
Resource(url),
|
Resource(url),
|
||||||
_type(type),
|
_type(type),
|
||||||
_sourceIsKTX(url.path().endsWith(".ktx")),
|
|
||||||
_maxNumPixels(maxNumPixels)
|
_maxNumPixels(maxNumPixels)
|
||||||
{
|
{
|
||||||
_textureSource = std::make_shared<gpu::TextureSource>(url, (int)type);
|
_textureSource = std::make_shared<gpu::TextureSource>(url, (int)type);
|
||||||
_lowestRequestedMipLevel = 0;
|
_lowestRequestedMipLevel = 0;
|
||||||
|
|
||||||
_shouldFailOnRedirect = !_sourceIsKTX;
|
qDebug() << "Creating networktexture: " << url;
|
||||||
|
if (url.fileName().endsWith(TEXTURE_META_EXTENSION)) {
|
||||||
|
_currentlyLoadingResourceType = ResourceType::META;
|
||||||
|
} else if (url.fileName().endsWith(".ktx")) {
|
||||||
|
_currentlyLoadingResourceType = ResourceType::KTX;
|
||||||
|
} else {
|
||||||
|
_currentlyLoadingResourceType = ResourceType::ORIGINAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
_shouldFailOnRedirect = _currentlyLoadingResourceType != ResourceType::KTX;
|
||||||
|
|
||||||
if (type == image::TextureUsage::CUBE_TEXTURE) {
|
if (type == image::TextureUsage::CUBE_TEXTURE) {
|
||||||
setLoadPriority(this, SKYBOX_LOAD_PRIORITY);
|
setLoadPriority(this, SKYBOX_LOAD_PRIORITY);
|
||||||
} else if (_sourceIsKTX) {
|
} else if (_currentlyLoadingResourceType == ResourceType::KTX) {
|
||||||
setLoadPriority(this, HIGH_MIPS_LOAD_PRIORITY);
|
setLoadPriority(this, HIGH_MIPS_LOAD_PRIORITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,7 +339,7 @@ NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type,
|
||||||
// if we have content, load it after we have our self pointer
|
// if we have content, load it after we have our self pointer
|
||||||
if (!content.isEmpty()) {
|
if (!content.isEmpty()) {
|
||||||
_startedLoading = true;
|
_startedLoading = true;
|
||||||
QMetaObject::invokeMethod(this, "loadContent", Qt::QueuedConnection, Q_ARG(const QByteArray&, content));
|
QMetaObject::invokeMethod(this, "downloadFinished", Qt::QueuedConnection, Q_ARG(const QByteArray&, content));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,12 +402,13 @@ NetworkTexture::~NetworkTexture() {
|
||||||
|
|
||||||
const uint16_t NetworkTexture::NULL_MIP_LEVEL = std::numeric_limits<uint16_t>::max();
|
const uint16_t NetworkTexture::NULL_MIP_LEVEL = std::numeric_limits<uint16_t>::max();
|
||||||
void NetworkTexture::makeRequest() {
|
void NetworkTexture::makeRequest() {
|
||||||
if (!_sourceIsKTX) {
|
qDebug() << "In makeRequest for " << _activeUrl << (int)_currentlyLoadingResourceType;
|
||||||
|
if (_currentlyLoadingResourceType != ResourceType::KTX) {
|
||||||
Resource::makeRequest();
|
Resource::makeRequest();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLocalUrl(_url)) {
|
if (isLocalUrl(_activeUrl)) {
|
||||||
auto self = _self;
|
auto self = _self;
|
||||||
QtConcurrent::run(QThreadPool::globalInstance(), [self] {
|
QtConcurrent::run(QThreadPool::globalInstance(), [self] {
|
||||||
auto resource = self.lock();
|
auto resource = self.lock();
|
||||||
|
@ -444,6 +454,7 @@ void NetworkTexture::makeRequest() {
|
||||||
|
|
||||||
_ktxHeaderRequest->send();
|
_ktxHeaderRequest->send();
|
||||||
|
|
||||||
|
qDebug() << "Starting mip range request";
|
||||||
startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL);
|
startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL);
|
||||||
} else if (_ktxResourceState == PENDING_MIP_REQUEST) {
|
} else if (_ktxResourceState == PENDING_MIP_REQUEST) {
|
||||||
if (_lowestKnownPopulatedMip > 0) {
|
if (_lowestKnownPopulatedMip > 0) {
|
||||||
|
@ -466,12 +477,12 @@ void NetworkTexture::handleLocalRequestCompleted() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkTexture::makeLocalRequest() {
|
void NetworkTexture::makeLocalRequest() {
|
||||||
const QString scheme = _url.scheme();
|
const QString scheme = _activeUrl.scheme();
|
||||||
QString path;
|
QString path;
|
||||||
if (scheme == URL_SCHEME_FILE) {
|
if (scheme == URL_SCHEME_FILE) {
|
||||||
path = PathUtils::expandToLocalDataAbsolutePath(_url).toLocalFile();
|
path = PathUtils::expandToLocalDataAbsolutePath(_activeUrl).toLocalFile();
|
||||||
} else {
|
} else {
|
||||||
path = ":" + _url.path();
|
path = ":" + _activeUrl.path();
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(this, &Resource::finished, this, &NetworkTexture::handleLocalRequestCompleted);
|
connect(this, &Resource::finished, this, &NetworkTexture::handleLocalRequestCompleted);
|
||||||
|
@ -497,7 +508,7 @@ void NetworkTexture::makeLocalRequest() {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (found == ktxDescriptor->keyValues.end() || found->_value.size() != gpu::SOURCE_HASH_BYTES) {
|
if (found == ktxDescriptor->keyValues.end() || found->_value.size() != gpu::SOURCE_HASH_BYTES) {
|
||||||
hash = _url.toString().toLocal8Bit().toHex().toStdString();
|
hash = _activeUrl.toString().toLocal8Bit().toHex().toStdString();
|
||||||
} else {
|
} else {
|
||||||
// at this point the source hash is in binary 16-byte form
|
// at this point the source hash is in binary 16-byte form
|
||||||
// and we need it in a hexadecimal string
|
// and we need it in a hexadecimal string
|
||||||
|
@ -536,11 +547,13 @@ void NetworkTexture::makeLocalRequest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NetworkTexture::handleFailedRequest(ResourceRequest::Result result) {
|
bool NetworkTexture::handleFailedRequest(ResourceRequest::Result result) {
|
||||||
if (!_sourceIsKTX && result == ResourceRequest::Result::RedirectFail) {
|
if (_currentlyLoadingResourceType != ResourceType::KTX
|
||||||
|
&& result == ResourceRequest::Result::RedirectFail) {
|
||||||
|
|
||||||
auto newPath = _request->getRelativePathUrl();
|
auto newPath = _request->getRelativePathUrl();
|
||||||
if (newPath.fileName().endsWith(".ktx")) {
|
if (newPath.fileName().endsWith(".ktx")) {
|
||||||
qDebug() << "Redirecting to" << newPath << "from" << _url;
|
qDebug() << "Redirecting to" << newPath << "from" << _url;
|
||||||
_sourceIsKTX = true;
|
_currentlyLoadingResourceType = ResourceType::KTX;
|
||||||
_activeUrl = newPath;
|
_activeUrl = newPath;
|
||||||
_shouldFailOnRedirect = false;
|
_shouldFailOnRedirect = false;
|
||||||
makeRequest();
|
makeRequest();
|
||||||
|
@ -581,6 +594,7 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
|
||||||
|
|
||||||
bool isHighMipRequest = low == NULL_MIP_LEVEL && high == NULL_MIP_LEVEL;
|
bool isHighMipRequest = low == NULL_MIP_LEVEL && high == NULL_MIP_LEVEL;
|
||||||
|
|
||||||
|
qDebug() << "Making ktx mip request to: " << _activeUrl;
|
||||||
_ktxMipRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(this, _activeUrl);
|
_ktxMipRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(this, _activeUrl);
|
||||||
|
|
||||||
if (!_ktxMipRequest) {
|
if (!_ktxMipRequest) {
|
||||||
|
@ -930,11 +944,79 @@ void NetworkTexture::handleFinishedInitialLoad() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkTexture::downloadFinished(const QByteArray& data) {
|
void NetworkTexture::downloadFinished(const QByteArray& data) {
|
||||||
loadContent(data);
|
qDebug() << "Loading content: " << _activeUrl;
|
||||||
|
if (_currentlyLoadingResourceType == ResourceType::META) {
|
||||||
|
qDebug() << "Loading meta content: " << _activeUrl;
|
||||||
|
loadMetaContent(data);
|
||||||
|
} else if (_currentlyLoadingResourceType == ResourceType::ORIGINAL) {
|
||||||
|
loadTextureContent(data);
|
||||||
|
} else {
|
||||||
|
TextureCache::requestCompleted(_self);
|
||||||
|
Resource::handleFailedRequest(ResourceRequest::Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkTexture::loadContent(const QByteArray& content) {
|
void NetworkTexture::loadMetaContent(const QByteArray& content) {
|
||||||
if (_sourceIsKTX) {
|
if (_currentlyLoadingResourceType != ResourceType::META) {
|
||||||
|
qWarning() << "Trying to load meta content when current resource type is not META";
|
||||||
|
assert(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureMeta meta;
|
||||||
|
if (!TextureMeta::deserialize(content, &meta)) {
|
||||||
|
qWarning() << "Failed to read texture meta from " << _url;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
auto& backend = DependencyManager::get<TextureCache>()->getGPUContext()->getBackend();
|
||||||
|
for (auto pair : meta.availableTextureTypes) {
|
||||||
|
gpu::Element elFormat;
|
||||||
|
|
||||||
|
if (gpu::Texture::getCompressedFormat(pair.first, elFormat)) {
|
||||||
|
if (backend->supportedTextureFormat(elFormat)) {
|
||||||
|
auto url = pair.second;
|
||||||
|
if (url.fileName().endsWith(TEXTURE_META_EXTENSION)) {
|
||||||
|
qWarning() << "Found a texture meta URL inside of the texture meta file at" << _activeUrl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentlyLoadingResourceType = ResourceType::KTX;
|
||||||
|
_activeUrl = _activeUrl.resolved(url);
|
||||||
|
qDebug() << "Active url is now: " << _activeUrl;
|
||||||
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
|
auto self = _self.lock();
|
||||||
|
if (!self) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QMetaObject::invokeMethod(this, "attemptRequest", Qt::QueuedConnection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!meta.original.isEmpty()) {
|
||||||
|
_currentlyLoadingResourceType = ResourceType::ORIGINAL;
|
||||||
|
_activeUrl = _activeUrl.resolved(meta.original);
|
||||||
|
|
||||||
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
|
auto self = _self.lock();
|
||||||
|
if (!self) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QMetaObject::invokeMethod(this, "attemptRequest", Qt::QueuedConnection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "Failed to find supported texture type in " << _activeUrl;
|
||||||
|
//TextureCache::requestCompleted(_self);
|
||||||
|
Resource::handleFailedRequest(ResourceRequest::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkTexture::loadTextureContent(const QByteArray& content) {
|
||||||
|
if (_currentlyLoadingResourceType != ResourceType::ORIGINAL) {
|
||||||
|
qWarning() << "Trying to load texture content when currentl resource type is not ORIGINAL";
|
||||||
assert(false);
|
assert(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,9 @@
|
||||||
#include <graphics/TextureMap.h>
|
#include <graphics/TextureMap.h>
|
||||||
#include <image/Image.h>
|
#include <image/Image.h>
|
||||||
#include <ktx/KTX.h>
|
#include <ktx/KTX.h>
|
||||||
|
#include <TextureMeta.h>
|
||||||
|
|
||||||
|
#include <gpu/Context.h>
|
||||||
#include "KTXCache.h"
|
#include "KTXCache.h"
|
||||||
|
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
@ -75,11 +77,13 @@ protected:
|
||||||
|
|
||||||
virtual bool isCacheable() const override { return _loaded; }
|
virtual bool isCacheable() const override { return _loaded; }
|
||||||
|
|
||||||
virtual void downloadFinished(const QByteArray& data) override;
|
Q_INVOKABLE virtual void downloadFinished(const QByteArray& data) override;
|
||||||
|
|
||||||
bool handleFailedRequest(ResourceRequest::Result result) override;
|
bool handleFailedRequest(ResourceRequest::Result result) override;
|
||||||
|
|
||||||
Q_INVOKABLE void loadContent(const QByteArray& content);
|
Q_INVOKABLE void loadMetaContent(const QByteArray& content);
|
||||||
|
Q_INVOKABLE void loadTextureContent(const QByteArray& content);
|
||||||
|
|
||||||
Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight);
|
Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight);
|
||||||
|
|
||||||
Q_INVOKABLE void startRequestForNextMipLevel();
|
Q_INVOKABLE void startRequestForNextMipLevel();
|
||||||
|
@ -93,6 +97,14 @@ private:
|
||||||
|
|
||||||
image::TextureUsage::Type _type;
|
image::TextureUsage::Type _type;
|
||||||
|
|
||||||
|
enum class ResourceType {
|
||||||
|
META,
|
||||||
|
ORIGINAL,
|
||||||
|
KTX
|
||||||
|
};
|
||||||
|
|
||||||
|
ResourceType _currentlyLoadingResourceType { ResourceType::META };
|
||||||
|
|
||||||
static const uint16_t NULL_MIP_LEVEL;
|
static const uint16_t NULL_MIP_LEVEL;
|
||||||
enum KTXResourceState {
|
enum KTXResourceState {
|
||||||
PENDING_INITIAL_LOAD = 0,
|
PENDING_INITIAL_LOAD = 0,
|
||||||
|
@ -103,7 +115,6 @@ private:
|
||||||
FAILED_TO_LOAD
|
FAILED_TO_LOAD
|
||||||
};
|
};
|
||||||
|
|
||||||
bool _sourceIsKTX { false };
|
|
||||||
KTXResourceState _ktxResourceState { PENDING_INITIAL_LOAD };
|
KTXResourceState _ktxResourceState { PENDING_INITIAL_LOAD };
|
||||||
|
|
||||||
// The current mips that are currently being requested w/ _ktxMipRequest
|
// The current mips that are currently being requested w/ _ktxMipRequest
|
||||||
|
@ -236,6 +247,9 @@ public:
|
||||||
static const int DEFAULT_SPECTATOR_CAM_WIDTH { 2048 };
|
static const int DEFAULT_SPECTATOR_CAM_WIDTH { 2048 };
|
||||||
static const int DEFAULT_SPECTATOR_CAM_HEIGHT { 1024 };
|
static const int DEFAULT_SPECTATOR_CAM_HEIGHT { 1024 };
|
||||||
|
|
||||||
|
void setGPUContext(const gpu::ContextPointer& context) { _gpuContext = context; }
|
||||||
|
gpu::ContextPointer getGPUContext() const { return _gpuContext; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @function TextureCache.spectatorCameraFramebufferReset
|
* @function TextureCache.spectatorCameraFramebufferReset
|
||||||
|
@ -268,6 +282,8 @@ private:
|
||||||
static const std::string KTX_DIRNAME;
|
static const std::string KTX_DIRNAME;
|
||||||
static const std::string KTX_EXT;
|
static const std::string KTX_EXT;
|
||||||
|
|
||||||
|
gpu::ContextPointer _gpuContext { nullptr };
|
||||||
|
|
||||||
std::shared_ptr<cache::FileCache> _ktxCache { std::make_shared<KTXCache>(KTX_DIRNAME, KTX_EXT) };
|
std::shared_ptr<cache::FileCache> _ktxCache { std::make_shared<KTXCache>(KTX_DIRNAME, KTX_EXT) };
|
||||||
|
|
||||||
// Map from image hashes to texture weak pointers
|
// Map from image hashes to texture weak pointers
|
||||||
|
|
|
@ -581,6 +581,7 @@ void Resource::refresh() {
|
||||||
ResourceCache::requestCompleted(_self);
|
ResourceCache::requestCompleted(_self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_activeUrl = _url;
|
||||||
init();
|
init();
|
||||||
ensureLoading();
|
ensureLoading();
|
||||||
emit onRefresh();
|
emit onRefresh();
|
||||||
|
@ -618,7 +619,7 @@ void Resource::init(bool resetLoaded) {
|
||||||
_loaded = false;
|
_loaded = false;
|
||||||
}
|
}
|
||||||
_attempts = 0;
|
_attempts = 0;
|
||||||
_activeUrl = _url;
|
qDebug() << "Initting resource: " << _url;
|
||||||
|
|
||||||
if (_url.isEmpty()) {
|
if (_url.isEmpty()) {
|
||||||
_startedLoading = _loaded = true;
|
_startedLoading = _loaded = true;
|
||||||
|
@ -671,6 +672,7 @@ void Resource::makeRequest() {
|
||||||
|
|
||||||
PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } });
|
PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } });
|
||||||
|
|
||||||
|
qDebug() << "Making request to " << _activeUrl;
|
||||||
_request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, _activeUrl);
|
_request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, _activeUrl);
|
||||||
|
|
||||||
if (!_request) {
|
if (!_request) {
|
||||||
|
@ -724,7 +726,7 @@ void Resource::handleReplyFinished() {
|
||||||
auto result = _request->getResult();
|
auto result = _request->getResult();
|
||||||
if (result == ResourceRequest::Success) {
|
if (result == ResourceRequest::Success) {
|
||||||
auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString());
|
auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString());
|
||||||
qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo);
|
qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_activeUrl.toDisplayString(), extraInfo);
|
||||||
|
|
||||||
auto relativePathURL = _request->getRelativePathUrl();
|
auto relativePathURL = _request->getRelativePathUrl();
|
||||||
if (!relativePathURL.isEmpty()) {
|
if (!relativePathURL.isEmpty()) {
|
||||||
|
|
|
@ -464,7 +464,7 @@ bool DomainBaker::rewriteSkyboxURL(QJsonValueRef urlValue, TextureBaker* baker)
|
||||||
if (oldSkyboxURL.matches(baker->getTextureURL(), QUrl::RemoveQuery | QUrl::RemoveFragment)) {
|
if (oldSkyboxURL.matches(baker->getTextureURL(), QUrl::RemoveQuery | QUrl::RemoveFragment)) {
|
||||||
// change the URL to point to the baked texture with its original query and fragment
|
// change the URL to point to the baked texture with its original query and fragment
|
||||||
|
|
||||||
auto newSkyboxURL = _destinationPath.resolved(baker->getBakedTextureFileName());
|
auto newSkyboxURL = _destinationPath.resolved(baker->getMetaTextureFileName());
|
||||||
newSkyboxURL.setQuery(oldSkyboxURL.query());
|
newSkyboxURL.setQuery(oldSkyboxURL.query());
|
||||||
newSkyboxURL.setFragment(oldSkyboxURL.fragment());
|
newSkyboxURL.setFragment(oldSkyboxURL.fragment());
|
||||||
newSkyboxURL.setUserInfo(oldSkyboxURL.userInfo());
|
newSkyboxURL.setUserInfo(oldSkyboxURL.userInfo());
|
||||||
|
|
Loading…
Reference in a new issue