From 243120b95c99d9c3d555bbb45022d4c33a998ea5 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 4 Oct 2019 14:57:39 -0700 Subject: [PATCH] Even better frame capture --- interface/src/Application.cpp | 2 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 63 +++++++----- libraries/gpu/src/gpu/FrameIO.cpp | 98 ++++++++++--------- libraries/gpu/src/gpu/FrameIO.h | 27 +++-- libraries/gpu/src/gpu/FrameReader.cpp | 59 ++++------- libraries/gpu/src/gpu/FrameWriter.cpp | 20 ++-- libraries/gpu/src/gpu/Texture.h | 3 + libraries/gpu/src/gpu/Texture_ktx.cpp | 85 +++++++++++----- libraries/ktx/src/ktx/KTX.h | 2 +- libraries/shared/src/shared/FileUtils.cpp | 13 +++ libraries/shared/src/shared/FileUtils.h | 7 +- libraries/shared/src/shared/Storage.h | 15 ++- tools/gpu-frame-player/src/PlayerWindow.cpp | 13 +-- 13 files changed, 242 insertions(+), 165 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c9b976cc14..b4a37519a6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4441,7 +4441,7 @@ void Application::keyPressEvent(QKeyEvent* event) { static const QString GPU_FRAME_FOLDER = QProcessEnvironment::systemEnvironment().contains(HIFI_FRAMES_FOLDER_VAR) ? QProcessEnvironment::systemEnvironment().value(HIFI_FRAMES_FOLDER_VAR) : "hifiFrames"; - static QString GPU_FRAME_TEMPLATE = GPU_FRAME_FOLDER + "/{DATE}_{TIME}.hfb"; + static QString GPU_FRAME_TEMPLATE = GPU_FRAME_FOLDER + "/{DATE}_{TIME}"; QString fullPath = FileUtils::computeDocumentPath(FileUtils::replaceDateTimeTokens(GPU_FRAME_TEMPLATE)); if (FileUtils::canCreateFile(fullPath)) { getActiveDisplayPlugin()->captureFrame(fullPath.toStdString()); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index e69791e73d..6ca11688a2 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -476,34 +477,50 @@ void OpenGLDisplayPlugin::submitFrame(const gpu::FramePointer& newFrame) { }); } +ktx::StoragePointer textureToKtx(const gpu::Texture& texture) { + ktx::Header header; + { + auto gpuDims = texture.getDimensions(); + header.pixelWidth = gpuDims.x; + header.pixelHeight = gpuDims.y; + header.pixelDepth = 0; + } + + { + auto gltexelformat = gpu::gl::GLTexelFormat::evalGLTexelFormat(texture.getStoredMipFormat()); + header.glInternalFormat = gltexelformat.internalFormat; + header.glFormat = gltexelformat.format; + header.glBaseInternalFormat = gltexelformat.format; + header.glType = gltexelformat.type; + header.glTypeSize = 1; + header.numberOfMipmapLevels = 1 + texture.getMaxMip(); + } + + auto memKtx = ktx::KTX::createBare(header); + auto storage = memKtx->_storage; + uint32_t faceCount = std::max(header.numberOfFaces, 1u); + uint32_t mipCount = std::max(header.numberOfMipmapLevels, 1u); + for (uint32_t mip = 0; mip < mipCount; ++mip) { + for (uint32_t face = 0; face < faceCount; ++face) { + const auto& image = memKtx->_images[mip]; + auto& faceBytes = const_cast(image._faceBytes[face]); + if (texture.isStoredMipFaceAvailable(mip, face)) { + auto storedImage = texture.accessStoredMipFace(mip, face); + auto storedSize = storedImage->size(); + memcpy(faceBytes, storedImage->data(), storedSize); + } + } + } + return storage; +} + void OpenGLDisplayPlugin::captureFrame(const std::string& filename) const { withOtherThreadContext([&] { using namespace gpu; auto glBackend = const_cast(*this).getGLBackend(); FramebufferPointer framebuffer{ Framebuffer::create("captureFramebuffer") }; - TextureCapturer captureLambda = [&](std::vector& outputBuffer, const gpu::TexturePointer& texture, uint16 layer) { - QImage image; - if (texture->getUsageType() == TextureUsageType::STRICT_RESOURCE) { - image = QImage{ 1, 1, QImage::Format_ARGB32 }; - auto storedImage = texture->accessStoredMipFace(0, 0); - memcpy(image.bits(), storedImage->data(), image.sizeInBytes()); - //if (texture == textureCache->getWhiteTexture()) { - //} else if (texture == textureCache->getBlackTexture()) { - //} else if (texture == textureCache->getBlueTexture()) { - //} else if (texture == textureCache->getGrayTexture()) { - } else { - ivec4 rect = { 0, 0, texture->getWidth(), texture->getHeight() }; - framebuffer->setRenderBuffer(0, texture, layer); - glBackend->syncGPUObject(*framebuffer); - - image = QImage{ rect.z, rect.w, QImage::Format_ARGB32 }; - glBackend->downloadFramebuffer(framebuffer, rect, image); - } - QBuffer buffer; - QImageWriter(&buffer, "png").write(image); - const auto& data = buffer.data(); - outputBuffer.resize(data.size()); - memcpy(outputBuffer.data(), data.constData(), data.size()); + TextureCapturer captureLambda = [&](const gpu::TexturePointer& texture)->storage::StoragePointer { + return textureToKtx(*texture); }; if (_currentFrame) { diff --git a/libraries/gpu/src/gpu/FrameIO.cpp b/libraries/gpu/src/gpu/FrameIO.cpp index 12e07b6e2a..a0f21df881 100644 --- a/libraries/gpu/src/gpu/FrameIO.cpp +++ b/libraries/gpu/src/gpu/FrameIO.cpp @@ -8,6 +8,7 @@ #include "FrameIO.h" #include +#include using namespace gpu::hfb; @@ -30,30 +31,44 @@ static bool read(const uint8_t*& ptr, size_t& remaining, T& output) { return skip(ptr, remaining, readSize); } -Descriptor Descriptor::parse(const uint8_t* const data, size_t size) { - const auto* ptr = data; - auto remaining = size; - Descriptor result; - if (!read(ptr, remaining, result.header)) { - return {}; - } - if (result.header.length != size) { - return {}; - } +Descriptor::Descriptor(const StoragePointer& storage) : storage(storage) { + const auto* const start = storage->data(); + const auto* ptr = storage->data(); + auto remaining = storage->size(); - while (remaining != 0) { - result.chunks.emplace_back(); - auto& chunk = result.chunks.back(); - ChunkHeader& chunkHeader = chunk; - if (!read(ptr, remaining, chunkHeader)) { - return {}; + try { + // Can't parse files more than 4GB + if (remaining > UINT32_MAX) { + throw std::runtime_error("File too large"); } - chunk.offset = ptr - data; - if (!skip(ptr, remaining, chunk.length)) { - return {}; + + if (!read(ptr, remaining, header)) { + throw std::runtime_error("Couldn't read binary header"); } + + if (header.length != storage->size()) { + throw std::runtime_error("Header/Actual size mismatch"); + } + + while (remaining != 0) { + chunks.emplace_back(); + auto& chunk = chunks.back(); + ChunkHeader& chunkHeader = chunk; + if (!read(ptr, remaining, chunkHeader)) { + throw std::runtime_error("Coulnd't read chunk header"); + } + chunk.offset = (uint32_t)(ptr - start); + if (chunk.end() > storage->size()) { + throw std::runtime_error("Chunk too large for file"); + } + if (!skip(ptr, remaining, chunk.length)) { + throw std::runtime_error("Skip chunk data failed"); + } + } + } catch (const std::runtime_error&) { + // LOG somnething + header.magic = 0; } - return result; } size_t Chunk::end() const { @@ -62,30 +77,15 @@ size_t Chunk::end() const { return result; } - -bool Descriptor::getChunkString(std::string& output, size_t chunkIndex, const uint8_t* const data, size_t size) { +StoragePointer Descriptor::getChunk(uint32_t chunkIndex) const { if (chunkIndex >= chunks.size()) { - return false; + return {}; } const auto& chunk = chunks[chunkIndex]; - if (chunk.end() > size) { - return false; + if (chunk.end() > storage->size()) { + return {}; } - output = std::string{ (const char*)(data + chunk.offset), chunk.length }; - return true; -} - -bool Descriptor::getChunkBuffer(Buffer& output, size_t chunkIndex, const uint8_t* const data, size_t size) { - if (chunkIndex >= chunks.size()) { - return false; - } - const auto& chunk = chunks[chunkIndex]; - if (chunk.end() > size) { - return false; - } - output.resize(chunk.length); - memcpy(output.data(), data + chunk.offset, chunk.length); - return true; + return storage->createView(chunk.length, chunk.offset); } static void writeUint(uint8_t*& dest, uint32_t value) { @@ -105,12 +105,15 @@ static void writeChunk(uint8_t*& dest, uint32_t chunkType, const T& chunkData) { void gpu::hfb::writeFrame(const std::string& filename, const std::string& json, const Buffer& binaryBuffer, - const Buffers& pngBuffers) { + const StorageBuilders& ktxBuilders) { uint32_t strLen = (uint32_t)json.size(); uint32_t size = gpu::hfb::HEADER_SIZE + gpu::hfb::CHUNK_HEADER_SIZE + strLen; size += gpu::hfb::CHUNK_HEADER_SIZE + (uint32_t)binaryBuffer.size(); - for (const auto& pngBuffer : pngBuffers) { - size += gpu::hfb::CHUNK_HEADER_SIZE + (uint32_t)pngBuffer.size(); + for (const auto& builder : ktxBuilders) { + auto storage = builder(); + if (storage) { + size += gpu::hfb::CHUNK_HEADER_SIZE + (uint32_t)storage->size(); + } } auto outputConst = storage::FileStorage::create(filename.c_str(), size, nullptr); @@ -121,8 +124,13 @@ void gpu::hfb::writeFrame(const std::string& filename, writeUint(ptr, size); writeChunk(ptr, gpu::hfb::CHUNK_TYPE_JSON, json); writeChunk(ptr, gpu::hfb::CHUNK_TYPE_BIN, binaryBuffer); - for (const auto& png : pngBuffers) { - writeChunk(ptr, gpu::hfb::CHUNK_TYPE_PNG, png); + for (const auto& builder : ktxBuilders) { + static StoragePointer EMPTY_STORAGE{ std::make_shared(0, nullptr) }; + auto storage = builder(); + if (!storage) { + storage = EMPTY_STORAGE; + } + writeChunk(ptr, gpu::hfb::CHUNK_TYPE_KTX, *storage); } assert((ptr - output->data()) == size); } diff --git a/libraries/gpu/src/gpu/FrameIO.h b/libraries/gpu/src/gpu/FrameIO.h index bb1dfa0f8c..77602ef4e8 100644 --- a/libraries/gpu/src/gpu/FrameIO.h +++ b/libraries/gpu/src/gpu/FrameIO.h @@ -12,28 +12,34 @@ #include "Forward.h" #include "Format.h" +#include + #include namespace gpu { -using TextureCapturer = std::function&, const TexturePointer&, uint16 layer)>; -using TextureLoader = std::function&, const TexturePointer&, uint16 layer)>; +using TextureCapturer = std::function; +//using TextureLoader = std::function; void writeFrame(const std::string& filename, const FramePointer& frame, const TextureCapturer& capturer = nullptr); -FramePointer readFrame(const std::string& filename, uint32_t externalTexture, const TextureLoader& loader = nullptr); +FramePointer readFrame(const std::string& filename, uint32_t externalTexture); namespace hfb { +using Storage = storage::Storage; +using StoragePointer = storage::Pointer; +using StorageBuilders = storage::Builders; + constexpr char* const EXTENSION{ ".hfb" }; constexpr uint32_t HEADER_SIZE{ sizeof(uint32_t) * 3 }; constexpr uint32_t CHUNK_HEADER_SIZE = sizeof(uint32_t) * 2; constexpr uint32_t MAGIC{ 0x49464948 }; constexpr uint32_t VERSION{ 0x01 }; constexpr uint32_t CHUNK_TYPE_JSON{ 0x4E4F534A }; +constexpr uint32_t CHUNK_TYPE_KTX{ 0x0058544b }; constexpr uint32_t CHUNK_TYPE_BIN{ 0x004E4942 }; constexpr uint32_t CHUNK_TYPE_PNG{ 0x00474E50 }; using Buffer = std::vector; -using Buffers = std::vector; struct Header { uint32_t magic{ 0 }; @@ -55,16 +61,21 @@ struct Chunk : public ChunkHeader { using Chunks = std::vector; struct Descriptor { + using Pointer = std::shared_ptr; + Header header; Chunks chunks; + StoragePointer storage; + Descriptor(const StoragePointer& storage); operator bool() const { return header.magic == MAGIC; } - bool getChunkString(std::string& output, size_t chunk, const uint8_t* const data, size_t size); - bool getChunkBuffer(Buffer& output, size_t chunk, const uint8_t* const data, size_t size); - static Descriptor parse(const uint8_t* const data, size_t size); + StoragePointer getChunk(uint32_t chunk) const; }; -void writeFrame(const std::string& filename, const std::string& json, const Buffer& binaryBuffer, const Buffers& pngBuffers); +void writeFrame(const std::string& filename, + const std::string& json, + const Buffer& binaryBuffer, + const StorageBuilders& pngBuffers); } // namespace hfb diff --git a/libraries/gpu/src/gpu/FrameReader.cpp b/libraries/gpu/src/gpu/FrameReader.cpp index 1f94828119..d636c6aaca 100644 --- a/libraries/gpu/src/gpu/FrameReader.cpp +++ b/libraries/gpu/src/gpu/FrameReader.cpp @@ -10,9 +10,7 @@ #include #include -#include -#include - +#include #include #include "Frame.h" #include "Batch.h" @@ -33,7 +31,7 @@ public: auto lastSlash = filename.rfind('/'); result = filename.substr(0, lastSlash + 1); } else { - result = QFileInfo(filename.c_str()).absoluteDir().canonicalPath().toStdString(); + result = FileUtils::getParentPath(filename.c_str()).toStdString(); if (*result.rbegin() != '/') { result += '/'; } @@ -41,18 +39,17 @@ public: return result; } - Deserializer(const std::string& filename_, uint32_t externalTexture = 0, const TextureLoader& loader = {}) : + Deserializer(const std::string& filename_, uint32_t externalTexture = 0) : filename(filename_), basedir(getBaseDir(filename_)), mappedFile(std::make_shared(filename.c_str())), - externalTexture(externalTexture), textureLoader(loader) { - descriptor = hfb::Descriptor::parse(mappedFile->data(), (uint32_t)mappedFile->size()); + externalTexture(externalTexture) { + descriptor = std::make_shared(mappedFile); } const std::string filename; const std::string basedir; const StoragePointer mappedFile; const uint32_t externalTexture; - hfb::Descriptor descriptor; - TextureLoader textureLoader; + hfb::Descriptor::Pointer descriptor; std::vector shaders; std::vector programs; std::vector textures; @@ -70,19 +67,8 @@ public: FramePointer deserializeFrame(); std::string getStringChunk(size_t chunkIndex) { - std::string result; - if (!descriptor.getChunkString(result, chunkIndex, mappedFile->data(), mappedFile->size())) { - return {}; - } - return result; - } - - hfb::Buffer getBufferChunk(size_t chunkIndex) { - hfb::Buffer result; - if (!descriptor.getChunkBuffer(result, chunkIndex, mappedFile->data(), mappedFile->size())) { - return {}; - } - return result; + auto storage = descriptor->getChunk((uint32_t)chunkIndex); + return std::string{ (const char*)storage->data(), storage->size() }; } void readBuffers(const json& node); @@ -240,8 +226,8 @@ public: static void readCommand(const json& node, Batch& batch); }; -FramePointer readFrame(const std::string& filename, uint32_t externalTexture, const TextureLoader& loader) { - return Deserializer(filename, externalTexture, loader).readFrame(); +FramePointer readFrame(const std::string& filename, uint32_t externalTexture) { + return Deserializer(filename, externalTexture).readFrame(); } } // namespace gpu @@ -249,7 +235,7 @@ FramePointer readFrame(const std::string& filename, uint32_t externalTexture, co using namespace gpu; void Deserializer::readBuffers(const json& buffersNode) { - const auto& binaryChunk = descriptor.chunks[1]; + const auto& binaryChunk = descriptor->chunks[1]; const auto* mapped = mappedFile->data() + binaryChunk.offset; const auto mappedSize = binaryChunk.length; size_t bufferCount = buffersNode.size(); @@ -333,7 +319,7 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) { std::string ktxFile; readOptional(ktxFile, node, keys::ktxFile); if (!ktxFile.empty()) { - if (!QFileInfo(ktxFile.c_str()).exists()) { + if (!FileUtils::exists(ktxFile.c_str())) { qDebug() << "Warning" << ktxFile.c_str() << " not found, ignoring"; ktxFile = {}; } @@ -347,8 +333,8 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) { frameReaderPath.replace("libraries/gpu/src/gpu/framereader.cpp", "interface/resources", Qt::CaseInsensitive); ktxFile.replace(0, 1, frameReaderPath.toStdString()); } - if (QFileInfo(ktxFile.c_str()).isRelative()) { - ktxFile = basedir + ktxFile; + if (FileUtils::isRelative(ktxFile.c_str())) { + ktxFile = basedir + "/" + ktxFile; } ktx::StoragePointer ktxStorage{ new storage::FileStorage(ktxFile.c_str()) }; auto ktxObject = ktx::KTX::create(ktxStorage); @@ -381,19 +367,14 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) { auto& texture = *result; readOptional(texture._source, node, keys::source); - if (!ktxFile.empty()) { - if (QFileInfo(ktxFile.c_str()).isRelative()) { - ktxFile = basedir + "/" + ktxFile; - } + + if (chunkIndex != INVALID_CHUNK_INDEX) { + auto ktxChunk = descriptor->getChunk(chunkIndex); + texture.setKtxBacking(ktxChunk); + } else if (!ktxFile.empty()) { texture.setSource(ktxFile); texture.setKtxBacking(ktxFile); - } else if (chunkIndex != INVALID_CHUNK_INDEX) { - if (textureLoader) { - texture.setSource("Chunk " + std::to_string(chunkIndex)); - textureLoader(getBufferChunk(chunkIndex), result, 0); - } - } - + } return result; } diff --git a/libraries/gpu/src/gpu/FrameWriter.cpp b/libraries/gpu/src/gpu/FrameWriter.cpp index 85397f149a..761f37a620 100644 --- a/libraries/gpu/src/gpu/FrameWriter.cpp +++ b/libraries/gpu/src/gpu/FrameWriter.cpp @@ -34,7 +34,7 @@ public: std::unordered_map queryMap; std::unordered_set captureTextures; hfb::Buffer binaryBuffer; - hfb::Buffers pngBuffers; + hfb::StorageBuilders ktxBuilders; Serializer(const std::string& basename, const TextureCapturer& capturer) : filename(basename + hfb::EXTENSION), textureCapturer(capturer) {} @@ -392,13 +392,17 @@ json Serializer::writeTexture(const TexturePointer& texturePointer) { const auto* storage = texture._storage.get(); const auto* ktxStorage = dynamic_cast(storage); if (ktxStorage) { - result[keys::ktxFile] = ktxStorage->_filename; + result[keys::chunk] = 2 + ktxBuilders.size(); + auto filename = ktxStorage->_filename; + ktxBuilders.push_back([=] { + return std::make_shared(filename.c_str()); + }); } else if (textureCapturer && captureTextures.count(texturePointer) != 0) { - auto layers = std::max(texture.getNumSlices(), 1); - result[keys::chunk] = 2 + pngBuffers.size(); - pngBuffers.push_back({}); - hfb::Buffer& pngBuffer = pngBuffers.back(); - textureCapturer(pngBuffer, texturePointer, 0); + result[keys::chunk] = 2 + ktxBuilders.size(); + auto storage = textureCapturer(texturePointer); + ktxBuilders.push_back([=] { + return storage; + }); } } return result; @@ -790,7 +794,7 @@ void Serializer::writeFrame(const Frame& frame) { writeBinaryBlob(); - hfb::writeFrame(filename, frameNode.dump(), binaryBuffer, pngBuffers); + hfb::writeFrame(filename, frameNode.dump(), binaryBuffer, ktxBuilders); } void Serializer::writeBinaryBlob() { diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 5e2485941d..debedf02a5 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -343,6 +343,7 @@ public: class KtxStorage : public Storage { public: + KtxStorage(const storage::StoragePointer& storage); KtxStorage(const std::string& filename); KtxStorage(const cache::FilePointer& file); PixelsPointer getMipFace(uint16 level, uint8 face = 0) const override; @@ -366,6 +367,7 @@ public: static std::vector, std::shared_ptr>> _cachedKtxFiles; static std::mutex _cachedKtxFilesMutex; + storage::StoragePointer _storage; std::string _filename; cache::FilePointer _cacheEntry; std::atomic _minMipLevelAvailable; @@ -543,6 +545,7 @@ public: Size getStoredSize() const; void setStorage(std::unique_ptr& newStorage); + void setKtxBacking(const storage::StoragePointer& storage); void setKtxBacking(const std::string& filename); void setKtxBacking(const cache::FilePointer& cacheEntry); diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index f471baf2c7..a5cea3e60e 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -159,7 +159,31 @@ struct IrradianceKTXPayload { }; const std::string IrradianceKTXPayload::KEY{ "hifi.irradianceSH" }; -KtxStorage::KtxStorage(const cache::FilePointer& cacheEntry) : KtxStorage(cacheEntry->getFilepath()) { +KtxStorage::KtxStorage(const storage::StoragePointer& storage) : _storage(storage) { + auto ktxPointer = ktx::KTX::create(storage); + _ktxDescriptor.reset(new ktx::KTXDescriptor(ktxPointer->toDescriptor())); + if (_ktxDescriptor->images.size() < _ktxDescriptor->header.numberOfMipmapLevels) { + qWarning() << "Bad images found in ktx"; + } + + _offsetToMinMipKV = _ktxDescriptor->getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY); + if (_offsetToMinMipKV) { + auto data = storage->data() + ktx::KTX_HEADER_SIZE + _offsetToMinMipKV; + _minMipLevelAvailable = *data; + } else { + // Assume all mip levels are available + _minMipLevelAvailable = 0; + } + + // 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; + } +} + +KtxStorage::KtxStorage(const cache::FilePointer& cacheEntry) : KtxStorage(cacheEntry->getFilepath()) { _cacheEntry = cacheEntry; } @@ -228,28 +252,31 @@ void KtxStorage::releaseOpenKtxFiles() { PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const { auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face); auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face); + storage::StoragePointer storageView; if (faceSize != 0 && faceOffset != 0) { - std::lock_guard lock(*_cacheFileMutex); - auto file = maybeOpenFile(); - if (file) { - auto storageView = file->createView(faceSize, faceOffset); - if (storageView) { - return storageView->toMemoryStorage(); - } else { - qWarning() << "Failed to get a valid storageView for faceSize=" << faceSize << " faceOffset=" << faceOffset << "out of valid file " << QString::fromStdString(_filename); - } + if (_storage) { + storageView = _storage->createView(faceSize, faceOffset); } else { - qWarning() << "Failed to get a valid file out of maybeOpenFile " << QString::fromStdString(_filename); + std::lock_guard lock(*_cacheFileMutex); + auto file = maybeOpenFile(); + if (file) { + storageView = file->createView(faceSize, faceOffset); + } else { + qWarning() << "Failed to get a valid file out of maybeOpenFile " << QString::fromStdString(_filename); + } } } - return nullptr; + if (!storageView) { + qWarning() << "Failed to get a valid storageView for faceSize=" << faceSize << " faceOffset=" << faceOffset + << "out of valid file " << QString::fromStdString(_filename); + } + return storageView->toMemoryStorage(); } Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const { return _ktxDescriptor->getMipFaceTexelsSize(level, face); } - bool KtxStorage::isMipAvailable(uint16 level, uint8 face) const { return level >= _minMipLevelAvailable; } @@ -271,7 +298,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor auto& imageDesc = _ktxDescriptor->images[level]; if (storage->size() != imageDesc._imageSize) { qWarning() << "Invalid image size: " << storage->size() << ", expected: " << imageDesc._imageSize - << ", level: " << level << ", filename: " << QString::fromStdString(_filename); + << ", level: " << level << ", filename: " << QString::fromStdString(_filename); return; } @@ -311,8 +338,7 @@ void KtxStorage::assignMipFaceData(uint16 level, uint8 face, const storage::Stor throw std::runtime_error("Invalid call"); } -bool validKtx(const std::string& filename) { - ktx::StoragePointer storage { new storage::FileStorage(filename.c_str()) }; +bool validKtx(const storage::StoragePointer& storage) { auto ktxPointer = ktx::KTX::create(storage); if (!ktxPointer) { return false; @@ -320,6 +346,21 @@ bool validKtx(const std::string& filename) { return true; } +bool validKtx(const std::string& filename) { + ktx::StoragePointer storage{ new storage::FileStorage(filename.c_str()) }; + return validKtx(storage); +} + +void Texture::setKtxBacking(const storage::StoragePointer& storage) { + // Check the KTX file for validity before using it as backing storage + if (!validKtx(storage)) { + return; + } + + auto newBacking = std::unique_ptr(new KtxStorage(storage)); + setStorage(newBacking); +} + void Texture::setKtxBacking(const std::string& filename) { // Check the KTX file for validity before using it as backing storage if (!validKtx(filename)) { @@ -355,7 +396,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { // Set Dimensions uint32_t numFaces = 1; switch (texture.getType()) { - case TEX_1D: { + case TEX_1D: { if (texture.isArray()) { header.set1DArray(texture.getWidth(), texture.getNumSlices()); } else { @@ -363,7 +404,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { } break; } - case TEX_2D: { + case TEX_2D: { if (texture.isArray()) { header.set2DArray(texture.getWidth(), texture.getHeight(), texture.getNumSlices()); } else { @@ -371,7 +412,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { } break; } - case TEX_3D: { + case TEX_3D: { if (texture.isArray()) { header.set3DArray(texture.getWidth(), texture.getHeight(), texture.getDepth(), texture.getNumSlices()); } else { @@ -379,7 +420,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { } break; } - case TEX_CUBE: { + case TEX_CUBE: { if (texture.isArray()) { header.setCubeArray(texture.getWidth(), texture.getHeight(), texture.getNumSlices()); } else { @@ -388,8 +429,8 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { numFaces = Texture::CUBE_FACE_COUNT; break; } - default: - return nullptr; + default: + return nullptr; } // Number level of mips coming diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index d755a482e3..0165113ec5 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -96,7 +96,7 @@ namespace ktx { using GLBaseInternalFormat = khronos::gl::texture::BaseInternalFormat; using Storage = storage::Storage; - using StoragePointer = std::shared_ptr; + using StoragePointer = std::shared_ptr; struct ImageDescriptor; using ImageDescriptors = std::vector; diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp index f2a4925351..164af091de 100644 --- a/libraries/shared/src/shared/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -176,3 +177,15 @@ bool FileUtils::canCreateFile(const QString& fullPath) { } return true; } + +QString FileUtils::getParentPath(const QString& fullPath) { + return QFileInfo(fullPath).absoluteDir().canonicalPath(); +} + +bool FileUtils::exists(const QString& fileName) { + return QFileInfo(fileName).exists(); +} + +bool FileUtils::isRelative(const QString& fileName) { + return QFileInfo(fileName).isRelative(); +} diff --git a/libraries/shared/src/shared/FileUtils.h b/libraries/shared/src/shared/FileUtils.h index 2f5e11f005..d4ff819e75 100644 --- a/libraries/shared/src/shared/FileUtils.h +++ b/libraries/shared/src/shared/FileUtils.h @@ -12,20 +12,23 @@ #ifndef hifi_FileUtils_h #define hifi_FileUtils_h -#include -#include +#include + class FileUtils { public: static const QStringList& getFileSelectors(); static QString selectFile(const QString& fileName); static void locateFile(const QString& fileName); + static bool exists(const QString& fileName); + static bool isRelative(const QString& fileName); static QString standardPath(QString subfolder); static QString readFile(const QString& filename); static QStringList readLines(const QString& filename, QString::SplitBehavior splitBehavior = QString::KeepEmptyParts); static QString replaceDateTimeTokens(const QString& path); static QString computeDocumentPath(const QString& path); static bool canCreateFile(const QString& fullPath); + static QString getParentPath(const QString& fullPath); }; #endif // hifi_FileUtils_h diff --git a/libraries/shared/src/shared/Storage.h b/libraries/shared/src/shared/Storage.h index 0e5032bb62..6a2cecf8b9 100644 --- a/libraries/shared/src/shared/Storage.h +++ b/libraries/shared/src/shared/Storage.h @@ -10,15 +10,22 @@ #ifndef hifi_Storage_h #define hifi_Storage_h -#include +#include #include #include -#include -#include +#include + +#include +#include namespace storage { class Storage; - using StoragePointer = std::shared_ptr; + using Pointer = std::shared_ptr; + using StoragePointer = Pointer; + // A function that can construct storage, useful for creating a list of + // things that can become storage without requiring that they all be instantiated at once + using Builder = std::function; + using Builders = std::vector; // Abstract class to represent memory that stored _somewhere_ (in system memory or in a file, for example) class Storage : public std::enable_shared_from_this { diff --git a/tools/gpu-frame-player/src/PlayerWindow.cpp b/tools/gpu-frame-player/src/PlayerWindow.cpp index db8d8e0f4d..8e7f730181 100644 --- a/tools/gpu-frame-player/src/PlayerWindow.cpp +++ b/tools/gpu-frame-player/src/PlayerWindow.cpp @@ -106,19 +106,8 @@ void PlayerWindow::resizeEvent(QResizeEvent* ev) { _renderThread.resize(ev->size()); } -void PlayerWindow::textureLoader(const std::vector& imageBytes, const gpu::TexturePointer& texture, uint16_t layer) { - QImage image; - QByteArray bytes{ (const char*)imageBytes.data(), (int)imageBytes.size() }; - QBuffer bytesBuffer(&bytes); - QImageReader(&bytesBuffer).read(&image); - if (layer > 0) { - return; - } - texture->assignStoredMip(0, image.byteCount(), image.constBits()); -} - void PlayerWindow::loadFrame(const QString& path) { - auto frame = gpu::readFrame(path.toStdString(), _renderThread._externalTexture, &PlayerWindow::textureLoader); + auto frame = gpu::readFrame(path.toStdString(), _renderThread._externalTexture); if (frame) { _renderThread.submitFrame(frame); if (!_renderThread.isThreaded()) {