Improve handling of KTX downloads in NetworkTexture

This commit is contained in:
Ryan Huffman 2017-04-18 00:14:04 -07:00 committed by Atlante45
parent 11751611e1
commit 70b816827e
4 changed files with 95 additions and 84 deletions

View file

@ -281,10 +281,10 @@ NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type,
_loaded = true; _loaded = true;
} }
if (_sourceIsKTX) { //if (_sourceIsKTX) {
_requestByteRange.fromInclusive = 0; //_requestByteRange.fromInclusive = 0;
_requestByteRange.toExclusive = 1000; //_requestByteRange.toExclusive = 1000;
} //}
// 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()) {
@ -346,7 +346,9 @@ void NetworkTexture::makeRequest() {
// We special-handle ktx requests to run 2 concurrent requests right off the bat // We special-handle ktx requests to run 2 concurrent requests right off the bat
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() } });
if (!_ktxHeaderLoaded && !_highMipRequestFinished) { if (_ktxResourceState == PENDING_INITIAL_LOAD) {
_ktxResourceState = LOADING_INITIAL_DATA;
qDebug() << ">>> Making request to " << _url << " for header"; qDebug() << ">>> Making request to " << _url << " for header";
_ktxHeaderRequest = ResourceManager::createResourceRequest(this, _activeUrl); _ktxHeaderRequest = ResourceManager::createResourceRequest(this, _activeUrl);
@ -374,10 +376,13 @@ void NetworkTexture::makeRequest() {
_ktxHeaderRequest->send(); _ktxHeaderRequest->send();
startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL); startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL);
} else { } else if (_ktxResourceState == PENDING_MIP_REQUEST) {
_ktxResourceState = REQUESTING_MIP;
if (_lowestKnownPopulatedMip > 0) { if (_lowestKnownPopulatedMip > 0) {
startMipRangeRequest(_lowestKnownPopulatedMip - 1, _lowestKnownPopulatedMip - 1); startMipRangeRequest(_lowestKnownPopulatedMip - 1, _lowestKnownPopulatedMip - 1);
} }
} else {
qWarning(networking) << "NetworkTexture::makeRequest() called while not in a valid state: " << _ktxResourceState;
} }
} }
@ -387,13 +392,7 @@ void NetworkTexture::handleMipInterestCallback(uint16_t level) {
} }
void NetworkTexture::handleMipInterestLevel(int level) { void NetworkTexture::handleMipInterestLevel(int level) {
_lowestRequestedMipLevel = std::min(static_cast<uint16_t>(level), _lowestRequestedMipLevel); startRequestForNextMipLevel();
if (!_ktxMipRequest && _lowestKnownPopulatedMip > 0) {
//startRequestForNextMipLevel();
clearLoadPriority(this);
setLoadPriority(this, _lowestKnownPopulatedMip - 1);
ResourceCache::attemptRequest(_self);
}
} }
void NetworkTexture::startRequestForNextMipLevel() { void NetworkTexture::startRequestForNextMipLevel() {
@ -401,11 +400,15 @@ void NetworkTexture::startRequestForNextMipLevel() {
qWarning(networking) << "Requesting next mip level but all have been fulfilled"; qWarning(networking) << "Requesting next mip level but all have been fulfilled";
return; return;
} }
if (_pending) {
return;
}
//startMipRangeRequest(std::max(0, _lowestKnownPopulatedMip - 1), std::max(0, _lowestKnownPopulatedMip - 1)); if (_ktxResourceState == WAITING_FOR_MIP_REQUEST) {
_ktxResourceState = PENDING_MIP_REQUEST;
setLoadPriority(this, _lowestKnownPopulatedMip);
init();
ResourceCache::attemptRequest(_self);
}
} }
// Load mips in the range [low, high] (inclusive) // Load mips in the range [low, high] (inclusive)
@ -415,10 +418,6 @@ 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;
if (!isHighMipRequest && !_ktxHeaderLoaded) {
return;
}
_ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl); _ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl);
qDebug(networking) << ">>> Making request to " << _url << " for " << low << " to " << high; qDebug(networking) << ">>> Making request to " << _url << " for " << low << " to " << high;
@ -446,59 +445,75 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
void NetworkTexture::ktxHeaderRequestFinished() { void NetworkTexture::ktxHeaderRequestFinished() {
assert(!_ktxHeaderLoaded); Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
_headerRequestFinished = true; _ktxHeaderRequestFinished = true;
if (_ktxHeaderRequest->getResult() == ResourceRequest::Success) { maybeHandleFinishedInitialLoad();
_ktxHeaderLoaded = true;
_ktxHeaderData = _ktxHeaderRequest->getData();
maybeHandleFinishedInitialLoad();
} else {
handleFailedRequest(_ktxHeaderRequest->getResult());
}
_ktxHeaderRequest->deleteLater();
_ktxHeaderRequest = nullptr;
} }
void NetworkTexture::ktxMipRequestFinished() { void NetworkTexture::ktxMipRequestFinished() {
bool isHighMipRequest = _ktxMipLevelRangeInFlight.first == NULL_MIP_LEVEL Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA || _ktxResourceState == REQUESTING_MIP);
&& _ktxMipLevelRangeInFlight.second == NULL_MIP_LEVEL;
if (_ktxMipRequest->getResult() == ResourceRequest::Success) { if (_ktxResourceState == LOADING_INITIAL_DATA) {
if (_highMipRequestFinished) { _ktxHighMipRequestFinished = true;
assert(_ktxMipLevelRangeInFlight.second - _ktxMipLevelRangeInFlight.first == 0); maybeHandleFinishedInitialLoad();
} else if (_ktxResourceState == REQUESTING_MIP) {
ResourceCache::requestCompleted(_self);
if (_ktxMipRequest->getResult() == ResourceRequest::Success) {
Q_ASSERT(_ktxMipLevelRangeInFlight.second - _ktxMipLevelRangeInFlight.first == 0);
_lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
auto texture = _textureSource->getGPUTexture(); auto texture = _textureSource->getGPUTexture();
if (!texture) { if (texture) {
_lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
texture->assignStoredMip(_ktxMipLevelRangeInFlight.first, texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
_ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data())); _ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
} else { } else {
qWarning(networking) << "Trying to update mips but texture is null"; qWarning(networking) << "Trying to update mips but texture is null";
} }
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
} else { } else {
_highMipRequestFinished = true; _ktxResourceState = PENDING_MIP_REQUEST;
_ktxHighMipData = _ktxMipRequest->getData(); handleFailedRequest(_ktxMipRequest->getResult());
maybeHandleFinishedInitialLoad();
} }
_ktxMipRequest->deleteLater();
_ktxMipRequest = nullptr;
} else { } else {
handleFailedRequest(_ktxMipRequest->getResult()); qWarning() << "Mip request finished in an unexpected state: " << _ktxResourceState;
} }
_ktxMipRequest->deleteLater();
_ktxMipRequest = nullptr;
} }
// This is called when the header or top mips have been loaded // This is called when the header or top mips have been loaded
void NetworkTexture::maybeHandleFinishedInitialLoad() { void NetworkTexture::maybeHandleFinishedInitialLoad() {
if (_headerRequestFinished && _highMipRequestFinished) { Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
if (_ktxHeaderRequestFinished && _ktxHighMipRequestFinished) {
ResourceCache::requestCompleted(_self); ResourceCache::requestCompleted(_self);
if (_ktxHeaderData.size() == 0 || _ktxHighMipData.size() == 0) { if (_ktxHeaderRequest->getResult() != ResourceRequest::Success || _ktxMipRequest->getResult() != ResourceRequest::Success) {
finishedLoading(false); if (handleFailedRequest(_ktxMipRequest->getResult())) {
_ktxResourceState = PENDING_INITIAL_LOAD;
} else {
_ktxResourceState = FAILED_TO_LOAD;
}
_ktxHeaderRequest->deleteLater();
_ktxHeaderRequest = nullptr;
_ktxMipRequest->deleteLater();
_ktxMipRequest = nullptr;
} else { } else {
// create ktx... // create ktx...
auto header = reinterpret_cast<const ktx::Header*>(_ktxHeaderData.data()); auto ktxHeaderData = _ktxHeaderRequest->getData();
auto ktxHighMipData = _ktxMipRequest->getData();
_ktxHeaderRequest->deleteLater();
_ktxHeaderRequest = nullptr;
_ktxMipRequest->deleteLater();
_ktxMipRequest = nullptr;
auto header = reinterpret_cast<const ktx::Header*>(ktxHeaderData.data());
qDebug() << "Creating KTX"; qDebug() << "Creating KTX";
qDebug() << "Identifier:" << QString(QByteArray((char*)header->identifier, 12)); qDebug() << "Identifier:" << QString(QByteArray((char*)header->identifier, 12));
@ -507,14 +522,16 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
qDebug() << "numberOfArrayElements:" << header->numberOfArrayElements; qDebug() << "numberOfArrayElements:" << header->numberOfArrayElements;
qDebug() << "numberOfFaces:" << header->numberOfFaces; qDebug() << "numberOfFaces:" << header->numberOfFaces;
qDebug() << "numberOfMipmapLevels:" << header->numberOfMipmapLevels; qDebug() << "numberOfMipmapLevels:" << header->numberOfMipmapLevels;
auto kvSize = header->bytesOfKeyValueData; auto kvSize = header->bytesOfKeyValueData;
if (kvSize > _ktxHeaderData.size() - ktx::KTX_HEADER_SIZE) { if (kvSize > ktxHeaderData.size() - ktx::KTX_HEADER_SIZE) {
qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request"; qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
_ktxResourceState = FAILED_TO_LOAD;
finishedLoading(false); finishedLoading(false);
return; return;
} }
auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(_ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE); auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE);
auto imageDescriptors = header->generateImageDescriptors(); auto imageDescriptors = header->generateImageDescriptors();
_originalKtxDescriptor.reset(new ktx::KTXDescriptor(*header, keyValues, imageDescriptors)); _originalKtxDescriptor.reset(new ktx::KTXDescriptor(*header, keyValues, imageDescriptors));
@ -532,6 +549,7 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
else { else {
if (found->_value.size() < 32) { if (found->_value.size() < 32) {
qWarning("Invalid source hash key found, bailing"); qWarning("Invalid source hash key found, bailing");
_ktxResourceState = FAILED_TO_LOAD;
finishedLoading(false); finishedLoading(false);
return; return;
} else { } else {
@ -553,10 +571,10 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
auto& ktxCache = textureCache->_ktxCache; auto& ktxCache = textureCache->_ktxCache;
if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) { if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
qCWarning(modelnetworking) << _url << " failed to write cache file"; qCWarning(modelnetworking) << _url << " failed to write cache file";
_ktxResourceState = FAILED_TO_LOAD;
finishedLoading(false); finishedLoading(false);
return; return;
} } else {
else {
_file = file; _file = file;
} }
@ -571,9 +589,9 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
texture->registerMipInterestListener(this); texture->registerMipInterestListener(this);
auto& images = _originalKtxDescriptor->images; auto& images = _originalKtxDescriptor->images;
size_t imageSizeRemaining = _ktxHighMipData.size(); size_t imageSizeRemaining = ktxHighMipData.size();
uint8_t* ktxData = reinterpret_cast<uint8_t*>(_ktxHighMipData.data()); uint8_t* ktxData = reinterpret_cast<uint8_t*>(ktxHighMipData.data());
ktxData += _ktxHighMipData.size(); ktxData += ktxHighMipData.size();
// TODO Move image offset calculation to ktx ImageDescriptor // TODO Move image offset calculation to ktx ImageDescriptor
int level; int level;
for (level = images.size() - 1; level >= 0; --level) { for (level = images.size() - 1; level >= 0; --level) {
@ -585,7 +603,7 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
ktxData -= image._imageSize; ktxData -= image._imageSize;
texture->assignStoredMip(level, image._imageSize, ktxData); texture->assignStoredMip(level, image._imageSize, ktxData);
ktxData -= 4; ktxData -= 4;
imageSizeRemaining - image._imageSize - 4; imageSizeRemaining -= (image._imageSize + 4);
} }
// We replace the texture with the one stored in the cache. This deals with the possible race condition of two different // We replace the texture with the one stored in the cache. This deals with the possible race condition of two different
@ -604,6 +622,7 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() {
} }
} }
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
setImage(texture, header->getPixelWidth(), header->getPixelHeight()); setImage(texture, header->getPixelWidth(), header->getPixelHeight());
} }
} }

