Merge pull request #11920 from Atlante45/feat/reduce-memory-usage

Reduce Image library memory usage
This commit is contained in:
Clément Brisset 2017-12-11 09:29:22 -08:00 committed by GitHub
commit 833f98b98b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 230 additions and 175 deletions

View file

@ -113,8 +113,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;
@ -125,11 +132,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);

View file

@ -118,64 +118,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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& abortProcessing) {
return processCubeTextureColorFromImage(srcImage, srcImageName, false, abortProcessing);
return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing);
}
@ -246,33 +246,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<bool>& 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();
return newImageReader.read();
}
}
return QImage();
}
gpu::TexturePointer processImage(QByteArray&& content, const std::string& filename,
int maxNumPixels, TextureUsage::Type textureType,
const std::atomic<bool>& abortProcessing) {
QImage image = processRawImageData(std::move(content), filename);
int imageWidth = image.width();
int imageHeight = image.height();
@ -290,22 +300,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))) {
@ -327,10 +341,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)
@ -437,10 +451,14 @@ public:
}
};
void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atomic<bool>& abortProcessing, int face) {
assert(image.format() == QIMAGE_HDR_FORMAT);
void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomic<bool>& 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<glm::vec4> data;
std::vector<glm::vec4>::iterator dataIt;
auto mipFormat = texture->getStoredMipFormat();
@ -479,10 +497,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<const uint32*>( image.constScanLine(lineNb) );
const uint32* srcPixelIt = reinterpret_cast<const uint32*>(localCopy.constScanLine(lineNb));
const uint32* srcPixelEnd = srcPixelIt + width;
while (srcPixelIt < srcPixelEnd) {
@ -493,6 +511,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<nvtt::OutputHandler> outputHandler;
@ -505,7 +526,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());
@ -526,13 +547,17 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom
}
}
void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic<bool>& 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<bool>& 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<const void*>(image.constBits());
const int width = localCopy.width(), height = localCopy.height();
const void* data = static_cast<const void*>(localCopy.constBits());
nvtt::TextureType textureType = nvtt::TextureType_2D;
nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB;
@ -545,7 +570,11 @@ void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic<boo
nvtt::InputOptions inputOptions;
inputOptions.setTextureLayout(textureType, width, height);
inputOptions.setMipmapData(data, width, height);
// setMipmapData copies the memory, so free up the memory afterward to avoid bloating the heap
data = nullptr;
localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one.
inputOptions.setFormat(inputFormat);
inputOptions.setGamma(inputGamma, outputGamma);
@ -649,14 +678,14 @@ void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic<boo
void generateMips(gpu::Texture* texture, QImage& image, const std::atomic<bool>& abortProcessing = false, int face = -1) {
void generateMips(gpu::Texture* texture, QImage&& image, const std::atomic<bool>& 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);
@ -690,10 +719,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<bool>& 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;
@ -739,7 +769,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;
@ -757,16 +787,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);
@ -779,14 +813,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
@ -815,13 +849,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<bool>& 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
@ -841,17 +875,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<bool>& 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);
@ -877,7 +911,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;
@ -947,7 +981,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;
@ -1150,8 +1184,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<uint32(const glm::vec3&)> packFunc;
#ifdef DEBUG_COLOR_PACKING
std::function<glm::vec3(uint32)> unpackFunc;
@ -1173,13 +1211,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<const QRgb*>( 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<const QRgb*>( localCopy.constScanLine(y) );
const QRgb* srcLineEnd = srcLineIt + localCopy.width();
uint32* hdrLineIt = reinterpret_cast<uint32*>( hdrImage.scanLine(y) );
glm::vec3 color;
@ -1204,86 +1242,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<bool>& 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<QImage> 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<QImage> 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);
}
}

View file

@ -41,41 +41,41 @@ enum Type {
UNUSED_TEXTURE
};
using TextureLoader = std::function<gpu::TexturePointer(const QImage&, const std::string&, const std::atomic<bool>&)>;
using TextureLoader = std::function<gpu::TexturePointer(QImage&&, const std::string&, const std::atomic<bool>&)>;
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<bool>& abortProcessing);
gpu::TexturePointer createStrict2DTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createAlbedoTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createEmissiveTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createNormalTextureFromNormalImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createNormalTextureFromBumpImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createRoughnessTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createRoughnessTextureFromGlossImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createMetallicTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createCubeTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& abortProcessing);
gpu::TexturePointer createLightmapTextureFromImage(const QImage& image, const std::string& srcImageName,
gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName,
const std::atomic<bool>& 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<bool>& 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<bool>& 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<bool>& 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<bool>& abortProcessing);
} // namespace TextureUsage
@ -92,7 +92,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<bool>& abortProcessing = false);

View file

@ -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<Resource> TextureCache::createResource(const QUrl& url, const QSharedPointer<Resource>& 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;