diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index ede1f21c1f..d9a0348e54 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -108,7 +108,8 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor } - ktx::StoragePointer file { new storage::FileStorage(_filename.c_str()) }; + auto fileStorage = new storage::FileStorage(_filename.c_str()); + ktx::StoragePointer file { fileStorage }; auto data = file->mutableData(); data += file->size(); @@ -197,19 +198,21 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { header.numberOfMipmapLevels = texture.getNumMips(); ktx::Images images; + uint32_t imageOffset = 0; for (uint32_t level = 0; level < header.numberOfMipmapLevels; level++) { auto mip = texture.accessStoredMipFace(level); if (mip) { if (numFaces == 1) { - images.emplace_back(ktx::Image((uint32_t)mip->getSize(), 0, mip->readData())); + images.emplace_back(ktx::Image(imageOffset, (uint32_t)mip->getSize(), 0, mip->readData())); } else { ktx::Image::FaceBytes cubeFaces(Texture::CUBE_FACE_COUNT); cubeFaces[0] = mip->readData(); for (uint32_t face = 1; face < Texture::CUBE_FACE_COUNT; face++) { cubeFaces[face] = texture.accessStoredMipFace(level, face)->readData(); } - images.emplace_back(ktx::Image((uint32_t)mip->getSize(), 0, cubeFaces)); + images.emplace_back(ktx::Image(imageOffset, (uint32_t)mip->getSize(), 0, cubeFaces)); } + imageOffset += mip->getSize() + 4; } } diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp index 1f22514226..73751bdef2 100644 --- a/libraries/ktx/src/ktx/KTX.cpp +++ b/libraries/ktx/src/ktx/KTX.cpp @@ -73,12 +73,18 @@ size_t Header::evalImageSize(uint32_t level) const { ImageDescriptors Header::generateImageDescriptors() const { ImageDescriptors descriptors; + uint32_t imageOffset = 0; for (auto level = 0; level < numberOfMipmapLevels; ++level) { + auto imageSize = static_cast(evalImageSize(level)); ImageHeader header { numberOfFaces == NUM_CUBEMAPFACES, - static_cast(evalImageSize(level)), + imageOffset, + imageSize, 0 }; + + imageOffset += imageSize + 4; + ImageHeader::FaceOffsets offsets; for (auto i = 0; i < numberOfFaces; ++i) { offsets.push_back(0); diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 8b5a62ebb3..7056f22ba8 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -414,12 +414,17 @@ namespace ktx { struct ImageHeader { using FaceOffsets = std::vector; using FaceBytes = std::vector; + // This is the byte offset from the _start_ of the image region. For example, level 0 + // will have a byte offset of 0. + const uint32_t _imageOffset; + const uint32_t _numFaces; const uint32_t _imageSize; const uint32_t _faceSize; const uint32_t _padding; - ImageHeader(bool cube, uint32_t imageSize, uint32_t padding) : + ImageHeader(bool cube, uint32_t imageOffset, uint32_t imageSize, uint32_t padding) : _numFaces(cube ? NUM_CUBEMAPFACES : 1), + _imageOffset(imageOffset), _imageSize(imageSize * _numFaces), _faceSize(imageSize), _padding(padding) { @@ -439,11 +444,11 @@ namespace ktx { struct Image : public ImageHeader { FaceBytes _faceBytes; Image(const ImageHeader& header, const FaceBytes& faces) : ImageHeader(header), _faceBytes(faces) {} - Image(uint32_t imageSize, uint32_t padding, const Byte* bytes) : - ImageHeader(false, imageSize, padding), + Image(uint32_t imageOffset, uint32_t imageSize, uint32_t padding, const Byte* bytes) : + ImageHeader(false, imageOffset, imageSize, padding), _faceBytes(1, bytes) {} - Image(uint32_t pageSize, uint32_t padding, const FaceBytes& cubeFaceBytes) : - ImageHeader(true, pageSize, padding) + Image(uint32_t imageOffset, uint32_t pageSize, uint32_t padding, const FaceBytes& cubeFaceBytes) : + ImageHeader(true, imageOffset, pageSize, padding) { if (cubeFaceBytes.size() == NUM_CUBEMAPFACES) { _faceBytes = cubeFaceBytes; diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp index bf72faeba5..b22f262e85 100644 --- a/libraries/ktx/src/ktx/Reader.cpp +++ b/libraries/ktx/src/ktx/Reader.cpp @@ -144,6 +144,7 @@ namespace ktx { while ((currentPtr - srcBytes) + sizeof(uint32_t) <= (srcSize)) { // Grab the imageSize coming up + uint32_t imageOffset = currentPtr - srcBytes; size_t imageSize = *reinterpret_cast(currentPtr); currentPtr += sizeof(uint32_t); @@ -158,10 +159,10 @@ namespace ktx { faces[face] = currentPtr; currentPtr += faceSize; } - images.emplace_back(Image((uint32_t) faceSize, padding, faces)); + images.emplace_back(Image(imageOffset, (uint32_t) faceSize, padding, faces)); currentPtr += padding; } else { - images.emplace_back(Image((uint32_t) imageSize, padding, currentPtr)); + images.emplace_back(Image(imageOffset, (uint32_t) imageSize, padding, currentPtr)); currentPtr += imageSize + padding; } } else { diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp index d149d559f9..4596bf00c0 100644 --- a/libraries/ktx/src/ktx/Writer.cpp +++ b/libraries/ktx/src/ktx/Writer.cpp @@ -211,6 +211,7 @@ namespace ktx { for (uint32_t l = 0; l < srcImages.size(); l++) { if (currentDataSize + sizeof(uint32_t) < allocatedImagesDataSize) { + uint32_t imageOffset = currentPtr - destBytes; size_t imageSize = srcImages[l]._imageSize; *(reinterpret_cast (currentPtr)) = (uint32_t) imageSize; currentPtr += sizeof(uint32_t); @@ -223,7 +224,7 @@ namespace ktx { // Single face vs cubes if (srcImages[l]._numFaces == 1) { memcpy(currentPtr, srcImages[l]._faceBytes[0], imageSize); - destImages.emplace_back(Image((uint32_t) imageSize, padding, currentPtr)); + destImages.emplace_back(Image(imageOffset, (uint32_t) imageSize, padding, currentPtr)); currentPtr += imageSize; } else { Image::FaceBytes faceBytes(NUM_CUBEMAPFACES); @@ -233,7 +234,7 @@ namespace ktx { faceBytes[face] = currentPtr; currentPtr += faceSize; } - destImages.emplace_back(Image(faceSize, padding, faceBytes)); + destImages.emplace_back(Image(imageOffset, faceSize, padding, faceBytes)); } currentPtr += padding; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 93a25330b1..592413e2bc 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -30,7 +30,6 @@ #include - #include #include @@ -395,6 +394,12 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) { _ktxMipRequest->setByteRange(range); } else { // TODO: Discover range for other mips + ByteRange range; + range.fromInclusive = ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData + + _ktxDescriptor->images[low]._imageOffset + 4; + range.toExclusive = ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData + + _ktxDescriptor->images[high + 1]._imageOffset; + _ktxMipRequest->setByteRange(range); } connect(_ktxMipRequest, &ResourceRequest::progress, this, &NetworkTexture::ktxMipRequestProgress); @@ -423,8 +428,17 @@ void NetworkTexture::ktxMipRequestFinished() { && _ktxMipLevelRangeInFlight.second == NULL_MIP_LEVEL; if (_ktxMipRequest->getResult() == ResourceRequest::Success) { - _ktxHighMipData = _ktxMipRequest->getData(); - maybeCreateKTX(); + if (_initialKtxLoaded) { + assert(_ktxMipLevelRangeInFlight.second - _ktxMipLevelRangeInFlight.first == 0); + + _textureSource->getGPUTexture()->assignStoredMip(_ktxMipLevelRangeInFlight.first, + _ktxMipRequest->getData().size(), reinterpret_cast(_ktxMipRequest->getData().data())); + //texture->assignStoredMip(level, image._imageSize, ktxData); + } else { + _ktxHighMipData = _ktxMipRequest->getData(); + maybeCreateKTX(); + } + } else { handleFailedRequest(_ktxMipRequest->getResult()); } @@ -457,7 +471,7 @@ void NetworkTexture::maybeCreateKTX() { auto memKtx = ktx::KTX::createBare(*header, keyValues); auto d = const_cast(memKtx->getStorage()->data()); - memcpy(d + memKtx->_storage->size() - _ktxHighMipData.size(), _ktxHighMipData.data(), _ktxHighMipData.size()); + ///memcpy(d + memKtx->_storage->size() - _ktxHighMipData.size(), _ktxHighMipData.data(), _ktxHighMipData.size()); auto textureCache = DependencyManager::get(); @@ -485,18 +499,21 @@ void NetworkTexture::maybeCreateKTX() { uint8_t* ktxData = reinterpret_cast(_ktxHighMipData.data()); ktxData += _ktxHighMipData.size(); // TODO Move image offset calculation to ktx ImageDescriptor - for (uint16_t i = images.size() - 1; i >= 0; --i) { - auto& image = images[i]; + uint16_t level; + for (level = images.size() - 1; level >= 0; --level) { + auto& image = images[level]; if (image._imageSize > imageSizeRemaining) { break; } - qDebug() << "Transferring " << i; + qDebug() << "Transferring " << level; ktxData -= image._imageSize; - texture->assignStoredMip(i, image._imageSize, ktxData); + texture->assignStoredMip(level, image._imageSize, ktxData); ktxData -= 4; imageSizeRemaining - image._imageSize - 4; } + _initialKtxLoaded = true; + // 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 @@ -506,6 +523,27 @@ void NetworkTexture::maybeCreateKTX() { setImage(texture, header->getPixelWidth(), header->getPixelHeight()); + + // Force load the next two levels + { + QTimer* timer = new QTimer(); + connect(timer, &QTimer::timeout, this, [=]() { + startMipRangeRequest(level, level); + }); + timer->setSingleShot(true); + timer->setInterval(4000); + timer->start(); + } + + { + QTimer* timer = new QTimer(); + connect(timer, &QTimer::timeout, this, [=]() { + startMipRangeRequest(level - 1, level - 1); + }); + timer->setSingleShot(true); + timer->setInterval(6000); + timer->start(); + } } } @@ -516,87 +554,6 @@ void NetworkTexture::downloadFinished(const QByteArray& data) { void NetworkTexture::loadContent(const QByteArray& content) { if (_sourceIsKTX) { assert(false); - if (_ktxLoadState == LOADING_HEADER) { - // TODO Handle case where we already have the source hash texture on disk - // TODO Handle case where data isn't as large as the ktx header - _ktxLoadState = LOADING_LOWEST_SIX; - auto header = reinterpret_cast(content.data()); - qDebug() << "Identifier:" << QString(QByteArray((char*)header->identifier, 12)); - qDebug() << "Type:" << header->glType; - qDebug() << "TypeSize:" << header->glTypeSize; - qDebug() << "numberOfArrayElements:" << header->numberOfArrayElements; - qDebug() << "numberOfFaces:" << header->numberOfFaces; - qDebug() << "numberOfMipmapLevels:" << header->numberOfMipmapLevels; - auto kvSize = header->bytesOfKeyValueData; - if (kvSize > content.size() - ktx::KTX_HEADER_SIZE) { - qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request"; - return; - } - - auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast(content.data()) + ktx::KTX_HEADER_SIZE); - - // Create bare ktx in memory - std::string filename = "test"; - auto memKtx = ktx::KTX::createBare(*header, keyValues); - - - auto textureCache = DependencyManager::get(); - - // Move ktx to file - const char* data = reinterpret_cast(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 << "file cache failed"; - } else { - _file = file; - } - - //auto texture = gpu::Texture::serializeHeader("test.ktx", *header, keyValues); - gpu::TexturePointer texture; - texture.reset(gpu::Texture::unserialize(_file->getFilepath(), memKtx->toDescriptor())); - texture->setKtxBacking(file->getFilepath()); - - // 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 - if (textureCache) { - texture = textureCache->cacheTextureByHash(filename, texture); - } - - - - auto desc = memKtx->toDescriptor(); - int numMips = desc.images.size(); - auto numMipsToGet = glm::min(numMips, 6); - auto sizeOfTopMips = 0; - for (int i = 0; i < numMipsToGet; ++i) { - auto mipLevel = numMips - 1 - i; - auto& img = desc.images[mipLevel]; - sizeOfTopMips += img._imageSize; - } - _requestByteRange.fromInclusive = length - sizeOfTopMips; - _requestByteRange.toExclusive = length; - QMetaObject::invokeMethod(this, "attemptRequest", Qt::QueuedConnection); - - - //texture->setMinMip(desc.images.size() - 1); - setImage(texture, header->getPixelWidth(), header->getPixelHeight()); - - } else { - qDebug() << "Got highest 6 mips"; - - ktx::StoragePointer storage { new storage::FileStorage(QString::fromStdString(_file->getFilepath())) }; - auto data = storage->mutableData(); - auto size = storage->getSize(); - //*data = 'H'; - memcpy(data + _requestByteRange.fromInclusive, content.data(), content.size()); - //getGPUTexture()->setMinMip(getGPUTexture()->getMinMip() - 6); - //auto ktxPointer = ktx::KTX::create(storage); - - //ktxPointer->writeMipData(level, data, size); - } return; } diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index a029b5b147..c032a9c29d 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -93,7 +93,8 @@ private: }; - KTXLoadState _ktxLoadState { LOADING_HEADER }; + bool _initialKtxLoaded { false }; + //KTXLoadState _ktxLoadState; KTXFilePointer _file; static const uint16_t NULL_MIP_LEVEL; bool _sourceIsKTX { false }; diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp index f07ab4450b..8958eeaf3b 100644 --- a/libraries/networking/src/HTTPResourceRequest.cpp +++ b/libraries/networking/src/HTTPResourceRequest.cpp @@ -65,7 +65,8 @@ void HTTPResourceRequest::doSend() { if (_byteRange.fromInclusive < 0) { byteRange = QString("bytes=%1").arg(_byteRange.fromInclusive); } else { - byteRange = QString("bytes=%1-%2").arg(_byteRange.fromInclusive).arg(_byteRange.toExclusive); + // HTTP byte ranges are inclusive on the `to` end: [from, to] + byteRange = QString("bytes=%1-%2").arg(_byteRange.fromInclusive).arg(_byteRange.toExclusive - 1); } qDebug() << "Setting http range to " << byteRange; networkRequest.setRawHeader("Range", byteRange.toLatin1());