View file

@ -93,31 +93,31 @@ private:
image::TextureUsage::Type _type; image::TextureUsage::Type _type;
static const uint16_t NULL_MIP_LEVEL; static const uint16_t NULL_MIP_LEVEL;
struct KTXResourceState { enum KTXResourceState {
NOT_LOADED = 0, PENDING_INITIAL_LOAD = 0,
LOADING_INITIAL_DATA, // Loading KTX Header + Low Resolution Mips LOADING_INITIAL_DATA, // Loading KTX Header + Low Resolution Mips
WAITING_FOR_MIP_REQUEST, // Waiting for the gpu layer to report that it needs higher resolution mips WAITING_FOR_MIP_REQUEST, // Waiting for the gpu layer to report that it needs higher resolution mips
PENDING_MIP_REQUEST, // We have added ourselves to the ResourceCache queue PENDING_MIP_REQUEST, // We have added ourselves to the ResourceCache queue
REQUESTING_MIP // We have a mip in flight REQUESTING_MIP, // We have a mip in flight
FAILED_TO_LOAD
}; };
KTXResourceState _ktxResourceState{ NOT_LOADED }; bool _sourceIsKTX { false };
KTXResourceState _ktxResourceState { PENDING_INITIAL_LOAD };
// TODO Can this be removed?
KTXFilePointer _file; KTXFilePointer _file;
bool _sourceIsKTX { false }; // The current mips that are currently being requested w/ _ktxMipRequest
bool _ktxHeaderLoaded { false };
bool _highMipRequestFinished { false };
std::pair<uint16_t, uint16_t> _ktxMipLevelRangeInFlight{ NULL_MIP_LEVEL, NULL_MIP_LEVEL }; std::pair<uint16_t, uint16_t> _ktxMipLevelRangeInFlight{ NULL_MIP_LEVEL, NULL_MIP_LEVEL };
ResourceRequest* _ktxHeaderRequest { nullptr }; ResourceRequest* _ktxHeaderRequest { nullptr };
ResourceRequest* _ktxMipRequest { nullptr }; ResourceRequest* _ktxMipRequest { nullptr };
bool _headerRequestFinished { false }; bool _ktxHeaderRequestFinished{ false };
bool _ktxHighMipRequestFinished{ false };
uint16_t _lowestRequestedMipLevel { NULL_MIP_LEVEL }; uint16_t _lowestRequestedMipLevel { NULL_MIP_LEVEL };
uint16_t _lowestKnownPopulatedMip { NULL_MIP_LEVEL }; uint16_t _lowestKnownPopulatedMip { NULL_MIP_LEVEL };
QByteArray _ktxHeaderData;
QByteArray _ktxHighMipData;
// This is a copy of the original KTX descriptor from the source url. // This is a copy of the original KTX descriptor from the source url.
// We need this because the KTX that will be cached will likely include extra data // We need this because the KTX that will be cached will likely include extra data

