Add caching of imageOffset to ktx ImageDescriptor

This commit is contained in:
Ryan Huffman 2017-04-13 23:05:23 -07:00 committed by Atlante45
parent e9bb895bff
commit 39c3fee838
8 changed files with 79 additions and 104 deletions

View file

@ -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;
}
}

View file

@ -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<uint32_t>(evalImageSize(level));
ImageHeader header {
numberOfFaces == NUM_CUBEMAPFACES,
static_cast<uint32_t>(evalImageSize(level)),
imageOffset,
imageSize,
0
};
imageOffset += imageSize + 4;
ImageHeader::FaceOffsets offsets;
for (auto i = 0; i < numberOfFaces; ++i) {
offsets.push_back(0);

View file

@ -414,12 +414,17 @@ namespace ktx {
struct ImageHeader {
using FaceOffsets = std::vector<size_t>;
using FaceBytes = std::vector<const Byte*>;
// 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;

View file

@ -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<const uint32_t*>(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 {

View file

@ -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<uint32_t*> (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;

View file

@ -30,7 +30,6 @@
#include <gpu/Batch.h>
#include <image/Image.h>
#include <NumericalConstants.h>
@ -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<uint8_t*>(_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<uint8_t*>(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<TextureCache>();
@ -485,18 +499,21 @@ void NetworkTexture::maybeCreateKTX() {
uint8_t* ktxData = reinterpret_cast<uint8_t*>(_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<const ktx::Header*>(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<const ktx::Byte*>(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<TextureCache>();
// 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 << "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;
}

View file

@ -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 };

View file

@ -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());