From b8065d46ce5f8a65968568dc1a2302954747da91 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 13 Sep 2017 19:15:51 +0200 Subject: [PATCH] Fixed bugs with conversion to packed floats --- libraries/gpu/src/gpu/Texture.cpp | 1 - libraries/image/src/image/Image.cpp | 171 +++++++++++++++++++++------- 2 files changed, 128 insertions(+), 44 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index d817d39883..4c2f4960c7 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -698,7 +698,6 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< assert(false); break; } - assert(mipFormat.getSemantic() == gpu::R11G11B10); const uint sqOrder = order*order; diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index f51319fadf..8ada88f008 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -205,6 +205,20 @@ void setCubeTexturesCompressionEnabled(bool enabled) { compressCubeTextures.set(enabled); } +static float denormalize(float value, const float minValue) { + return value < minValue ? 0.f : value; +} + +uint32 packR11G11B10F(const glm::vec3& color) { + // Denormalize else unpacking gives high and incorrect values + // See https://www.khronos.org/opengl/wiki/Small_Float_Formats for this min value + static const auto minValue = 6.10e-5f; + glm::vec3 ucolor; + ucolor.r = denormalize(color.r, minValue); + ucolor.g = denormalize(color.g, minValue); + ucolor.b = denormalize(color.b, minValue); + return glm::packF2x11_1x10(ucolor); +} gpu::TexturePointer processImage(const QByteArray& content, const std::string& filename, int maxNumPixels, TextureUsage::Type textureType) { // Help the QImage loader by extracting the image file format from the url filename ext. @@ -291,8 +305,8 @@ QImage processSourceImage(const QImage& srcImage, bool cubemap) { return srcImage; } -struct MyOutputHandler : public nvtt::OutputHandler { - MyOutputHandler(gpu::Texture* texture, int face) : _texture(texture), _face(face) {} +struct OutputHandler : public nvtt::OutputHandler { + OutputHandler(gpu::Texture* texture, int face) : _texture(texture), _face(face) {} virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { _size = size; @@ -310,7 +324,8 @@ struct MyOutputHandler : public nvtt::OutputHandler { virtual void endImage() override { if (_face >= 0) { _texture->assignStoredMipFace(_miplevel, _face, _size, static_cast(_data)); - } else { + } + else { _texture->assignStoredMip(_miplevel, _size, static_cast(_data)); } free(_data); @@ -324,6 +339,51 @@ struct MyOutputHandler : public nvtt::OutputHandler { int _size = 0; int _face = -1; }; + +struct PackedFloatOutputHandler : public OutputHandler { + PackedFloatOutputHandler(gpu::Texture* texture, int face, gpu::Element format) : OutputHandler(texture, face) { + if (format == gpu::Element::COLOR_RGB9E5) { + _packFunc = glm::packF3x9_E1x5; + } else if (format == gpu::Element::COLOR_R11G11B10) { + _packFunc = packR11G11B10F; + } else { + qCWarning(imagelogging) << "Unknown handler format"; + Q_UNREACHABLE(); + } + } + + virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { + // Divide by 3 because we will compress from 3*floats to 1 uint32 + OutputHandler::beginImage(size / 3, width, height, depth, face, miplevel); + } + virtual bool writeData(const void* data, int size) override { + // Expecting to write multiple of floats + if (_packFunc) { + assert((size % sizeof(float)) == 0); + auto floatCount = size / sizeof(float); + const float* floatBegin = (const float*)data; + const float* floatEnd = floatBegin + floatCount; + + while (floatBegin < floatEnd) { + _pixel[_coordIndex] = *floatBegin; + floatBegin++; + _coordIndex++; + if (_coordIndex == 3) { + uint32 packedRGB = _packFunc(_pixel); + _coordIndex = 0; + OutputHandler::writeData(&packedRGB, sizeof(packedRGB)); + } + } + return true; + } + return false; + } + + std::function _packFunc; + glm::vec3 _pixel; + int _coordIndex{ 0 }; +}; + struct MyErrorHandler : public nvtt::ErrorHandler { virtual void error(nvtt::Error e) override { qCWarning(imagelogging) << "Texture compression error:" << nvtt::errorString(e); @@ -336,20 +396,8 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, int face) { const int width = image.width(), height = image.height(); std::vector data; std::vector::iterator dataIt; - - data.resize(width*height); - dataIt = data.begin(); - for (auto lineNb = 0; lineNb < height; lineNb++) { - const uint32* srcPixelIt = (const uint32*) image.constScanLine(lineNb); - const uint32* srcPixelEnd = srcPixelIt + width; - - while (srcPixelIt < srcPixelEnd) { - *dataIt = glm::vec4(glm::unpackF2x11_1x10(*srcPixelIt), 1.f); - ++srcPixelIt; - ++dataIt; - } - } - assert(dataIt == data.end()); + auto mipFormat = texture->getStoredMipFormat(); + std::function unpackFunc; nvtt::TextureType textureType = nvtt::TextureType_2D; nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F; @@ -360,38 +408,55 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, int face) { nvtt::CompressionOptions compressionOptions; compressionOptions.setQuality(nvtt::Quality_Production); - auto mipFormat = texture->getStoredMipFormat(); if (mipFormat == gpu::Element::COLOR_COMPRESSED_HDR_RGB) { compressionOptions.setFormat(nvtt::Format_BC6); - } - else if (mipFormat == gpu::Element::COLOR_RGB9E5) { - compressionOptions.setFormat(nvtt::Format_RGB); - compressionOptions.setPixelType(nvtt::PixelType_SharedExp); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(9, 9, 9, 5); - } - else if (mipFormat == gpu::Element::COLOR_R11G11B10) { - // WARNING : With NVTT 2.1, using float 11/10 produces an assertion in the compressor + } else if (mipFormat == gpu::Element::COLOR_RGB9E5) { compressionOptions.setFormat(nvtt::Format_RGB); compressionOptions.setPixelType(nvtt::PixelType_Float); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(11, 11, 10, 0); - } - else { + compressionOptions.setPixelFormat(32, 32, 32, 0); + unpackFunc = glm::unpackF3x9_E1x5; + } else if (mipFormat == gpu::Element::COLOR_R11G11B10) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_Float); + compressionOptions.setPixelFormat(32, 32, 32, 0); + unpackFunc = glm::unpackF2x11_1x10; + } else { qCWarning(imagelogging) << "Unknown mip format"; Q_UNREACHABLE(); return; } + data.resize(width*height); + dataIt = data.begin(); + for (auto lineNb = 0; lineNb < height; lineNb++) { + const uint32* srcPixelIt = (const uint32*)image.constScanLine(lineNb); + const uint32* srcPixelEnd = srcPixelIt + width; + + while (srcPixelIt < srcPixelEnd) { + *dataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.f); + ++srcPixelIt; + ++dataIt; + } + } + assert(dataIt == data.end()); + nvtt::OutputOptions outputOptions; outputOptions.setOutputHeader(false); - MyOutputHandler outputHandler(texture, face); - outputOptions.setOutputHandler(&outputHandler); + std::auto_ptr outputHandler; MyErrorHandler errorHandler; outputOptions.setErrorHandler(&errorHandler); nvtt::Context context; int mipLevel = 0; + if (mipFormat == gpu::Element::COLOR_RGB9E5 || mipFormat == gpu::Element::COLOR_R11G11B10) { + // 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) ); + } + + outputOptions.setOutputHandler(outputHandler.get()); + nvtt::Surface surface; surface.setImage(inputFormat, width, height, 1, &(*data.begin())); surface.setAlphaMode(alphaMode); @@ -512,7 +577,7 @@ void generateLDRMips(gpu::Texture* texture, QImage& image, int face) { nvtt::OutputOptions outputOptions; outputOptions.setOutputHeader(false); - MyOutputHandler outputHandler(texture, face); + OutputHandler outputHandler(texture, face); outputOptions.setOutputHandler(&outputHandler); MyErrorHandler errorHandler; outputOptions.setErrorHandler(&errorHandler); @@ -1007,8 +1072,21 @@ const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = { }; const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout); -QImage convertToHDRFormat(QImage srcImage) { +QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { QImage hdrImage(srcImage.width(), srcImage.height(), (QImage::Format)QIMAGE_HDR_FORMAT); + std::function packFunc; + switch (format.getSemantic()) { + case gpu::R11G11B10: + packFunc = packR11G11B10F; + break; + case gpu::RGB9E5: + packFunc = glm::packF3x9_E1x5; + break; + default: + qCWarning(imagelogging) << "Unsupported HDR format"; + Q_UNREACHABLE(); + return srcImage; + } srcImage = srcImage.convertToFormat(QImage::Format_ARGB32); for (auto y = 0; y < srcImage.height(); y++) { @@ -1021,7 +1099,12 @@ QImage convertToHDRFormat(QImage srcImage) { color.r = qRed(*srcLineIt); color.g = qGreen(*srcLineIt); color.b = qBlue(*srcLineIt); - *hdrLineIt = glm::packF2x11_1x10(color / 255.f); + // Normalize and apply gamma + color /= 255.f; + color.r = powf(color.r, 2.2f); + color.g = powf(color.g, 2.2f); + color.b = powf(color.b, 2.2f); + *hdrLineIt = packFunc(color); ++srcLineIt; ++hdrLineIt; } @@ -1035,18 +1118,20 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& 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); - } + const auto cubeMapHDRFormat = gpu::Element::COLOR_RGB9E5; gpu::Element formatMip; gpu::Element formatGPU; if (isCubeTexturesCompressionEnabled()) { formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; } else { - formatMip = gpu::Element::COLOR_R11G11B10; - formatGPU = gpu::Element::COLOR_R11G11B10; + formatMip = cubeMapHDRFormat; + formatGPU = cubeMapHDRFormat; + } + + if (image.format() != QIMAGE_HDR_FORMAT) { + image = convertToHDRFormat(image, cubeMapHDRFormat); } // Find the layout of the cubemap in the 2D image @@ -1094,9 +1179,9 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& // Generate irradiance while we are at it if (generateIrradiance) { PROFILE_RANGE(resource_parse, "generateIrradiance"); - auto irradianceTexture = gpu::Texture::createCube(gpu::Element::COLOR_R11G11B10, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + auto irradianceTexture = gpu::Texture::createCube(cubeMapHDRFormat, 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(gpu::Element::COLOR_R11G11B10); + irradianceTexture->setStoredMipFormat(cubeMapHDRFormat); for (uint8 face = 0; face < faces.size(); ++face) { irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); }