View file

@ -475,12 +475,6 @@ int ResourceCache::getLoadingRequestCount() {
bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) { bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) {
Q_ASSERT(!resource.isNull()); Q_ASSERT(!resource.isNull());
if (resource->_pending) {
qWarning(networking) << "Attempted to request " << resource->getURL() << " but it was already pending";
return false;
}
resource->_pending = true;
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>(); auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
if (_requestsActive >= _requestLimit) { if (_requestsActive >= _requestLimit) {
@ -498,11 +492,6 @@ bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) {
void ResourceCache::requestCompleted(QWeakPointer<Resource> resource) { void ResourceCache::requestCompleted(QWeakPointer<Resource> resource) {
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>(); auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
auto sharedResource = resource.lock();
if (sharedResource) {
sharedResource->_pending = true;
}
sharedItems->removeRequest(resource); sharedItems->removeRequest(resource);
--_requestsActive; --_requestsActive;
@ -747,7 +736,8 @@ void Resource::handleReplyFinished() {
_request = nullptr; _request = nullptr;
} }
void Resource::handleFailedRequest(ResourceRequest::Result result) { bool Resource::handleFailedRequest(ResourceRequest::Result result) {
bool willRetry = false;
switch (result) { switch (result) {
case ResourceRequest::Result::Timeout: { case ResourceRequest::Result::Timeout: {
qCDebug(networking) << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal; qCDebug(networking) << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal;
@ -763,6 +753,7 @@ void Resource::handleFailedRequest(ResourceRequest::Result result) {
<< "if resource is still needed"; << "if resource is still needed";
QTimer::singleShot(waitTime, this, &Resource::attemptRequest); QTimer::singleShot(waitTime, this, &Resource::attemptRequest);
willRetry = true;
break; break;
} }
// fall through to final failure // fall through to final failure
@ -772,10 +763,12 @@ void Resource::handleFailedRequest(ResourceRequest::Result result) {
auto error = (result == ResourceRequest::Timeout) ? QNetworkReply::TimeoutError auto error = (result == ResourceRequest::Timeout) ? QNetworkReply::TimeoutError
: QNetworkReply::UnknownNetworkError; : QNetworkReply::UnknownNetworkError;
emit failed(error); emit failed(error);
willRetry = false;
finishedLoading(false); finishedLoading(false);
break; break;
} }
} }
return willRetry;
} }
uint qHash(const QPointer<QObject>& value, uint seed) { uint qHash(const QPointer<QObject>& value, uint seed) {

View file

@ -445,7 +445,8 @@ protected:
Q_INVOKABLE void allReferencesCleared(); Q_INVOKABLE void allReferencesCleared();
void handleFailedRequest(ResourceRequest::Result result); /// Return true if the resource will be retried
bool handleFailedRequest(ResourceRequest::Result result);
QUrl _url; QUrl _url;
QUrl _activeUrl; QUrl _activeUrl;
@ -464,8 +465,6 @@ protected:
int _requestID; int _requestID;
ResourceRequest* _request{ nullptr }; ResourceRequest* _request{ nullptr };
bool _pending{ false };
public slots: public slots:
void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal); void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal);
void handleReplyFinished(); void handleReplyFinished();