Offload ktx processing to threadpool

This commit is contained in:
Atlante45 2017-06-13 15:44:34 -07:00
parent 3110dd72bf
commit d2251bb840
2 changed files with 169 additions and 152 deletions

View file

@ -13,6 +13,8 @@
#include <mutex>
#include <QtConcurrent\QtConcurrentRun>
#include <QCryptographicHash>
#include <QImageReader>
#include <QRunnable>
@ -300,20 +302,11 @@ void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth,
_textureSource->resetTexture(texture);
if (texture) {
if (_sourceIsKTX) {
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
}
_width = texture->getWidth();
_height = texture->getHeight();
setSize(texture->getStoredSize());
finishedLoading(true);
} else {
// FIXME: If !gpuTexture, we failed to load!
if (_sourceIsKTX) {
_ktxResourceState = FAILED_TO_LOAD;
}
_width = _height = 0;
finishedLoading(false);
}
@ -484,10 +477,10 @@ void NetworkTexture::ktxHeaderRequestFinished() {
Q_ASSERT_X(_ktxHeaderRequest, "Resource::handleReplyFinished", "Request should not be null while in handleReplyFinished");
Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID), {
{ "from_cache", _ktxHeaderRequest->loadedFromCache() },
{ "size_mb", _bytesTotal / 1000000.0 }
});
//PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID), {
// { "from_cache", _ktxHeaderRequest->loadedFromCache() },
// { "size_mb", _bytesTotal / 1000000.0 }
//});
setSize(_bytesTotal);
@ -505,9 +498,8 @@ void NetworkTexture::ktxHeaderRequestFinished() {
auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString());
qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo);
_ktxHeaderRequestFinished = true;
_ktxHeaderData = _ktxHeaderRequest->getData();
maybeHandleFinishedInitialLoad();
} else {
handleFailedRequest(result);
}
@ -545,7 +537,7 @@ void NetworkTexture::ktxMipRequestFinished() {
qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo);
if (_ktxResourceState == LOADING_INITIAL_DATA) {
_ktxHighMipRequestFinished = true;
_ktxHighMipData = _ktxMipRequest->getData();
maybeHandleFinishedInitialLoad();
} else if (_ktxResourceState == REQUESTING_MIP) {
Q_ASSERT(_ktxMipLevelRangeInFlight.first != NULL_MIP_LEVEL);
@ -558,21 +550,37 @@ void NetworkTexture::ktxMipRequestFinished() {
startRequestForNextMipLevel();
}
//// Move to other thread
auto texture = _textureSource->getGPUTexture();
if (texture) {
auto data = _ktxMipRequest->getData();
texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
data.size(), reinterpret_cast<uint8_t*>(data.data()));
}
finishedLoading(true);
auto self = _self;
auto data = _ktxMipRequest->getData();
auto mipLevel = _ktxMipLevelRangeInFlight.first;
QtConcurrent::run(QThreadPool::globalInstance(), [this, self, data, mipLevel] {
auto that = self.lock();
if (!that) {
// Resource no longer exists, bail
return;
}
auto texture = _textureSource->getGPUTexture();
if (texture) {
texture->assignStoredMip(mipLevel, data.size(), reinterpret_cast<const uint8_t*>(data.data()));
QMetaObject::invokeMethod(this, "setImage",
Q_ARG(gpu::TexturePointer, texture),
Q_ARG(int, texture->getWidth()),
Q_ARG(int, texture->getHeight()));
} else {
QMetaObject::invokeMethod(this, "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
}
});
} else {
qWarning(networking) << "Mip request finished in an unexpected state: " << _ktxResourceState;
finishedLoading(false);
}
} else {
if (handleFailedRequest(result)) {
_ktxResourceState = PENDING_MIP_REQUEST;
@ -588,149 +596,158 @@ void NetworkTexture::ktxMipRequestFinished() {
// This is called when the header or top mips have been loaded
void NetworkTexture::maybeHandleFinishedInitialLoad() {
PROFILE_RANGE(app, __FUNCTION__);
Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
if (_ktxHeaderRequestFinished && _ktxHighMipRequestFinished) {
auto resource = _self;
PROFILE_RANGE(app, __FUNCTION__);
if (!_ktxHeaderData.isEmpty() && !_ktxHighMipData.isEmpty()) {
// create ktx...
auto ktxHeaderData = _ktxHeaderRequest->getData();
auto ktxHighMipData = _ktxMipRequest->getData();
auto ktxHeaderData = _ktxHeaderData;
auto ktxHighMipData = _ktxHighMipData;
_ktxHeaderData.clear();
_ktxHighMipData.clear();
auto header = reinterpret_cast<const ktx::Header*>(ktxHeaderData.data());
if (!ktx::checkIdentifier(header->identifier)) {
qWarning() << "Cannot load " << _url << ", invalid header identifier";
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
}
auto kvSize = header->bytesOfKeyValueData;
if (kvSize > (ktxHeaderData.size() - ktx::KTX_HEADER_SIZE)) {
qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
}
auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE);
auto imageDescriptors = header->generateImageDescriptors();
if (imageDescriptors.size() == 0) {
qWarning(networking) << "Failed to process ktx file " << _url;
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
}
_originalKtxDescriptor.reset(new ktx::KTXDescriptor(*header, keyValues, imageDescriptors));
// Create bare ktx in memory
auto found = std::find_if(keyValues.begin(), keyValues.end(), [](const ktx::KeyValue& val) -> bool {
return val._key.compare(gpu::SOURCE_HASH_KEY) == 0;
});
std::string filename;
std::string hash;
if (found == keyValues.end() || found->_value.size() != gpu::SOURCE_HASH_BYTES) {
qWarning("Invalid source hash key found, bailing");
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
} else {
// at this point the source hash is in binary 16-byte form
// and we need it in a hexadecimal string
auto binaryHash = QByteArray(reinterpret_cast<char*>(found->_value.data()), gpu::SOURCE_HASH_BYTES);
hash = filename = binaryHash.toHex().toStdString();
}
auto textureCache = DependencyManager::get<TextureCache>();
gpu::TexturePointer texture = textureCache->getTextureByHash(hash);
if (!texture) {
KTXFilePointer ktxFile = textureCache->_ktxCache.getFile(hash);
if (ktxFile) {
texture = gpu::Texture::unserialize(ktxFile->getFilepath());
if (texture) {
texture = textureCache->cacheTextureByHash(hash, texture);
}
}
}
if (!texture) {
auto memKtx = ktx::KTX::createBare(*header, keyValues);
if (!memKtx) {
qWarning() << " Ktx could not be created, bailing";
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
auto self = _self;
QtConcurrent::run(QThreadPool::globalInstance(), [=] {
auto that = self.lock();
if (!that) {
// Resource no longer exists, bail
return;
}
// Move ktx to file
const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
size_t length = memKtx->_storage->size();
KTXFilePointer file;
auto& ktxCache = textureCache->_ktxCache;
if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
qCWarning(modelnetworking) << _url << " failed to write cache file";
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
auto header = reinterpret_cast<const ktx::Header*>(ktxHeaderData.data());
if (!ktx::checkIdentifier(header->identifier)) {
qWarning() << "Cannot load " << _url << ", invalid header identifier";
QMetaObject::invokeMethod(this, "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
}
auto kvSize = header->bytesOfKeyValueData;
if (kvSize > (ktxHeaderData.size() - ktx::KTX_HEADER_SIZE)) {
qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
QMetaObject::invokeMethod(this, "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
}
auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE);
auto imageDescriptors = header->generateImageDescriptors();
if (imageDescriptors.size() == 0) {
qWarning(networking) << "Failed to process ktx file " << _url;
QMetaObject::invokeMethod(this, "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
}
_originalKtxDescriptor.reset(new ktx::KTXDescriptor(*header, keyValues, imageDescriptors));
// Create bare ktx in memory
auto found = std::find_if(keyValues.begin(), keyValues.end(), [](const ktx::KeyValue& val) -> bool {
return val._key.compare(gpu::SOURCE_HASH_KEY) == 0;
});
std::string filename;
std::string hash;
if (found == keyValues.end() || found->_value.size() != gpu::SOURCE_HASH_BYTES) {
qWarning("Invalid source hash key found, bailing");
QMetaObject::invokeMethod(this, "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
} else {
_file = file;
// at this point the source hash is in binary 16-byte form
// and we need it in a hexadecimal string
auto binaryHash = QByteArray(reinterpret_cast<char*>(found->_value.data()), gpu::SOURCE_HASH_BYTES);
hash = filename = binaryHash.toHex().toStdString();
}
auto newKtxDescriptor = memKtx->toDescriptor();
auto textureCache = DependencyManager::get<TextureCache>();
texture = gpu::Texture::unserialize(_file->getFilepath(), newKtxDescriptor);
texture->setKtxBacking(file->getFilepath());
texture->setSource(filename);
gpu::TexturePointer texture = textureCache->getTextureByHash(hash);
auto& images = _originalKtxDescriptor->images;
size_t imageSizeRemaining = ktxHighMipData.size();
uint8_t* ktxData = reinterpret_cast<uint8_t*>(ktxHighMipData.data());
ktxData += ktxHighMipData.size();
// TODO Move image offset calculation to ktx ImageDescriptor
for (int level = static_cast<int>(images.size()) - 1; level >= 0; --level) {
auto& image = images[level];
if (image._imageSize > imageSizeRemaining) {
break;
if (!texture) {
KTXFilePointer ktxFile = textureCache->_ktxCache.getFile(hash);
if (ktxFile) {
texture = gpu::Texture::unserialize(ktxFile->getFilepath());
if (texture) {
texture = textureCache->cacheTextureByHash(hash, texture);
}
}
ktxData -= image._imageSize;
texture->assignStoredMip(static_cast<gpu::uint16>(level), image._imageSize, ktxData);
ktxData -= ktx::IMAGE_SIZE_WIDTH;
imageSizeRemaining -= (image._imageSize + ktx::IMAGE_SIZE_WIDTH);
}
// We replace the texture with the one stored in the cache. This deals with the possible race condition of two different
// images with the same hash being loaded concurrently. Only one of them will make it into the cache by hash first and will
// be the winner
texture = textureCache->cacheTextureByHash(filename, texture);
}
if (!texture) {
_lowestKnownPopulatedMip = texture->minAvailableMipLevel();
auto memKtx = ktx::KTX::createBare(*header, keyValues);
if (!memKtx) {
qWarning() << " Ktx could not be created, bailing";
QMetaObject::invokeMethod(this, "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
}
// Move ktx to file
const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
size_t length = memKtx->_storage->size();
KTXFilePointer file;
auto& ktxCache = textureCache->_ktxCache;
if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
qCWarning(modelnetworking) << _url << " failed to write cache file";
QMetaObject::invokeMethod(this, "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
} else {
_file = file;
}
auto newKtxDescriptor = memKtx->toDescriptor();
texture = gpu::Texture::unserialize(_file->getFilepath(), newKtxDescriptor);
texture->setKtxBacking(file->getFilepath());
texture->setSource(filename);
auto& images = _originalKtxDescriptor->images;
size_t imageSizeRemaining = ktxHighMipData.size();
const uint8_t* ktxData = reinterpret_cast<const uint8_t*>(ktxHighMipData.data());
ktxData += ktxHighMipData.size();
// TODO Move image offset calculation to ktx ImageDescriptor
for (int level = static_cast<int>(images.size()) - 1; level >= 0; --level) {
auto& image = images[level];
if (image._imageSize > imageSizeRemaining) {
break;
}
ktxData -= image._imageSize;
texture->assignStoredMip(static_cast<gpu::uint16>(level), image._imageSize, ktxData);
ktxData -= ktx::IMAGE_SIZE_WIDTH;
imageSizeRemaining -= (image._imageSize + ktx::IMAGE_SIZE_WIDTH);
}
// We replace the texture with the one stored in the cache. This deals with the possible race condition of two different
// images with the same hash being loaded concurrently. Only one of them will make it into the cache by hash first and will
// be the winner
texture = textureCache->cacheTextureByHash(filename, texture);
}
_lowestKnownPopulatedMip = texture->minAvailableMipLevel();
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, texture),
Q_ARG(int, texture->getWidth()),
Q_ARG(int, texture->getHeight()));
QMetaObject::invokeMethod(this, "setImage",
Q_ARG(gpu::TexturePointer, texture),
Q_ARG(int, texture->getWidth()),
Q_ARG(int, texture->getHeight()));
QMetaObject::invokeMethod(this, "startRequestForNextMipLevel");
});
}
}

View file

@ -78,7 +78,7 @@ protected:
Q_INVOKABLE void loadContent(const QByteArray& content);
Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight);
void startRequestForNextMipLevel();
Q_INVOKABLE void startRequestForNextMipLevel();
void startMipRangeRequest(uint16_t low, uint16_t high);
void maybeHandleFinishedInitialLoad();
@ -110,8 +110,8 @@ private:
ResourceRequest* _ktxHeaderRequest { nullptr };
ResourceRequest* _ktxMipRequest { nullptr };
bool _ktxHeaderRequestFinished{ false };
bool _ktxHighMipRequestFinished{ false };
QByteArray _ktxHeaderData;
QByteArray _ktxHighMipData;
uint16_t _lowestRequestedMipLevel { NULL_MIP_LEVEL };
uint16_t _lowestKnownPopulatedMip { NULL_MIP_LEVEL };