diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index 36aaf75e81..7293c23052 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -222,6 +222,7 @@ GL45StrictResourceTexture::GL45StrictResourceTexture(const std::weak_ptrgetSize(); _bufferingLambda = [=] { + auto mipData = _parent._gpuObject.accessStoredMipFace(sourceMip, face); + _transferSize = mipData->getSize(); _buffer.resize(_transferSize); memcpy(&_buffer[0], mipData->readData(), _transferSize); _bufferingCompleted = true; @@ -100,12 +100,13 @@ TransferJob::TransferJob(const GL45VariableAllocationTexture& parent, uint16_t s } else { transferDimensions.y = lines; - auto dimensions = _parent._gpuObject.evalMipDimensions(sourceMip); - auto mipSize = mipData->getSize(); - auto bytesPerLine = (uint32_t)mipSize / dimensions.y; - _transferSize = bytesPerLine * lines; - auto sourceOffset = bytesPerLine * lineOffset; _bufferingLambda = [=] { + auto mipData = _parent._gpuObject.accessStoredMipFace(sourceMip, face); + auto dimensions = _parent._gpuObject.evalMipDimensions(sourceMip); + auto mipSize = mipData->getSize(); + auto bytesPerLine = (uint32_t)mipSize / dimensions.y; + _transferSize = bytesPerLine * lines; + auto sourceOffset = bytesPerLine * lineOffset; _buffer.resize(_transferSize); memcpy(&_buffer[0], mipData->readData() + sourceOffset, _transferSize); _bufferingCompleted = true; @@ -430,6 +431,7 @@ void GL45VariableAllocationTexture::executeNextTransfer(const TexturePointer& cu _currentTransferTexture = currentTexture; if (_pendingTransfers.front()->tryTransfer()) { _pendingTransfers.pop(); + _currentTransferTexture->finishTransfer(); _currentTransferTexture.reset(); } } @@ -454,7 +456,7 @@ GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr& backend _memoryPressureStateStale = true; copyMipsFromTexture(); syncSampler(); - + _gpuObject.finishTransfer(); } void GL45ResourceTexture::allocateStorage(uint16 allocatedMip) { @@ -585,10 +587,10 @@ void GL45ResourceTexture::populateTransferQueue() { // break down the transfers into chunks so that no single transfer is // consuming more than X bandwidth - auto mipData = _gpuObject.accessStoredMipFace(sourceMip, face); + auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face); const auto lines = mipDimensions.y; - auto bytesPerLine = (uint32_t)mipData->getSize() / lines; - Q_ASSERT(0 == (mipData->getSize() % lines)); + auto bytesPerLine = mipSize / lines; + Q_ASSERT(0 == (mipSize % lines)); uint32_t linesPerTransfer = (uint32_t)(MAX_TRANSFER_SIZE / bytesPerLine); uint32_t lineOffset = 0; while (lineOffset < lines) { diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 1f66b2900e..523d938854 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -149,6 +149,10 @@ PixelsPointer MemoryStorage::getMipFace(uint16 level, uint8 face) const { return PixelsPointer(); } +Size MemoryStorage::getMipFaceSize(uint16 level, uint8 face) const { + return getMipFace(level, face)->getSize(); +} + bool MemoryStorage::isMipAvailable(uint16 level, uint8 face) const { PixelsPointer mipFace = getMipFace(level, face); return (mipFace && mipFace->getSize()); @@ -980,3 +984,9 @@ Texture::ExternalUpdates Texture::getUpdates() const { void Texture::setStorage(std::unique_ptr& newStorage) { _storage.swap(newStorage); } + +void Texture::finishTransfer() const { + if (_storage) { + _storage->releaseTempResources(); + } +} diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index f7297b3280..f40e936578 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -25,6 +25,8 @@ namespace ktx { class KTX; using KTXUniquePointer = std::unique_ptr; + struct KTXDescriptor; + using KTXDescriptorPointer = std::unique_ptr; struct Header; } @@ -261,9 +263,11 @@ public: virtual void reset() = 0; virtual PixelsPointer getMipFace(uint16 level, uint8 face = 0) const = 0; + virtual Size getMipFaceSize(uint16 level, uint8 face = 0) const = 0; virtual void assignMipData(uint16 level, const storage::StoragePointer& storage) = 0; virtual void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) = 0; virtual bool isMipAvailable(uint16 level, uint8 face = 0) const = 0; + virtual void releaseTempResources() const {} Texture::Type getType() const { return _type; } Stamp getStamp() const { return _stamp; } @@ -286,6 +290,7 @@ public: public: void reset() override; PixelsPointer getMipFace(uint16 level, uint8 face = 0) const override; + Size getMipFaceSize(uint16 level, uint8 face = 0) const override; void assignMipData(uint16 level, const storage::StoragePointer& storage) override; void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) override; bool isMipAvailable(uint16 level, uint8 face = 0) const override; @@ -297,8 +302,9 @@ public: class KtxStorage : public Storage { public: - KtxStorage(ktx::KTXUniquePointer& ktxData); + KtxStorage(const std::string& filename); PixelsPointer getMipFace(uint16 level, uint8 face = 0) const override; + Size getMipFaceSize(uint16 level, uint8 face = 0) const override; // By convention, all mip levels and faces MUST be populated when using KTX backing bool isMipAvailable(uint16 level, uint8 face = 0) const override { return true; } @@ -310,9 +316,12 @@ public: throw std::runtime_error("Invalid call"); } void reset() override { } + void releaseTempResources() const override { _ktxData.release(); } protected: - ktx::KTXUniquePointer _ktxData; + std::string _filename; + ktx::KTXDescriptorPointer _ktxDescriptor; + mutable ktx::KTXUniquePointer _ktxData; friend class Texture; }; @@ -478,9 +487,10 @@ public: // Access the the sub mips bool isStoredMipFaceAvailable(uint16 level, uint8 face = 0) const { return _storage->isMipAvailable(level, face); } const PixelsPointer accessStoredMipFace(uint16 level, uint8 face = 0) const { return _storage->getMipFace(level, face); } + Size getStoredMipFaceSize(uint16 level, uint8 face = 0) const { return _storage->getMipFaceSize(level, face); } void setStorage(std::unique_ptr& newStorage); - void setKtxBacking(ktx::KTXUniquePointer& newBacking); + void setKtxBacking(const std::string& filename); // access sizes for the stored mips uint16 getStoredMipWidth(uint16 level) const; @@ -516,6 +526,8 @@ public: ExternalUpdates getUpdates() const; + void finishTransfer() const; + // Textures can be serialized directly to ktx data file, here is how static ktx::KTXUniquePointer serialize(const Texture& texture); static Texture* unserialize(const ktx::KTXUniquePointer& srcData, TextureUsageType usageType = TextureUsageType::RESOURCE, Usage usage = Usage(), const Sampler::Desc& sampler = Sampler::Desc()); diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 5f0ededee7..ef96810251 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -42,30 +42,33 @@ struct GPUKTXPayload { std::string GPUKTXPayload::KEY { "hifi.gpu" }; -KtxStorage::KtxStorage(ktx::KTXUniquePointer& ktxData) { - - // if the source ktx is valid let's config this KtxStorage correctly - if (ktxData && ktxData->getHeader()) { - - // now that we know the ktx, let's get the header info to configure this Texture::Storage: - Format mipFormat = Format::COLOR_BGRA_32; - Format texelFormat = Format::COLOR_SRGBA_32; - if (Texture::evalTextureFormat(*ktxData->getHeader(), mipFormat, texelFormat)) { - _format = mipFormat; - } - +KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) { + ktx::StoragePointer storage { new storage::FileStorage(_filename.c_str()) }; + auto ktxPointer = ktx::KTX::create(storage); + _ktxDescriptor.reset(new ktx::KTXDescriptor(ktxPointer->toDescriptor())); + // now that we know the ktx, let's get the header info to configure this Texture::Storage: + Format mipFormat = Format::COLOR_BGRA_32; + Format texelFormat = Format::COLOR_SRGBA_32; + if (Texture::evalTextureFormat(_ktxDescriptor->header, mipFormat, texelFormat)) { + _format = mipFormat; } - - _ktxData.reset(ktxData.release()); } PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const { + if (!_ktxData) { + ktx::StoragePointer storage { new storage::FileStorage(_filename.c_str()) }; + _ktxData = _ktxDescriptor->toKTX(storage); + } return _ktxData->getMipFaceTexelsData(level, face); } -void Texture::setKtxBacking(ktx::KTXUniquePointer& ktxBacking) { - auto newBacking = std::unique_ptr(new KtxStorage(ktxBacking)); +Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const { + return _ktxDescriptor->getMipFaceTexelsSize(level, face); +} + +void Texture::setKtxBacking(const std::string& filename) { + auto newBacking = std::unique_ptr(new KtxStorage(filename)); setStorage(newBacking); } @@ -181,7 +184,7 @@ Texture* Texture::unserialize(const ktx::KTXUniquePointer& srcData, TextureUsage if (!srcData) { return nullptr; } - const auto& header = *srcData->getHeader(); + const auto& header = srcData->getHeader(); Format mipFormat = Format::COLOR_BGRA_32; Format texelFormat = Format::COLOR_SRGBA_32; diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp index bbd4e1bc86..90ca19d762 100644 --- a/libraries/ktx/src/ktx/KTX.cpp +++ b/libraries/ktx/src/ktx/KTX.cpp @@ -108,47 +108,39 @@ KTX::~KTX() { void KTX::resetStorage(const StoragePointer& storage) { _storage = storage; + if (_storage->size() >= sizeof(Header)) { + memcpy(&_header, _storage->data(), sizeof(Header)); + } } -const Header* KTX::getHeader() const { - if (!_storage) { - return nullptr; - } - return reinterpret_cast(_storage->data()); +const Header& KTX::getHeader() const { + return _header; } size_t KTX::getKeyValueDataSize() const { - if (_storage) { - return getHeader()->bytesOfKeyValueData; - } else { - return 0; - } + return _header.bytesOfKeyValueData; } size_t KTX::getTexelsDataSize() const { - if (_storage) { - //return _storage->size() - (sizeof(Header) + getKeyValueDataSize()); - return (_storage->data() + _storage->size()) - getTexelsData(); - } else { + if (!_storage) { return 0; } + return (_storage->data() + _storage->size()) - getTexelsData(); } const Byte* KTX::getKeyValueData() const { - if (_storage) { - return (_storage->data() + sizeof(Header)); - } else { + if (!_storage) { return nullptr; } + return (_storage->data() + sizeof(Header)); } const Byte* KTX::getTexelsData() const { - if (_storage) { - return (_storage->data() + sizeof(Header) + getKeyValueDataSize()); - } else { + if (!_storage) { return nullptr; } + return (_storage->data() + sizeof(Header) + getKeyValueDataSize()); } storage::StoragePointer KTX::getMipFaceTexelsData(uint16_t mip, uint8_t face) const { @@ -163,3 +155,55 @@ storage::StoragePointer KTX::getMipFaceTexelsData(uint16_t mip, uint8_t face) co } return result; } + +size_t KTXDescriptor::getMipFaceTexelsSize(uint16_t mip, uint8_t face) const { + size_t result { 0 }; + if (mip < images.size()) { + const auto& faces = images[mip]; + if (face < faces._numFaces) { + result = faces._faceSize; + } + } + return result; +} + +ImageDescriptor Image::toImageDescriptor(const Byte* baseAddress) const { + FaceOffsets offsets; + offsets.resize(_faceBytes.size()); + for (size_t face = 0; face < _numFaces; ++face) { + offsets[face] = _faceBytes[face] - baseAddress; + } + // Note, implicit cast of *this to const ImageHeader& + return ImageDescriptor(*this, offsets); +} + +Image ImageDescriptor::toImage(const ktx::StoragePointer& storage) const { + FaceBytes faces; + faces.resize(_faceOffsets.size()); + for (size_t face = 0; face < _numFaces; ++face) { + faces[face] = storage->data() + _faceOffsets[face]; + } + // Note, implicit cast of *this to const ImageHeader& + return Image(*this, faces); +} + +KTXDescriptor KTX::toDescriptor() const { + ImageDescriptors newDescriptors; + auto storageStart = _storage ? _storage->data() : nullptr; + for (size_t i = 0; i < _images.size(); ++i) { + newDescriptors.emplace_back(_images[i].toImageDescriptor(storageStart)); + } + return { this->_header, this->_keyValues, newDescriptors }; +} + +std::unique_ptr KTXDescriptor::toKTX(const ktx::StoragePointer& storage) const { + Images newImages; + for (size_t i = 0; i < images.size(); ++i) { + newImages.emplace_back(images[i].toImage(storage)); + } + + return std::unique_ptr(new KTX { storage, header, keyValues, newImages }); +} + +KTX::KTX(const StoragePointer& storage, const Header& header, const KeyValues& keyValues, const Images& images) + : _storage(storage), _header(header), _keyValues(keyValues), _images(images) { } \ No newline at end of file diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 8e901b1105..c34127d357 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -407,43 +407,69 @@ namespace ktx { }; using KeyValues = KeyValue::KeyValues; - - struct Image { + struct ImageHeader { + using FaceOffsets = std::vector; using FaceBytes = std::vector; - - uint32_t _numFaces{ 1 }; - uint32_t _imageSize; - uint32_t _faceSize; - uint32_t _padding; - FaceBytes _faceBytes; - - - Image(uint32_t imageSize, uint32_t padding, const Byte* bytes) : - _numFaces(1), - _imageSize(imageSize), + 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) : + _numFaces(cube ? NUM_CUBEMAPFACES : 1), + _imageSize(imageSize * _numFaces), _faceSize(imageSize), - _padding(padding), - _faceBytes(1, bytes) {} + _padding(padding) { + } + }; + struct Image; + + struct ImageDescriptor : public ImageHeader { + const FaceOffsets _faceOffsets; + ImageDescriptor(const ImageHeader& header, const FaceOffsets& offsets) : ImageHeader(header), _faceOffsets(offsets) {} + Image toImage(const ktx::StoragePointer& storage) const; + }; + + using ImageDescriptors = std::vector; + + 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), + _faceBytes(1, bytes) {} Image(uint32_t pageSize, uint32_t padding, const FaceBytes& cubeFaceBytes) : - _numFaces(NUM_CUBEMAPFACES), - _imageSize(pageSize * NUM_CUBEMAPFACES), - _faceSize(pageSize), - _padding(padding) + ImageHeader(true, pageSize, padding) { if (cubeFaceBytes.size() == NUM_CUBEMAPFACES) { _faceBytes = cubeFaceBytes; } } + + ImageDescriptor toImageDescriptor(const Byte* baseAddress) const; }; + using Images = std::vector; + class KTX; + + // A KTX descriptor is a lightweight container for all the information about a serialized KTX file, but without the + // actual image / face data available. + struct KTXDescriptor { + KTXDescriptor(const Header& header, const KeyValues& keyValues, const ImageDescriptors& imageDescriptors) : header(header), keyValues(keyValues), images(imageDescriptors) {} + const Header header; + const KeyValues keyValues; + const ImageDescriptors images; + size_t getMipFaceTexelsSize(uint16_t mip = 0, uint8_t face = 0) const; + std::unique_ptr toKTX(const ktx::StoragePointer& storage) const; + }; + class KTX { void resetStorage(const StoragePointer& src); KTX(); + KTX(const StoragePointer& storage, const Header& header, const KeyValues& keyValues, const Images& images); public: - ~KTX(); // Define a KTX object manually to write it somewhere (in a file on disk?) @@ -475,18 +501,23 @@ namespace ktx { static Images parseImages(const Header& header, size_t srcSize, const Byte* srcBytes); // Access raw pointers to the main sections of the KTX - const Header* getHeader() const; + const Header& getHeader() const; + const Byte* getKeyValueData() const; const Byte* getTexelsData() const; storage::StoragePointer getMipFaceTexelsData(uint16_t mip = 0, uint8_t face = 0) const; const StoragePointer& getStorage() const { return _storage; } + KTXDescriptor toDescriptor() const; size_t getKeyValueDataSize() const; size_t getTexelsDataSize() const; + Header _header; StoragePointer _storage; KeyValues _keyValues; Images _images; + + friend struct KTXDescriptor; }; } diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp index 277ce42e69..bf72faeba5 100644 --- a/libraries/ktx/src/ktx/Reader.cpp +++ b/libraries/ktx/src/ktx/Reader.cpp @@ -185,10 +185,10 @@ namespace ktx { result->resetStorage(src); // read metadata - result->_keyValues = parseKeyValues(result->getHeader()->bytesOfKeyValueData, result->getKeyValueData()); + result->_keyValues = parseKeyValues(result->getHeader().bytesOfKeyValueData, result->getKeyValueData()); // populate image table - result->_images = parseImages(*result->getHeader(), result->getTexelsDataSize(), result->getTexelsData()); + result->_images = parseImages(result->getHeader(), result->getTexelsDataSize(), result->getTexelsData()); return result; } diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 5dfaddd471..17cafaa5ad 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -444,7 +444,7 @@ void NetworkTexture::loadContent(const QByteArray& content) { texture.reset(gpu::Texture::unserialize(ktx)); // Ensure that the texture population worked if (texture) { - texture->setKtxBacking(ktx); + texture->setKtxBacking(ktxFile->getFilepath()); texture = textureCache->cacheTextureByHash(hash, texture); } } @@ -586,10 +586,7 @@ void ImageReader::read() { qCWarning(modelnetworking) << _url << "file cache failed"; } else { resource.staticCast()->_file = file; - auto fileKtx = file->getKTX(); - if (fileKtx) { - texture->setKtxBacking(fileKtx); - } + texture->setKtxBacking(file->getFilepath()); } } diff --git a/tests/ktx/src/main.cpp b/tests/ktx/src/main.cpp index aa6795e17b..3a6fcabf43 100644 --- a/tests/ktx/src/main.cpp +++ b/tests/ktx/src/main.cpp @@ -111,38 +111,40 @@ int main(int argc, char** argv) { outFile.close(); } - auto ktxFile = ktx::KTX::create(std::shared_ptr(new storage::FileStorage(TEST_IMAGE_KTX))); { - const auto& memStorage = ktxMemory->getStorage(); - const auto& fileStorage = ktxFile->getStorage(); - Q_ASSERT(memStorage->size() == fileStorage->size()); - Q_ASSERT(memStorage->data() != fileStorage->data()); - Q_ASSERT(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size())); - Q_ASSERT(ktxFile->_images.size() == ktxMemory->_images.size()); - auto imageCount = ktxFile->_images.size(); - auto startMemory = ktxMemory->_storage->data(); - auto startFile = ktxFile->_storage->data(); - for (size_t i = 0; i < imageCount; ++i) { - auto memImages = ktxMemory->_images[i]; - auto fileImages = ktxFile->_images[i]; - Q_ASSERT(memImages._padding == fileImages._padding); - Q_ASSERT(memImages._numFaces == fileImages._numFaces); - Q_ASSERT(memImages._imageSize == fileImages._imageSize); - Q_ASSERT(memImages._faceSize == fileImages._faceSize); - Q_ASSERT(memImages._faceBytes.size() == memImages._numFaces); - Q_ASSERT(fileImages._faceBytes.size() == fileImages._numFaces); - auto faceCount = fileImages._numFaces; - for (uint32_t face = 0; face < faceCount; ++face) { - auto memFace = memImages._faceBytes[face]; - auto memOffset = memFace - startMemory; - auto fileFace = fileImages._faceBytes[face]; - auto fileOffset = fileFace - startFile; - Q_ASSERT(memOffset % 4 == 0); - Q_ASSERT(memOffset == fileOffset); + auto ktxFile = ktx::KTX::create(std::shared_ptr(new storage::FileStorage(TEST_IMAGE_KTX))); + { + const auto& memStorage = ktxMemory->getStorage(); + const auto& fileStorage = ktxFile->getStorage(); + Q_ASSERT(memStorage->size() == fileStorage->size()); + Q_ASSERT(memStorage->data() != fileStorage->data()); + Q_ASSERT(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size())); + Q_ASSERT(ktxFile->_images.size() == ktxMemory->_images.size()); + auto imageCount = ktxFile->_images.size(); + auto startMemory = ktxMemory->_storage->data(); + auto startFile = ktxFile->_storage->data(); + for (size_t i = 0; i < imageCount; ++i) { + auto memImages = ktxMemory->_images[i]; + auto fileImages = ktxFile->_images[i]; + Q_ASSERT(memImages._padding == fileImages._padding); + Q_ASSERT(memImages._numFaces == fileImages._numFaces); + Q_ASSERT(memImages._imageSize == fileImages._imageSize); + Q_ASSERT(memImages._faceSize == fileImages._faceSize); + Q_ASSERT(memImages._faceBytes.size() == memImages._numFaces); + Q_ASSERT(fileImages._faceBytes.size() == fileImages._numFaces); + auto faceCount = fileImages._numFaces; + for (uint32_t face = 0; face < faceCount; ++face) { + auto memFace = memImages._faceBytes[face]; + auto memOffset = memFace - startMemory; + auto fileFace = fileImages._faceBytes[face]; + auto fileOffset = fileFace - startFile; + Q_ASSERT(memOffset % 4 == 0); + Q_ASSERT(memOffset == fileOffset); + } } } } - testTexture->setKtxBacking(ktxFile); + testTexture->setKtxBacking(TEST_IMAGE_KTX.toStdString()); return 0; }