mirror of
https://github.com/lubosz/overte.git
synced 2025-04-05 21:22:00 +02:00
Merge pull request #12956 from huffman/feat/client-texture-selection-baking
Add support for client texture selection
This commit is contained in:
commit
ec354620db
27 changed files with 554 additions and 182 deletions
|
@ -36,10 +36,11 @@ enum class BakedAssetType : int {
|
|||
Undefined
|
||||
};
|
||||
|
||||
// ATTENTION! If you change the current version for an asset type, you will also
|
||||
// need to update the function currentBakeVersionForAssetType() inside of AssetServer.cpp.
|
||||
// ATTENTION! Do not remove baking versions, and do not reorder them. If you add
|
||||
// a new value, it will immediately become the "current" version.
|
||||
enum class ModelBakeVersion : BakeVersion {
|
||||
Initial = INITIAL_BAKE_VERSION,
|
||||
MetaTextureJson,
|
||||
|
||||
COUNT
|
||||
};
|
||||
|
@ -47,6 +48,7 @@ enum class ModelBakeVersion : BakeVersion {
|
|||
// ATTENTION! See above.
|
||||
enum class TextureBakeVersion : BakeVersion {
|
||||
Initial = INITIAL_BAKE_VERSION,
|
||||
MetaTextureJson,
|
||||
|
||||
COUNT
|
||||
};
|
||||
|
|
|
@ -1324,6 +1324,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
// Create the main thread context, the GPU backend, and the display plugins
|
||||
initializeGL();
|
||||
DependencyManager::get<TextureCache>()->setGPUContext(_gpuContext);
|
||||
qCDebug(interfaceapp, "Initialized Display.");
|
||||
// Create the rendering engine. This can be slow on some machines due to lots of
|
||||
// GPU pipeline creation.
|
||||
|
|
|
@ -70,13 +70,6 @@ void FBXBaker::bakeSourceCopy() {
|
|||
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)
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
|
@ -352,27 +345,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>
|
||||
|
||||
static const QString BAKED_FBX_EXTENSION = ".baked.fbx";
|
||||
|
||||
using TextureBakerThreadGetter = std::function<QThread*()>;
|
||||
|
||||
class FBXBaker : public ModelBaker {
|
||||
|
@ -51,11 +49,11 @@ private:
|
|||
void loadSourceFBX();
|
||||
|
||||
void importScene();
|
||||
void embedTextureMetaData();
|
||||
void rewriteAndBakeSceneModels();
|
||||
void rewriteAndBakeSceneTextures();
|
||||
void exportScene();
|
||||
|
||||
FBXNode _rootNode;
|
||||
FBXGeometry* _geometry;
|
||||
QHash<QString, int> _textureNameMatchCount;
|
||||
QHash<QUrl, QString> _remappedTexturePaths;
|
||||
|
|
|
@ -246,9 +246,9 @@ bool ModelBaker::compressMesh(FBXMesh& mesh, bool hasDeformers, FBXNode& dracoMe
|
|||
|
||||
QString ModelBaker::compressTexture(QString modelTextureFileName, image::TextureUsage::Type textureType) {
|
||||
|
||||
QFileInfo modelTextureFileInfo{ modelTextureFileName.replace("\\", "/") };
|
||||
QFileInfo modelTextureFileInfo { modelTextureFileName.replace("\\", "/") };
|
||||
|
||||
if (modelTextureFileInfo.suffix() == BAKED_TEXTURE_EXT.mid(1)) {
|
||||
if (modelTextureFileInfo.suffix().toLower() == BAKED_TEXTURE_KTX_EXT.mid(1)) {
|
||||
// re-baking a model that already references baked textures
|
||||
// this is an error - return from here
|
||||
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());
|
||||
|
||||
QString bakedTextureFileName;
|
||||
QString baseTextureFileName;
|
||||
if (_remappedTexturePaths.contains(urlToTexture)) {
|
||||
bakedTextureFileName = _remappedTexturePaths[urlToTexture];
|
||||
baseTextureFileName = _remappedTexturePaths[urlToTexture];
|
||||
} else {
|
||||
// construct the new baked texture file name and file path
|
||||
// ensuring that the baked texture will have a unique name
|
||||
// even if there was another texture with the same name at a different path
|
||||
bakedTextureFileName = createBakedTextureFileName(modelTextureFileInfo);
|
||||
_remappedTexturePaths[urlToTexture] = bakedTextureFileName;
|
||||
baseTextureFileName = createBaseTextureFileName(modelTextureFileInfo);
|
||||
_remappedTexturePaths[urlToTexture] = baseTextureFileName;
|
||||
}
|
||||
|
||||
qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName
|
||||
<< "to" << bakedTextureFileName;
|
||||
<< "to" << baseTextureFileName;
|
||||
|
||||
QString bakedTextureFilePath{
|
||||
_bakedOutputDir + "/" + bakedTextureFileName
|
||||
QString bakedTextureFilePath {
|
||||
_bakedOutputDir + "/" + baseTextureFileName + BAKED_META_TEXTURE_SUFFIX
|
||||
};
|
||||
|
||||
textureChild = bakedTextureFileName;
|
||||
textureChild = baseTextureFileName + BAKED_META_TEXTURE_SUFFIX;
|
||||
|
||||
if (!_bakingTextures.contains(urlToTexture)) {
|
||||
_outputFiles.push_back(bakedTextureFilePath);
|
||||
|
||||
// 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
|
||||
QSharedPointer<TextureBaker> bakingTexture{
|
||||
new TextureBaker(textureURL, textureType, outputDir, bakedFilename, textureContent),
|
||||
new TextureBaker(textureURL, textureType, outputDir, "../", bakedFilename, textureContent),
|
||||
&TextureBaker::deleteLater
|
||||
};
|
||||
|
||||
|
@ -484,30 +484,30 @@ void ModelBaker::checkIfTexturesFinished() {
|
|||
} else {
|
||||
qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL;
|
||||
|
||||
texturesFinished();
|
||||
|
||||
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
|
||||
// in case another texture referenced by this model has the same base name
|
||||
auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()];
|
||||
|
||||
QString bakedTextureFileName{ textureFileInfo.completeBaseName() };
|
||||
QString baseTextureFileName{ textureFileInfo.completeBaseName() };
|
||||
|
||||
if (nameMatches > 0) {
|
||||
// there are already nameMatches texture with this name
|
||||
// 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
|
||||
++nameMatches;
|
||||
|
||||
return bakedTextureFileName;
|
||||
return baseTextureFileName;
|
||||
}
|
||||
|
||||
void ModelBaker::setWasAborted(bool wasAborted) {
|
||||
|
@ -519,3 +519,91 @@ 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());
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
};
|
||||
|
||||
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 GetMaterialIDCallback = std::function <int(int)>;
|
||||
|
||||
static const QString BAKED_FBX_EXTENSION = ".baked.fbx";
|
||||
|
||||
class ModelBaker : public Baker {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -49,7 +51,11 @@ public slots:
|
|||
|
||||
protected:
|
||||
void checkIfTexturesFinished();
|
||||
void texturesFinished();
|
||||
void embedTextureMetaData();
|
||||
void exportScene();
|
||||
|
||||
FBXNode _rootNode;
|
||||
QHash<QByteArray, QByteArray> _textureContentMap;
|
||||
QUrl _modelURL;
|
||||
QString _bakedOutputDir;
|
||||
|
@ -63,7 +69,7 @@ private slots:
|
|||
void handleAbortedTexture();
|
||||
|
||||
private:
|
||||
QString createBakedTextureFileName(const QFileInfo & textureFileInfo);
|
||||
QString createBaseTextureFileName(const QFileInfo & textureFileInfo);
|
||||
QUrl getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded = false);
|
||||
void bakeTexture(const QUrl & textureURL, image::TextureUsage::Type textureType, const QDir & outputDir,
|
||||
const QString & bakedFilename, const QByteArray & textureContent);
|
||||
|
|
|
@ -147,31 +147,7 @@ void OBJBaker::bakeOBJ() {
|
|||
auto geometry = reader.readOBJ(objData, QVariantHash(), combineParts, _modelURL);
|
||||
|
||||
// Write OBJ Data as FBX tree nodes
|
||||
FBXNode rootNode;
|
||||
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;
|
||||
createFBXNodeTree(_rootNode, *geometry);
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
|
@ -203,15 +179,17 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
|||
globalSettingsNode.children = { properties70Node };
|
||||
|
||||
// Generating Object node
|
||||
_objectNode.name = OBJECTS_NODE_NAME;
|
||||
FBXNode objectNode;
|
||||
objectNode.name = OBJECTS_NODE_NAME;
|
||||
|
||||
// Generating Object node's child - Geometry node
|
||||
FBXNode geometryNode;
|
||||
geometryNode.name = GEOMETRY_NODE_NAME;
|
||||
NodeID geometryID;
|
||||
{
|
||||
_geometryID = nextNodeID();
|
||||
geometryID = nextNodeID();
|
||||
geometryNode.properties = {
|
||||
_geometryID,
|
||||
geometryID,
|
||||
GEOMETRY_NODE_NAME,
|
||||
MESH
|
||||
};
|
||||
|
@ -226,12 +204,13 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
|||
// Generating Object node's child - Model node
|
||||
FBXNode modelNode;
|
||||
modelNode.name = MODEL_NODE_NAME;
|
||||
NodeID modelID;
|
||||
{
|
||||
_modelID = nextNodeID();
|
||||
modelNode.properties = { _modelID, MODEL_NODE_NAME, MESH };
|
||||
modelID = nextNodeID();
|
||||
modelNode.properties = { modelID, MODEL_NODE_NAME, MESH };
|
||||
}
|
||||
|
||||
_objectNode.children = { geometryNode, modelNode };
|
||||
objectNode.children = { geometryNode, modelNode };
|
||||
|
||||
// Generating Objects node's child - Material node
|
||||
auto& meshParts = geometry.meshes[0].parts;
|
||||
|
@ -247,7 +226,7 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
|||
setMaterialNodeProperties(materialNode, meshPart.materialID, geometry);
|
||||
}
|
||||
|
||||
_objectNode.children.append(materialNode);
|
||||
objectNode.children.append(materialNode);
|
||||
}
|
||||
|
||||
// Generating Texture Node
|
||||
|
@ -257,13 +236,13 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
|||
QString material = meshParts[i].materialID;
|
||||
FBXMaterial currentMaterial = geometry.materials[material];
|
||||
if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) {
|
||||
_textureID = nextNodeID();
|
||||
_mapTextureMaterial.emplace_back(_textureID, i);
|
||||
auto textureID = nextNodeID();
|
||||
_mapTextureMaterial.emplace_back(textureID, i);
|
||||
|
||||
FBXNode textureNode;
|
||||
{
|
||||
textureNode.name = TEXTURE_NODE_NAME;
|
||||
textureNode.properties = { _textureID };
|
||||
textureNode.properties = { textureID, "texture" + QString::number(textureID) };
|
||||
}
|
||||
|
||||
// Texture node child - TextureName node
|
||||
|
@ -295,7 +274,7 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
|||
|
||||
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
|
||||
FBXNode cNode;
|
||||
cNode.name = C_NODE_NAME;
|
||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, _geometryID, _modelID };
|
||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, geometryID, modelID };
|
||||
connectionsNode.children = { cNode };
|
||||
|
||||
// connect all materials to model
|
||||
for (auto& materialID : _materialIDs) {
|
||||
FBXNode cNode;
|
||||
cNode.name = C_NODE_NAME;
|
||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, materialID, _modelID };
|
||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, materialID, modelID };
|
||||
connectionsNode.children.append(cNode);
|
||||
}
|
||||
|
||||
|
@ -341,7 +320,7 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
|||
}
|
||||
|
||||
// Make all generated nodes children of rootNode
|
||||
rootNode.children = { globalSettingsNode, _objectNode, connectionsNode };
|
||||
rootNode.children = { globalSettingsNode, objectNode, connectionsNode };
|
||||
}
|
||||
|
||||
// Set properties for material nodes
|
||||
|
|
|
@ -43,12 +43,9 @@ private:
|
|||
void setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry);
|
||||
NodeID nextNodeID() { return _nodeID++; }
|
||||
|
||||
|
||||
NodeID _nodeID { 0 };
|
||||
NodeID _geometryID;
|
||||
NodeID _modelID;
|
||||
std::vector<NodeID> _materialIDs;
|
||||
NodeID _textureID;
|
||||
std::vector<std::pair<NodeID, int>> _mapTextureMaterial;
|
||||
FBXNode _objectNode;
|
||||
};
|
||||
#endif // hifi_OBJBaker_h
|
||||
|
|
|
@ -18,26 +18,30 @@
|
|||
#include <ktx/KTX.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <TextureMeta.h>
|
||||
|
||||
#include "ModelBakingLoggingCategory.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,
|
||||
const QDir& outputDirectory, const QString& bakedFilename,
|
||||
const QByteArray& textureContent) :
|
||||
const QDir& outputDirectory, const QString& metaTexturePathPrefix,
|
||||
const QString& baseFilename, const QByteArray& textureContent) :
|
||||
_textureURL(textureURL),
|
||||
_originalTexture(textureContent),
|
||||
_textureType(textureType),
|
||||
_baseFilename(baseFilename),
|
||||
_outputDirectory(outputDirectory),
|
||||
_bakedTextureFileName(bakedFilename)
|
||||
_metaTexturePathPrefix(metaTexturePathPrefix)
|
||||
{
|
||||
if (bakedFilename.isEmpty()) {
|
||||
if (baseFilename.isEmpty()) {
|
||||
// figure out the baked texture 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);
|
||||
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 original texture for " + _textureURL.toString());
|
||||
return;
|
||||
}
|
||||
_outputFiles.push_back(filePath);
|
||||
meta.original = _metaTexturePathPrefix +_textureURL.fileName();
|
||||
}
|
||||
|
||||
// IMPORTANT: _originalTexture is empty past this point
|
||||
auto processedTexture = image::processImage(std::move(_originalTexture), _textureURL.toString().toStdString(),
|
||||
ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, _abortProcessing);
|
||||
|
@ -140,17 +157,38 @@ void TextureBaker::processTexture() {
|
|||
return;
|
||||
}
|
||||
|
||||
const char* data = reinterpret_cast<const char*>(memKTX->_storage->data());
|
||||
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;
|
||||
}
|
||||
|
||||
// attempt to write the baked texture to the destination file path
|
||||
auto filePath = _outputDirectory.absoluteFilePath(_bakedTextureFileName);
|
||||
QFile bakedTextureFile { filePath };
|
||||
{
|
||||
const char* data = reinterpret_cast<const char*>(memKTX->_storage->data());
|
||||
const size_t length = memKTX->_storage->size();
|
||||
|
||||
if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) {
|
||||
handleError("Could not write baked texture for " + _textureURL.toString());
|
||||
} else {
|
||||
auto fileName = _baseFilename + BAKED_TEXTURE_BCN_SUFFIX;
|
||||
auto filePath = _outputDirectory.absoluteFilePath(fileName);
|
||||
QFile bakedTextureFile { filePath };
|
||||
if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) {
|
||||
handleError("Could not write baked texture for " + _textureURL.toString());
|
||||
return;
|
||||
}
|
||||
_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;
|
||||
|
|
|
@ -21,22 +21,22 @@
|
|||
|
||||
#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 {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType,
|
||||
const QDir& outputDirectory, const QString& bakedFilename = QString(),
|
||||
const QByteArray& textureContent = QByteArray());
|
||||
const QDir& outputDirectory, const QString& metaTexturePathPrefix = "",
|
||||
const QString& baseFilename = QString(), const QByteArray& textureContent = QByteArray());
|
||||
|
||||
const QByteArray& getOriginalTexture() const { return _originalTexture; }
|
||||
|
||||
QUrl getTextureURL() const { return _textureURL; }
|
||||
|
||||
QString getDestinationFilePath() const { return _outputDirectory.absoluteFilePath(_bakedTextureFileName); }
|
||||
QString getBakedTextureFileName() const { return _bakedTextureFileName; }
|
||||
QString getMetaTextureFileName() const { return _metaTextureFileName; }
|
||||
|
||||
virtual void setWasAborted(bool wasAborted) override;
|
||||
|
||||
|
@ -58,8 +58,10 @@ private:
|
|||
QByteArray _originalTexture;
|
||||
image::TextureUsage::Type _textureType;
|
||||
|
||||
QString _baseFilename;
|
||||
QDir _outputDirectory;
|
||||
QString _bakedTextureFileName;
|
||||
QString _metaTextureFileName;
|
||||
QString _metaTexturePathPrefix;
|
||||
|
||||
std::atomic<bool> _abortProcessing { false };
|
||||
};
|
||||
|
|
|
@ -52,6 +52,8 @@ public:
|
|||
static const std::string GL41_VERSION;
|
||||
const std::string& getVersion() const override { return GL41_VERSION; }
|
||||
|
||||
bool supportedTextureFormat(const gpu::Element& format) override;
|
||||
|
||||
class GL41Texture : public GLTexture {
|
||||
using Parent = GLTexture;
|
||||
friend class GL41Backend;
|
||||
|
@ -173,8 +175,6 @@ protected:
|
|||
void makeProgramBindings(ShaderObject& shaderObject) 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;
|
||||
const std::string& getVersion() const override { return GL45_VERSION; }
|
||||
|
||||
bool supportedTextureFormat(const gpu::Element& format) override;
|
||||
|
||||
class GL45Texture : public GLTexture {
|
||||
using Parent = GLTexture;
|
||||
friend class GL45Backend;
|
||||
|
|
|
@ -32,6 +32,24 @@ using namespace gpu::gl45;
|
|||
#define FORCE_STRICT_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) {
|
||||
if (!texturePointer) {
|
||||
return nullptr;
|
||||
|
|
|
@ -32,7 +32,6 @@ public:
|
|||
static const GLint RESOURCE_TRANSFER_EXTRA_TEX_UNIT { 33 };
|
||||
static const GLint RESOURCE_BUFFER_TEXBUF_TEX_UNIT { 34 };
|
||||
static const GLint RESOURCE_BUFFER_SLOT0_TEX_UNIT { 35 };
|
||||
static bool supportedTextureFormat(const gpu::Element& format);
|
||||
explicit GLESBackend(bool syncCache) : Parent(syncCache) {}
|
||||
GLESBackend() : Parent() {}
|
||||
virtual ~GLESBackend() {
|
||||
|
@ -40,6 +39,8 @@ public:
|
|||
// which is pure virtual from GLBackend's dtor.
|
||||
resetStages();
|
||||
}
|
||||
|
||||
bool supportedTextureFormat(const gpu::Element& format) override;
|
||||
|
||||
static const std::string GLES_VERSION;
|
||||
const std::string& getVersion() const override { return GLES_VERSION; }
|
||||
|
|
|
@ -64,6 +64,8 @@ public:
|
|||
virtual void recycle() const = 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
|
||||
#include "TransformCamera_shared.slh"
|
||||
|
||||
|
|
|
@ -37,6 +37,10 @@ namespace ktx {
|
|||
using KeyValues = std::list<KeyValue>;
|
||||
}
|
||||
|
||||
namespace khronos { namespace gl { namespace texture {
|
||||
enum class InternalFormat: uint32_t;
|
||||
}}}
|
||||
|
||||
namespace gpu {
|
||||
|
||||
|
||||
|
@ -565,6 +569,7 @@ public:
|
|||
|
||||
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 getCompressedFormat(khronos::gl::texture::InternalFormat format, Element& elFormat);
|
||||
|
||||
protected:
|
||||
const TextureUsageType _usageType;
|
||||
|
|
|
@ -619,6 +619,47 @@ bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat
|
|||
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) {
|
||||
if (header.getGLFormat() == ktx::GLFormat::BGRA && header.getGLType() == ktx::GLType::UNSIGNED_BYTE && header.getTypeSize() == 1) {
|
||||
if (header.getGLInternaFormat() == ktx::GLInternalFormat::RGBA8) {
|
||||
|
@ -661,41 +702,7 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E
|
|||
mipFormat = Format::COLOR_RGB9E5;
|
||||
texelFormat = Format::COLOR_RGB9E5;
|
||||
} else if (header.isCompressed()) {
|
||||
if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT) {
|
||||
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 {
|
||||
if (!getCompressedFormat(header.getGLInternaFormat(), texelFormat)) {
|
||||
return false;
|
||||
}
|
||||
mipFormat = texelFormat;
|
||||
|
|
64
libraries/ktx/src/TextureMeta.cpp
Normal file
64
libraries/ktx/src/TextureMeta.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// 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 <QDebug>
|
||||
#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 (error.error != QJsonParseError::NoError) {
|
||||
qDebug() << "Failed to parse TextureMeta:" << error.errorString();
|
||||
return false;
|
||||
}
|
||||
if (!doc.isObject()) {
|
||||
qDebug() << "Unable to process TextureMeta: top-level value is not an Object";
|
||||
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
|
||||
#define khronos_khr_hpp
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace khronos {
|
||||
|
||||
namespace gl {
|
||||
|
@ -209,6 +211,63 @@ namespace khronos {
|
|||
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) {
|
||||
switch (format) {
|
||||
case InternalFormat::R8:
|
||||
|
|
|
@ -537,10 +537,11 @@ QUrl NetworkMaterial::getTextureUrl(const QUrl& baseUrl, const FBXTexture& textu
|
|||
// Inlined file: cache under the fbx file to avoid namespace clashes
|
||||
// NOTE: We cannot resolve the path because filename may be an absolute path
|
||||
assert(texture.filename.size() > 0);
|
||||
auto baseUrlStripped = baseUrl.toDisplayString(QUrl::RemoveFragment | QUrl::RemoveQuery | QUrl::RemoveUserInfo);
|
||||
if (texture.filename.at(0) == '/') {
|
||||
return baseUrl.toString() + texture.filename;
|
||||
return baseUrlStripped + texture.filename;
|
||||
} else {
|
||||
return baseUrl.toString() + '/' + texture.filename;
|
||||
return baseUrlStripped + '/' + texture.filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,8 @@
|
|||
#include <Trace.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_raw, "trace.resource.parse.image.raw")
|
||||
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) :
|
||||
Resource(url),
|
||||
_type(),
|
||||
_sourceIsKTX(false),
|
||||
_maxNumPixels(100)
|
||||
{
|
||||
_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) :
|
||||
Resource(url),
|
||||
_type(type),
|
||||
_sourceIsKTX(url.path().endsWith(".ktx")),
|
||||
_maxNumPixels(maxNumPixels)
|
||||
{
|
||||
_textureSource = std::make_shared<gpu::TextureSource>(url, (int)type);
|
||||
_lowestRequestedMipLevel = 0;
|
||||
|
||||
_shouldFailOnRedirect = !_sourceIsKTX;
|
||||
auto fileNameLowercase = url.fileName().toLower();
|
||||
if (fileNameLowercase.endsWith(TEXTURE_META_EXTENSION)) {
|
||||
_currentlyLoadingResourceType = ResourceType::META;
|
||||
} else if (fileNameLowercase.endsWith(".ktx")) {
|
||||
_currentlyLoadingResourceType = ResourceType::KTX;
|
||||
} else {
|
||||
_currentlyLoadingResourceType = ResourceType::ORIGINAL;
|
||||
}
|
||||
|
||||
_shouldFailOnRedirect = _currentlyLoadingResourceType != ResourceType::KTX;
|
||||
|
||||
if (type == image::TextureUsage::CUBE_TEXTURE) {
|
||||
setLoadPriority(this, SKYBOX_LOAD_PRIORITY);
|
||||
} else if (_sourceIsKTX) {
|
||||
} else if (_currentlyLoadingResourceType == ResourceType::KTX) {
|
||||
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 (!content.isEmpty()) {
|
||||
_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,12 @@ NetworkTexture::~NetworkTexture() {
|
|||
|
||||
const uint16_t NetworkTexture::NULL_MIP_LEVEL = std::numeric_limits<uint16_t>::max();
|
||||
void NetworkTexture::makeRequest() {
|
||||
if (!_sourceIsKTX) {
|
||||
if (_currentlyLoadingResourceType != ResourceType::KTX) {
|
||||
Resource::makeRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLocalUrl(_url)) {
|
||||
if (isLocalUrl(_activeUrl)) {
|
||||
auto self = _self;
|
||||
QtConcurrent::run(QThreadPool::globalInstance(), [self] {
|
||||
auto resource = self.lock();
|
||||
|
@ -466,12 +475,12 @@ void NetworkTexture::handleLocalRequestCompleted() {
|
|||
}
|
||||
|
||||
void NetworkTexture::makeLocalRequest() {
|
||||
const QString scheme = _url.scheme();
|
||||
const QString scheme = _activeUrl.scheme();
|
||||
QString path;
|
||||
if (scheme == URL_SCHEME_FILE) {
|
||||
path = PathUtils::expandToLocalDataAbsolutePath(_url).toLocalFile();
|
||||
path = PathUtils::expandToLocalDataAbsolutePath(_activeUrl).toLocalFile();
|
||||
} else {
|
||||
path = ":" + _url.path();
|
||||
path = ":" + _activeUrl.path();
|
||||
}
|
||||
|
||||
connect(this, &Resource::finished, this, &NetworkTexture::handleLocalRequestCompleted);
|
||||
|
@ -497,7 +506,7 @@ void NetworkTexture::makeLocalRequest() {
|
|||
});
|
||||
|
||||
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 {
|
||||
// at this point the source hash is in binary 16-byte form
|
||||
// and we need it in a hexadecimal string
|
||||
|
@ -536,11 +545,13 @@ void NetworkTexture::makeLocalRequest() {
|
|||
}
|
||||
|
||||
bool NetworkTexture::handleFailedRequest(ResourceRequest::Result result) {
|
||||
if (!_sourceIsKTX && result == ResourceRequest::Result::RedirectFail) {
|
||||
if (_currentlyLoadingResourceType != ResourceType::KTX
|
||||
&& result == ResourceRequest::Result::RedirectFail) {
|
||||
|
||||
auto newPath = _request->getRelativePathUrl();
|
||||
if (newPath.fileName().endsWith(".ktx")) {
|
||||
qDebug() << "Redirecting to" << newPath << "from" << _url;
|
||||
_sourceIsKTX = true;
|
||||
_currentlyLoadingResourceType = ResourceType::KTX;
|
||||
_activeUrl = newPath;
|
||||
_shouldFailOnRedirect = false;
|
||||
makeRequest();
|
||||
|
@ -930,11 +941,75 @@ void NetworkTexture::handleFinishedInitialLoad() {
|
|||
}
|
||||
|
||||
void NetworkTexture::downloadFinished(const QByteArray& data) {
|
||||
loadContent(data);
|
||||
if (_currentlyLoadingResourceType == ResourceType::META) {
|
||||
loadMetaContent(data);
|
||||
} else if (_currentlyLoadingResourceType == ResourceType::ORIGINAL) {
|
||||
loadTextureContent(data);
|
||||
} else {
|
||||
TextureCache::requestCompleted(_self);
|
||||
Resource::handleFailedRequest(ResourceRequest::Error);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkTexture::loadContent(const QByteArray& content) {
|
||||
if (_sourceIsKTX) {
|
||||
void NetworkTexture::loadMetaContent(const QByteArray& content) {
|
||||
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);
|
||||
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;
|
||||
Resource::handleFailedRequest(ResourceRequest::NotFound);
|
||||
}
|
||||
|
||||
void NetworkTexture::loadTextureContent(const QByteArray& content) {
|
||||
if (_currentlyLoadingResourceType != ResourceType::ORIGINAL) {
|
||||
qWarning() << "Trying to load texture content when current resource type is not ORIGINAL";
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
#include <graphics/TextureMap.h>
|
||||
#include <image/Image.h>
|
||||
#include <ktx/KTX.h>
|
||||
#include <TextureMeta.h>
|
||||
|
||||
#include <gpu/Context.h>
|
||||
#include "KTXCache.h"
|
||||
|
||||
namespace gpu {
|
||||
|
@ -75,11 +77,13 @@ protected:
|
|||
|
||||
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;
|
||||
|
||||
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 startRequestForNextMipLevel();
|
||||
|
@ -93,6 +97,14 @@ private:
|
|||
|
||||
image::TextureUsage::Type _type;
|
||||
|
||||
enum class ResourceType {
|
||||
META,
|
||||
ORIGINAL,
|
||||
KTX
|
||||
};
|
||||
|
||||
ResourceType _currentlyLoadingResourceType { ResourceType::META };
|
||||
|
||||
static const uint16_t NULL_MIP_LEVEL;
|
||||
enum KTXResourceState {
|
||||
PENDING_INITIAL_LOAD = 0,
|
||||
|
@ -103,7 +115,6 @@ private:
|
|||
FAILED_TO_LOAD
|
||||
};
|
||||
|
||||
bool _sourceIsKTX { false };
|
||||
KTXResourceState _ktxResourceState { PENDING_INITIAL_LOAD };
|
||||
|
||||
// The current mips that are currently being requested w/ _ktxMipRequest
|
||||
|
@ -233,6 +244,9 @@ public:
|
|||
static const int DEFAULT_SPECTATOR_CAM_WIDTH { 2048 };
|
||||
static const int DEFAULT_SPECTATOR_CAM_HEIGHT { 1024 };
|
||||
|
||||
void setGPUContext(const gpu::ContextPointer& context) { _gpuContext = context; }
|
||||
gpu::ContextPointer getGPUContext() const { return _gpuContext; }
|
||||
|
||||
signals:
|
||||
/**jsdoc
|
||||
* @function TextureCache.spectatorCameraFramebufferReset
|
||||
|
@ -266,6 +280,8 @@ private:
|
|||
static const std::string KTX_DIRNAME;
|
||||
static const std::string KTX_EXT;
|
||||
|
||||
gpu::ContextPointer _gpuContext { nullptr };
|
||||
|
||||
std::shared_ptr<cache::FileCache> _ktxCache { std::make_shared<KTXCache>(KTX_DIRNAME, KTX_EXT) };
|
||||
|
||||
// Map from image hashes to texture weak pointers
|
||||
|
|
|
@ -581,6 +581,7 @@ void Resource::refresh() {
|
|||
ResourceCache::requestCompleted(_self);
|
||||
}
|
||||
|
||||
_activeUrl = _url;
|
||||
init();
|
||||
ensureLoading();
|
||||
emit onRefresh();
|
||||
|
@ -618,7 +619,6 @@ void Resource::init(bool resetLoaded) {
|
|||
_loaded = false;
|
||||
}
|
||||
_attempts = 0;
|
||||
_activeUrl = _url;
|
||||
|
||||
if (_url.isEmpty()) {
|
||||
_startedLoading = _loaded = true;
|
||||
|
@ -724,7 +724,7 @@ void Resource::handleReplyFinished() {
|
|||
auto result = _request->getResult();
|
||||
if (result == ResourceRequest::Success) {
|
||||
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();
|
||||
if (!relativePathURL.isEmpty()) {
|
||||
|
|
|
@ -59,11 +59,10 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
return 17;
|
||||
case PacketType::AssetMappingOperation:
|
||||
case PacketType::AssetMappingOperationReply:
|
||||
return static_cast<PacketVersion>(AssetServerPacketVersion::RedirectedMappings);
|
||||
case PacketType::AssetGetInfo:
|
||||
case PacketType::AssetGet:
|
||||
case PacketType::AssetUpload:
|
||||
return static_cast<PacketVersion>(AssetServerPacketVersion::RangeRequestSupport);
|
||||
return static_cast<PacketVersion>(AssetServerPacketVersion::BakingTextureMeta);
|
||||
case PacketType::NodeIgnoreRequest:
|
||||
return 18; // Introduction of node ignore request (which replaced an unused packet tpye)
|
||||
|
||||
|
|
|
@ -252,7 +252,8 @@ enum class EntityQueryPacketVersion: PacketVersion {
|
|||
enum class AssetServerPacketVersion: PacketVersion {
|
||||
VegasCongestionControl = 19,
|
||||
RangeRequestSupport,
|
||||
RedirectedMappings
|
||||
RedirectedMappings,
|
||||
BakingTextureMeta
|
||||
};
|
||||
|
||||
enum class AvatarMixerPacketVersion : PacketVersion {
|
||||
|
|
|
@ -464,7 +464,7 @@ bool DomainBaker::rewriteSkyboxURL(QJsonValueRef urlValue, TextureBaker* baker)
|
|||
if (oldSkyboxURL.matches(baker->getTextureURL(), QUrl::RemoveQuery | QUrl::RemoveFragment)) {
|
||||
// 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.setFragment(oldSkyboxURL.fragment());
|
||||
newSkyboxURL.setUserInfo(oldSkyboxURL.userInfo());
|
||||
|
|
Loading…
Reference in a new issue