From 4fd83ddd0edc5e07658b6561dab82d5fd499e3cb Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 4 Dec 2017 10:05:55 -0800 Subject: [PATCH] Reduce Image memory usage by moving the data --- libraries/baking/src/TextureBaker.cpp | 14 +- libraries/image/src/image/Image.cpp | 351 ++++++++++-------- libraries/image/src/image/Image.h | 36 +- .../src/model-networking/TextureCache.cpp | 6 +- 4 files changed, 231 insertions(+), 176 deletions(-) diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index 1a320efabc..5951e7c2e8 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -121,8 +121,15 @@ void TextureBaker::handleTextureNetworkReply() { } void TextureBaker::processTexture() { - auto processedTexture = image::processImage(_originalTexture, _textureURL.toString().toStdString(), + // the baked textures need to have the source hash added for cache checks in Interface + // so we add that to the processed texture before handling it off to be serialized + auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5); + std::string hash = hashData.toHex().toStdString(); + + // IMPORTANT: _originalTexture is empty past this point + auto processedTexture = image::processImage(std::move(_originalTexture), _textureURL.toString().toStdString(), ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, _abortProcessing); + processedTexture->setSourceHash(hash); if (shouldStop()) { return; @@ -133,11 +140,6 @@ void TextureBaker::processTexture() { return; } - // the baked textures need to have the source hash added for cache checks in Interface - // so we add that to the processed texture before handling it off to be serialized - auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5); - std::string hash = hashData.toHex().toStdString(); - processedTexture->setSourceHash(hash); auto memKTX = gpu::Texture::serialize(*processedTexture); diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 9f584c844f..1236c19798 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -110,64 +110,64 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con } } -gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, true, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing); } -gpu::TexturePointer TextureUsage::create2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::create2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(srcImage, srcImageName, true, abortProcessing); + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, true, abortProcessing); } -gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(srcImage, srcImageName, true, abortProcessing); + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, true, abortProcessing); } -gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createCubeTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(srcImage, srcImageName, true, abortProcessing); + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing); } -gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } @@ -238,33 +238,43 @@ uint32 packR11G11B10F(const glm::vec3& color) { return glm::packF2x11_1x10(ucolor); } -gpu::TexturePointer processImage(const QByteArray& content, const std::string& filename, - int maxNumPixels, TextureUsage::Type textureType, - const std::atomic& abortProcessing) { +QImage processRawImageData(QByteArray&& content, const std::string& filename) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QByteArray localCopy = std::move(content); + // Help the QImage loader by extracting the image file format from the url filename ext. // Some tga are not created properly without it. auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); QBuffer buffer; - buffer.setData(content); + buffer.setData(localCopy); QImageReader imageReader(&buffer, filenameExtension.c_str()); - QImage image; if (imageReader.canRead()) { - image = imageReader.read(); + return imageReader.read(); } else { // Extension could be incorrect, try to detect the format from the content QImageReader newImageReader; newImageReader.setDecideFormatFromContent(true); - buffer.setData(content); + buffer.setData(localCopy); newImageReader.setDevice(&buffer); if (newImageReader.canRead()) { qCWarning(imagelogging) << "Image file" << filename.c_str() << "has extension" << filenameExtension.c_str() - << "but is actually a" << qPrintable(newImageReader.format()) << "(recovering)"; - image = newImageReader.read(); + << "but is actually a" << qPrintable(newImageReader.format()) << "(recovering)"; + return newImageReader.read(); } } + return QImage(); +} + +gpu::TexturePointer processImage(QByteArray&& content, const std::string& filename, + int maxNumPixels, TextureUsage::Type textureType, + const std::atomic& abortProcessing) { + + QImage image = processRawImageData(std::move(content), filename); + int imageWidth = image.width(); int imageHeight = image.height(); @@ -282,22 +292,26 @@ gpu::TexturePointer processImage(const QByteArray& content, const std::string& f int originalHeight = imageHeight; imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f); imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f); - QImage newImage = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - image.swap(newImage); + image = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); qCDebug(imagelogging).nospace() << "Downscaled " << filename.c_str() << " (" << QSize(originalWidth, originalHeight) << " to " << QSize(imageWidth, imageHeight) << ")"; } auto loader = TextureUsage::getTextureLoaderForType(textureType); - auto texture = loader(image, filename, abortProcessing); + auto texture = loader(std::move(image), filename, abortProcessing); return texture; } -QImage processSourceImage(const QImage& srcImage, bool cubemap) { +QImage processSourceImage(QImage&& srcImage, bool cubemap) { PROFILE_RANGE(resource_parse, "processSourceImage"); - const glm::uvec2 srcImageSize = toGlm(srcImage.size()); + + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QImage localCopy = std::move(srcImage); + + const glm::uvec2 srcImageSize = toGlm(localCopy.size()); glm::uvec2 targetSize = srcImageSize; while (glm::any(glm::greaterThan(targetSize, MAX_TEXTURE_SIZE))) { @@ -319,10 +333,10 @@ QImage processSourceImage(const QImage& srcImage, bool cubemap) { if (targetSize != srcImageSize) { PROFILE_RANGE(resource_parse, "processSourceImage Rectify"); qCDebug(imagelogging) << "Resizing texture from " << srcImageSize.x << "x" << srcImageSize.y << " to " << targetSize.x << "x" << targetSize.y; - return srcImage.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + return localCopy.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } - return srcImage; + return localCopy; } #if defined(NVTT_API) @@ -429,10 +443,14 @@ public: } }; -void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atomic& abortProcessing, int face) { - assert(image.format() == QIMAGE_HDR_FORMAT); +void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing, int face) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QImage localCopy = std::move(image); - const int width = image.width(), height = image.height(); + assert(localCopy.format() == QIMAGE_HDR_FORMAT); + + const int width = localCopy.width(), height = localCopy.height(); std::vector data; std::vector::iterator dataIt; auto mipFormat = texture->getStoredMipFormat(); @@ -471,10 +489,10 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom return; } - data.resize(width*height); + data.resize(width * height); dataIt = data.begin(); for (auto lineNb = 0; lineNb < height; lineNb++) { - const uint32* srcPixelIt = reinterpret_cast( image.constScanLine(lineNb) ); + const uint32* srcPixelIt = reinterpret_cast(localCopy.constScanLine(lineNb)); const uint32* srcPixelEnd = srcPixelIt + width; while (srcPixelIt < srcPixelEnd) { @@ -485,6 +503,9 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom } assert(dataIt == data.end()); + // We're done with the localCopy, free up the memory to avoid bloating the heap + localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. + nvtt::OutputOptions outputOptions; outputOptions.setOutputHeader(false); std::unique_ptr outputHandler; @@ -497,7 +518,7 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats outputHandler.reset(new PackedFloatOutputHandler(texture, face, mipFormat)); } else { - outputHandler.reset( new OutputHandler(texture, face) ); + outputHandler.reset(new OutputHandler(texture, face)); } outputOptions.setOutputHandler(outputHandler.get()); @@ -518,13 +539,17 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom } } -void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic& abortProcessing, int face) { - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); +void generateLDRMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing, int face) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QImage localCopy = std::move(image); + + if (localCopy.format() != QImage::Format_ARGB32) { + localCopy = localCopy.convertToFormat(QImage::Format_ARGB32); } - const int width = image.width(), height = image.height(); - const void* data = static_cast(image.constBits()); + const int width = localCopy.width(), height = localCopy.height(); + const void* data = static_cast(localCopy.constBits()); nvtt::TextureType textureType = nvtt::TextureType_2D; nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; @@ -537,7 +562,11 @@ void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic& abortProcessing = false, int face = -1) { +void generateMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing = false, int face = -1) { #if CPU_MIPMAPS PROFILE_RANGE(resource_parse, "generateMips"); if (image.format() == QIMAGE_HDR_FORMAT) { - generateHDRMips(texture, image, abortProcessing, face); + generateHDRMips(texture, std::move(image), abortProcessing, face); } else { - generateLDRMips(texture, image, abortProcessing, face); + generateLDRMips(texture, std::move(image), abortProcessing, face); } #else texture->setAutoGenerateMips(true); @@ -682,10 +711,11 @@ void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAs validAlpha = (numOpaques != NUM_PIXELS); } -gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool isStrict, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage"); - QImage image = processSourceImage(srcImage, false); + QImage image = processSourceImage(std::move(srcImage), false); + bool validAlpha = image.hasAlphaChannel(); bool alphaAsMask = false; @@ -731,7 +761,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& s } theTexture->setUsage(usage.build()); theTexture->setStoredMipFormat(formatMip); - generateMips(theTexture.get(), image, abortProcessing); + generateMips(theTexture.get(), std::move(image), abortProcessing); } return theTexture; @@ -749,16 +779,20 @@ double mapComponent(double sobelValue) { return (sobelValue + 1.0) * factor; } -QImage processBumpMap(QImage& image) { - if (image.format() != QImage::Format_Grayscale8) { - image = image.convertToFormat(QImage::Format_Grayscale8); +QImage processBumpMap(QImage&& image) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QImage localCopy = std::move(image); + + if (localCopy.format() != QImage::Format_Grayscale8) { + localCopy = localCopy.convertToFormat(QImage::Format_Grayscale8); } // PR 5540 by AlessandroSigna integrated here as a specialized TextureLoader for bumpmaps // The conversion is done using the Sobel Filter to calculate the derivatives from the grayscale image const double pStrength = 2.0; - int width = image.width(); - int height = image.height(); + int width = localCopy.width(); + int height = localCopy.height(); QImage result(width, height, QImage::Format_ARGB32); @@ -771,14 +805,14 @@ QImage processBumpMap(QImage& image) { const int jPrevClamped = clampPixelCoordinate(j - 1, height - 1); // surrounding pixels - const QRgb topLeft = image.pixel(iPrevClamped, jPrevClamped); - const QRgb top = image.pixel(iPrevClamped, j); - const QRgb topRight = image.pixel(iPrevClamped, jNextClamped); - const QRgb right = image.pixel(i, jNextClamped); - const QRgb bottomRight = image.pixel(iNextClamped, jNextClamped); - const QRgb bottom = image.pixel(iNextClamped, j); - const QRgb bottomLeft = image.pixel(iNextClamped, jPrevClamped); - const QRgb left = image.pixel(i, jPrevClamped); + const QRgb topLeft = localCopy.pixel(iPrevClamped, jPrevClamped); + const QRgb top = localCopy.pixel(iPrevClamped, j); + const QRgb topRight = localCopy.pixel(iPrevClamped, jNextClamped); + const QRgb right = localCopy.pixel(i, jNextClamped); + const QRgb bottomRight = localCopy.pixel(iNextClamped, jNextClamped); + const QRgb bottom = localCopy.pixel(iNextClamped, j); + const QRgb bottomLeft = localCopy.pixel(iNextClamped, jPrevClamped); + const QRgb left = localCopy.pixel(i, jPrevClamped); // take their gray intensities // since it's a grayscale image, the value of each component RGB is the same @@ -807,13 +841,13 @@ QImage processBumpMap(QImage& image) { return result; } -gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool isBumpMap, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage"); - QImage image = processSourceImage(srcImage, false); + QImage image = processSourceImage(std::move(srcImage), false); if (isBumpMap) { - image = processBumpMap(image); + image = processBumpMap(std::move(image)); } // Make sure the normal map source image is ARGB32 @@ -833,17 +867,17 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(const QImag theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); theTexture->setSource(srcImageName); theTexture->setStoredMipFormat(formatMip); - generateMips(theTexture.get(), image, abortProcessing); + generateMips(theTexture.get(), std::move(image), abortProcessing); } return theTexture; } -gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool isInvertedPixels, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage"); - QImage image = processSourceImage(srcImage, false); + QImage image = processSourceImage(std::move(srcImage), false); if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); @@ -869,7 +903,7 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(const QImag theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); theTexture->setSource(srcImageName); theTexture->setStoredMipFormat(formatMip); - generateMips(theTexture.get(), image, abortProcessing); + generateMips(theTexture.get(), std::move(image), abortProcessing); } return theTexture; @@ -939,7 +973,7 @@ public: static QImage extractEquirectangularFace(const QImage& source, gpu::Texture::CubeFace face, int faceWidth) { QImage image(faceWidth, faceWidth, source.format()); - glm::vec2 dstInvSize(1.0f / (float)image.width(), 1.0f / (float)image.height()); + glm::vec2 dstInvSize(1.0f / (float)source.width(), 1.0f / (float)source.height()); struct CubeToXYZ { gpu::Texture::CubeFace _face; @@ -1142,8 +1176,12 @@ const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) //#define DEBUG_COLOR_PACKING -QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { - QImage hdrImage(srcImage.width(), srcImage.height(), (QImage::Format)QIMAGE_HDR_FORMAT); +QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QImage localCopy = std::move(srcImage); + + QImage hdrImage(localCopy.width(), localCopy.height(), (QImage::Format)QIMAGE_HDR_FORMAT); std::function packFunc; #ifdef DEBUG_COLOR_PACKING std::function unpackFunc; @@ -1165,13 +1203,13 @@ QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { default: qCWarning(imagelogging) << "Unsupported HDR format"; Q_UNREACHABLE(); - return srcImage; + return localCopy; } - srcImage = srcImage.convertToFormat(QImage::Format_ARGB32); - for (auto y = 0; y < srcImage.height(); y++) { - const QRgb* srcLineIt = reinterpret_cast( srcImage.constScanLine(y) ); - const QRgb* srcLineEnd = srcLineIt + srcImage.width(); + localCopy = localCopy.convertToFormat(QImage::Format_ARGB32); + for (auto y = 0; y < localCopy.height(); y++) { + const QRgb* srcLineIt = reinterpret_cast( localCopy.constScanLine(y) ); + const QRgb* srcLineEnd = srcLineIt + localCopy.width(); uint32* hdrLineIt = reinterpret_cast( hdrImage.scanLine(y) ); glm::vec3 color; @@ -1196,86 +1234,99 @@ QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { return hdrImage; } -gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool generateIrradiance, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage"); + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + QImage localCopy = std::move(srcImage); + + int originalWidth = localCopy.width(); + int originalHeight = localCopy.height(); + if ((originalWidth <= 0) && (originalHeight <= 0)) { + return nullptr; + } + gpu::TexturePointer theTexture = nullptr; - if ((srcImage.width() > 0) && (srcImage.height() > 0)) { - QImage image = processSourceImage(srcImage, true); - if (image.format() != QIMAGE_HDR_FORMAT) { - image = convertToHDRFormat(image, HDR_FORMAT); + QImage image = processSourceImage(std::move(localCopy), true); + + if (image.format() != QIMAGE_HDR_FORMAT) { + image = convertToHDRFormat(std::move(image), HDR_FORMAT); + } + + gpu::Element formatMip; + gpu::Element formatGPU; + if (isCubeTexturesCompressionEnabled()) { + formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; + formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; + } else { + formatMip = HDR_FORMAT; + formatGPU = HDR_FORMAT; + } + + // Find the layout of the cubemap in the 2D image + // Use the original image size since processSourceImage may have altered the size / aspect ratio + int foundLayout = CubeLayout::findLayout(originalWidth, originalHeight); + + if (foundLayout < 0) { + qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str()); + return nullptr; + } + + std::vector faces; + + // If found, go extract the faces as separate images + auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout]; + if (layout._type == CubeLayout::FLAT) { + int faceWidth = image.width() / layout._widthRatio; + + faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror)); + } else if (layout._type == CubeLayout::EQUIRECTANGULAR) { + // THe face width is estimated from the input image + const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4; + const int EQUIRECT_MAX_FACE_WIDTH = 2048; + int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH); + for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) { + QImage faceImage = CubeLayout::extractEquirectangularFace(std::move(image), (gpu::Texture::CubeFace) face, faceWidth); + faces.push_back(std::move(faceImage)); } + } - gpu::Element formatMip; - gpu::Element formatGPU; - if (isCubeTexturesCompressionEnabled()) { - formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; - formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; - } else { - formatMip = HDR_FORMAT; - formatGPU = HDR_FORMAT; - } + // free up the memory afterward to avoid bloating the heap + image = QImage(); // QImage doesn't have a clear function, so override it with an empty one. - // Find the layout of the cubemap in the 2D image - // Use the original image size since processSourceImage may have altered the size / aspect ratio - int foundLayout = CubeLayout::findLayout(srcImage.width(), srcImage.height()); - - std::vector faces; - // If found, go extract the faces as separate images - if (foundLayout >= 0) { - auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout]; - if (layout._type == CubeLayout::FLAT) { - int faceWidth = image.width() / layout._widthRatio; - - faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror)); - } else if (layout._type == CubeLayout::EQUIRECTANGULAR) { - // THe face width is estimated from the input image - const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4; - const int EQUIRECT_MAX_FACE_WIDTH = 2048; - int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH); - for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) { - QImage faceImage = CubeLayout::extractEquirectangularFace(image, (gpu::Texture::CubeFace) face, faceWidth); - faces.push_back(faceImage); - } - } - } else { - qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str()); - return nullptr; - } - - // If the 6 faces have been created go on and define the true Texture - if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { - theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); - theTexture->setSource(srcImageName); - theTexture->setStoredMipFormat(formatMip); + // If the 6 faces have been created go on and define the true Texture + if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { + theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + theTexture->setSource(srcImageName); + theTexture->setStoredMipFormat(formatMip); + // Generate irradiance while we are at it + if (generateIrradiance) { + PROFILE_RANGE(resource_parse, "generateIrradiance"); + auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + irradianceTexture->setSource(srcImageName); + irradianceTexture->setStoredMipFormat(HDR_FORMAT); for (uint8 face = 0; face < faces.size(); ++face) { - generateMips(theTexture.get(), faces[face], abortProcessing, face); + irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); } - // Generate irradiance while we are at it - if (generateIrradiance) { - PROFILE_RANGE(resource_parse, "generateIrradiance"); - auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); - irradianceTexture->setSource(srcImageName); - irradianceTexture->setStoredMipFormat(HDR_FORMAT); - for (uint8 face = 0; face < faces.size(); ++face) { - irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); - } + irradianceTexture->generateIrradiance(); - irradianceTexture->generateIrradiance(); + auto irradiance = irradianceTexture->getIrradiance(); + theTexture->overrideIrradiance(irradiance); + } - auto irradiance = irradianceTexture->getIrradiance(); - theTexture->overrideIrradiance(irradiance); - } + for (uint8 face = 0; face < faces.size(); ++face) { + generateMips(theTexture.get(), std::move(faces[face]), abortProcessing, face); } } diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index 856dc009cf..43a4c30a7b 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -41,41 +41,41 @@ enum Type { UNUSED_TEXTURE }; -using TextureLoader = std::function&)>; +using TextureLoader = std::function&)>; TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap()); -gpu::TexturePointer create2DTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createStrict2DTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createAlbedoTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createEmissiveTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createNormalTextureFromNormalImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createNormalTextureFromBumpImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createRoughnessTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createRoughnessTextureFromGlossImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createMetallicTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createCubeTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createLightmapTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isStrict, +gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool isStrict, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureNormalMapFromImage(const QImage& srcImage, const std::string& srcImageName, bool isBumpMap, +gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool isBumpMap, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureGrayscaleFromImage(const QImage& srcImage, const std::string& srcImageName, bool isInvertedPixels, +gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool isInvertedPixels, const std::atomic& abortProcessing); -gpu::TexturePointer processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool generateIrradiance, +gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool generateIrradiance, const std::atomic& abortProcessing); } // namespace TextureUsage @@ -90,7 +90,7 @@ void setNormalTexturesCompressionEnabled(bool enabled); void setGrayscaleTexturesCompressionEnabled(bool enabled); void setCubeTexturesCompressionEnabled(bool enabled); -gpu::TexturePointer processImage(const QByteArray& content, const std::string& url, +gpu::TexturePointer processImage(QByteArray&& content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType, const std::atomic& abortProcessing = false); diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index d25c5225d6..b8f81dc7a4 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -264,7 +264,7 @@ gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) { gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type, QVariantMap options) { QImage image = QImage(path); auto loader = image::TextureUsage::getTextureLoaderForType(type, options); - return gpu::TexturePointer(loader(image, QUrl::fromLocalFile(path).fileName().toStdString(), false)); + return gpu::TexturePointer(loader(std::move(image), QUrl::fromLocalFile(path).fileName().toStdString(), false)); } QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, @@ -954,7 +954,9 @@ void ImageReader::read() { gpu::TexturePointer texture; { PROFILE_RANGE_EX(resource_parse_image_raw, __FUNCTION__, 0xffff0000, 0); - texture = image::processImage(_content, _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType()); + + // IMPORTANT: _content is empty past this point + texture = image::processImage(std::move(_content), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType()); if (!texture) { qCWarning(modelnetworking) << "Could not process:" << _url;