From aa4401801e481991954246130280c980ede44cb1 Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Wed, 27 Mar 2019 11:54:45 +0100
Subject: [PATCH 01/23] Added new texture types : sky and ambient in place of
 just cube

---
 libraries/baking/src/TextureBaker.cpp          |  2 +-
 .../src/RenderableZoneEntityItem.cpp           |  4 ++--
 libraries/image/src/image/Image.cpp            |  4 +++-
 libraries/image/src/image/Image.h              |  3 ++-
 .../src/material-networking/TextureCache.cpp   | 11 ++++++++---
 .../src/DeferredLightingEffect.cpp             | 18 ++++++++++++++++--
 tools/oven/src/BakerCLI.cpp                    |  5 +++--
 tools/oven/src/DomainBaker.cpp                 |  4 ++--
 tools/oven/src/ui/SkyboxBakeWidget.cpp         |  2 +-
 9 files changed, 38 insertions(+), 15 deletions(-)

diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp
index d097b4765b..c37e61cb4b 100644
--- a/libraries/baking/src/TextureBaker.cpp
+++ b/libraries/baking/src/TextureBaker.cpp
@@ -206,7 +206,7 @@ void TextureBaker::processTexture() {
     }
 
     // Uncompressed KTX
-    if (_textureType == image::TextureUsage::Type::CUBE_TEXTURE) {
+    if (_textureType == image::TextureUsage::Type::SKY_TEXTURE || _textureType == image::TextureUsage::Type::AMBIENT_TEXTURE) {
         buffer->reset();
         auto processedTexture = image::processImage(std::move(buffer), _textureURL.toString().toStdString(), image::ColorChannel::NONE,
                                                     ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, gpu::BackendTarget::GL45, _abortProcessing);
diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp
index 631148c27a..967ede0709 100644
--- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp
@@ -465,7 +465,7 @@ void ZoneEntityRenderer::setAmbientURL(const QString& ambientUrl) {
     } else {
         _pendingAmbientTexture = true;
         auto textureCache = DependencyManager::get<TextureCache>();
-        _ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::CUBE_TEXTURE);
+        _ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::AMBIENT_TEXTURE);
 
         // keep whatever is assigned on the ambient map/sphere until texture is loaded
     }
@@ -506,7 +506,7 @@ void ZoneEntityRenderer::setSkyboxURL(const QString& skyboxUrl) {
     } else {
         _pendingSkyboxTexture = true;
         auto textureCache = DependencyManager::get<TextureCache>();
-        _skyboxTexture = textureCache->getTexture(_skyboxTextureURL, image::TextureUsage::CUBE_TEXTURE);
+        _skyboxTexture = textureCache->getTexture(_skyboxTextureURL, image::TextureUsage::SKY_TEXTURE);
     }
 }
 
diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp
index 4154a46c8d..88ca440908 100644
--- a/libraries/image/src/image/Image.cpp
+++ b/libraries/image/src/image/Image.cpp
@@ -100,7 +100,9 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con
             return image::TextureUsage::createEmissiveTextureFromImage;
         case LIGHTMAP_TEXTURE:
             return image::TextureUsage::createLightmapTextureFromImage;
-        case CUBE_TEXTURE:
+        case SKY_TEXTURE:
+            return image::TextureUsage::createCubeTextureFromImageWithoutIrradiance;
+        case AMBIENT_TEXTURE:
             if (options.value("generateIrradiance", true).toBool()) {
                 return image::TextureUsage::createCubeTextureFromImage;
             } else {
diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h
index a64a9e4571..b816edac39 100644
--- a/libraries/image/src/image/Image.h
+++ b/libraries/image/src/image/Image.h
@@ -41,7 +41,8 @@ enum Type {
     ROUGHNESS_TEXTURE,
     GLOSS_TEXTURE,
     EMISSIVE_TEXTURE,
-    CUBE_TEXTURE,
+    SKY_TEXTURE,
+    AMBIENT_TEXTURE,
     OCCLUSION_TEXTURE,
     SCATTERING_TEXTURE = OCCLUSION_TEXTURE,
     LIGHTMAP_TEXTURE,
diff --git a/libraries/material-networking/src/material-networking/TextureCache.cpp b/libraries/material-networking/src/material-networking/TextureCache.cpp
index 43f467266a..b15020de42 100644
--- a/libraries/material-networking/src/material-networking/TextureCache.cpp
+++ b/libraries/material-networking/src/material-networking/TextureCache.cpp
@@ -224,10 +224,14 @@ NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUs
         return getResourceTexture(url);
     }
     auto modifiedUrl = url;
-    if (type == image::TextureUsage::CUBE_TEXTURE) {
+    if (type == image::TextureUsage::SKY_TEXTURE) {
         QUrlQuery query { url.query() };
         query.addQueryItem("skybox", "");
         modifiedUrl.setQuery(query.toString());
+    } else if (type == image::TextureUsage::AMBIENT_TEXTURE) {
+        QUrlQuery query{ url.query() };
+        query.addQueryItem("ambient", "");
+        modifiedUrl.setQuery(query.toString());
     }
     TextureExtra extra = { type, content, maxNumPixels, sourceChannel };
     return ResourceCache::getResource(modifiedUrl, QUrl(), &extra, std::hash<TextureExtra>()(extra)).staticCast<NetworkTexture>();
@@ -283,7 +287,8 @@ gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) {
         case image::TextureUsage::BUMP_TEXTURE:
         case image::TextureUsage::SPECULAR_TEXTURE:
         case image::TextureUsage::GLOSS_TEXTURE:
-        case image::TextureUsage::CUBE_TEXTURE:
+        case image::TextureUsage::SKY_TEXTURE:
+        case image::TextureUsage::AMBIENT_TEXTURE:
         case image::TextureUsage::STRICT_TEXTURE:
         default:
             break;
@@ -408,7 +413,7 @@ void NetworkTexture::setExtra(void* extra) {
 
     _shouldFailOnRedirect = _currentlyLoadingResourceType != ResourceType::KTX;
 
-    if (_type == image::TextureUsage::CUBE_TEXTURE) {
+    if (_type == image::TextureUsage::SKY_TEXTURE) {
         setLoadPriority(this, SKYBOX_LOAD_PRIORITY);
     } else if (_currentlyLoadingResourceType == ResourceType::KTX) {
         setLoadPriority(this, HIGH_MIPS_LOAD_PRIORITY);
diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp
index ab9dea2325..b936060741 100644
--- a/libraries/render-utils/src/DeferredLightingEffect.cpp
+++ b/libraries/render-utils/src/DeferredLightingEffect.cpp
@@ -647,20 +647,34 @@ void RenderDeferred::run(const RenderContextPointer& renderContext, const Inputs
 void DefaultLightingSetup::run(const RenderContextPointer& renderContext) {
 
     if (!_defaultLight || !_defaultBackground) {
+        auto defaultSkyboxURL = PathUtils::resourcesUrl() + "images/Default-Sky-9-cubemap/Default-Sky-9-cubemap.texmeta.json";
+
         if (!_defaultSkyboxNetworkTexture) {
             PROFILE_RANGE(render, "Process Default Skybox");
             _defaultSkyboxNetworkTexture = DependencyManager::get<TextureCache>()->getTexture(
-                PathUtils::resourcesUrl() + "images/Default-Sky-9-cubemap/Default-Sky-9-cubemap.texmeta.json", image::TextureUsage::CUBE_TEXTURE);
+                defaultSkyboxURL, image::TextureUsage::SKY_TEXTURE);
+        }
+
+        if (!_defaultSkyboxAmbientTexture) {
+            PROFILE_RANGE(render, "Process Default Ambient map");
+            _defaultSkyboxAmbientTexture = DependencyManager::get<TextureCache>()->getTexture(
+                defaultSkyboxURL, image::TextureUsage::AMBIENT_TEXTURE);
         }
 
         if (_defaultSkyboxNetworkTexture && _defaultSkyboxNetworkTexture->isLoaded() && _defaultSkyboxNetworkTexture->getGPUTexture()) {
-            _defaultSkyboxAmbientTexture = _defaultSkyboxNetworkTexture->getGPUTexture();
             _defaultSkybox->setCubemap(_defaultSkyboxAmbientTexture);
         } else {
             // Don't do anything until the skybox has loaded
             return;
         }
 
+        if (_defaultSkyboxAmbientTexture && _defaultSkyboxAmbientTexture->isLoaded() && _defaultSkyboxAmbientTexture->getGPUTexture()) {
+            _defaultSkyboxAmbientTexture = _defaultSkyboxAmbientTexture->getGPUTexture();
+        } else {
+            // Don't do anything until the ambient box has been loaded
+            return;
+        }
+
         auto lightStage = renderContext->_scene->getStage<LightStage>();
         if (lightStage) { 
 
diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp
index 2946db650c..ba2703a895 100644
--- a/tools/oven/src/BakerCLI.cpp
+++ b/tools/oven/src/BakerCLI.cpp
@@ -82,8 +82,9 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString&
                 { "roughness", image::TextureUsage::ROUGHNESS_TEXTURE },
                 { "gloss", image::TextureUsage::GLOSS_TEXTURE },
                 { "emissive", image::TextureUsage::EMISSIVE_TEXTURE },
-                { "cube", image::TextureUsage::CUBE_TEXTURE },
-                { "skybox", image::TextureUsage::CUBE_TEXTURE },
+                { "cube", image::TextureUsage::SKY_TEXTURE },
+                { "skybox", image::TextureUsage::SKY_TEXTURE },
+                { "ambient", image::TextureUsage::AMBIENT_TEXTURE },
                 { "occlusion", image::TextureUsage::OCCLUSION_TEXTURE },
                 { "scattering", image::TextureUsage::SCATTERING_TEXTURE },
                 { "lightmap", image::TextureUsage::LIGHTMAP_TEXTURE },
diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp
index 05745aad24..f7bb214a33 100644
--- a/tools/oven/src/DomainBaker.cpp
+++ b/tools/oven/src/DomainBaker.cpp
@@ -390,13 +390,13 @@ void DomainBaker::enumerateEntities() {
             if (entity.contains(AMBIENT_LIGHT_KEY)) {
                 auto ambientLight = entity[AMBIENT_LIGHT_KEY].toObject();
                 if (ambientLight.contains(AMBIENT_URL_KEY)) {
-                    addTextureBaker(AMBIENT_LIGHT_KEY + "." + AMBIENT_URL_KEY, ambientLight[AMBIENT_URL_KEY].toString(), image::TextureUsage::CUBE_TEXTURE, *it);
+                    addTextureBaker(AMBIENT_LIGHT_KEY + "." + AMBIENT_URL_KEY, ambientLight[AMBIENT_URL_KEY].toString(), image::TextureUsage::AMBIENT_TEXTURE, *it);
                 }
             }
             if (entity.contains(SKYBOX_KEY)) {
                 auto skybox = entity[SKYBOX_KEY].toObject();
                 if (skybox.contains(SKYBOX_URL_KEY)) {
-                    addTextureBaker(SKYBOX_KEY + "." + SKYBOX_URL_KEY, skybox[SKYBOX_URL_KEY].toString(), image::TextureUsage::CUBE_TEXTURE, *it);
+                    addTextureBaker(SKYBOX_KEY + "." + SKYBOX_URL_KEY, skybox[SKYBOX_URL_KEY].toString(), image::TextureUsage::SKY_TEXTURE, *it);
                 }
             }
 
diff --git a/tools/oven/src/ui/SkyboxBakeWidget.cpp b/tools/oven/src/ui/SkyboxBakeWidget.cpp
index 71ae0cbab0..113346c5e7 100644
--- a/tools/oven/src/ui/SkyboxBakeWidget.cpp
+++ b/tools/oven/src/ui/SkyboxBakeWidget.cpp
@@ -181,7 +181,7 @@ void SkyboxBakeWidget::bakeButtonClicked() {
 
         // everything seems to be in place, kick off a bake for this skybox now
         auto baker = std::unique_ptr<TextureBaker> {
-            new TextureBaker(skyboxToBakeURL, image::TextureUsage::CUBE_TEXTURE, outputDirectory.absolutePath())
+            new TextureBaker(skyboxToBakeURL, image::TextureUsage::SKY_TEXTURE, outputDirectory.absolutePath())
         };
 
         // move the baker to a worker thread

From a39fe7452ce7f828c9925b5358fe4de875cc7756 Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Wed, 27 Mar 2019 16:31:08 +0100
Subject: [PATCH 02/23] Preparing for cubemap convolution

---
 libraries/image/src/image/CubeMap.cpp |  28 ++++
 libraries/image/src/image/CubeMap.h   |  43 ++++++
 libraries/image/src/image/Image.cpp   | 190 ++++++++++++++++++++------
 libraries/image/src/image/Image.h     |  16 ++-
 4 files changed, 231 insertions(+), 46 deletions(-)
 create mode 100644 libraries/image/src/image/CubeMap.cpp
 create mode 100644 libraries/image/src/image/CubeMap.h

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
new file mode 100644
index 0000000000..303cb98fe7
--- /dev/null
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -0,0 +1,28 @@
+//
+//  CubeMap.h
+//  image/src/image
+//
+//  Created by Olivier Prat on 03/27/2019.
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#include "CubeMap.h"
+
+using namespace image;
+
+CubeMap::CubeMap(int width, int height, int mipCount) :
+    _width(width), _height(height) {
+    assert(mipCount >0 && _width > 0 && _height > 0);
+    _mips.resize(mipCount);
+    for (auto mipLevel = 0; mipLevel < mipCount; mipLevel++) {
+        auto mipWidth = std::max(1, width >> mipLevel);
+        auto mipHeight = std::max(1, height >> mipLevel);
+        auto mipPixelCount = mipWidth * mipHeight;
+
+        for (auto& face : _mips[mipLevel]) {
+            face.resize(mipPixelCount);
+        }
+    }
+}
diff --git a/libraries/image/src/image/CubeMap.h b/libraries/image/src/image/CubeMap.h
new file mode 100644
index 0000000000..05a571cafc
--- /dev/null
+++ b/libraries/image/src/image/CubeMap.h
@@ -0,0 +1,43 @@
+//
+//  CubeMap.h
+//  image/src/image
+//
+//  Created by Olivier Prat on 03/27/2019.
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_image_CubeMap_h
+#define hifi_image_CubeMap_h
+
+#include <gpu/Forward.h>
+#include <glm/vec4.hpp>
+#include <vector>
+#include <array>
+
+namespace image {
+
+    class CubeMap {
+    public:
+        
+        using Face = std::vector<glm::vec4>;
+        using Faces = std::array<Face, 6>;
+
+        CubeMap(int width, int height, int mipCount);
+
+        gpu::uint16 getMipCount() const { return (gpu::uint16)_mips.size(); }
+        Faces& editMip(gpu::uint16 mipLevel) { return _mips[mipLevel]; }
+        const Faces& getMip(gpu::uint16 mipLevel) const { return _mips[mipLevel]; }
+
+    private:
+
+        int _width;
+        int _height;
+        std::vector<Faces> _mips;
+    };
+
+}
+
+#endif // hifi_image_CubeMap_h
diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp
index 88ca440908..6e7e08ea89 100644
--- a/libraries/image/src/image/Image.cpp
+++ b/libraries/image/src/image/Image.cpp
@@ -30,6 +30,7 @@
 #include "OpenEXRReader.h"
 #endif
 #include "ImageLogging.h"
+#include "CubeMap.h"
 
 using namespace gpu;
 
@@ -101,12 +102,12 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con
         case LIGHTMAP_TEXTURE:
             return image::TextureUsage::createLightmapTextureFromImage;
         case SKY_TEXTURE:
-            return image::TextureUsage::createCubeTextureFromImageWithoutIrradiance;
+            return image::TextureUsage::createCubeTextureFromImage;
         case AMBIENT_TEXTURE:
             if (options.value("generateIrradiance", true).toBool()) {
-                return image::TextureUsage::createCubeTextureFromImage;
+                return image::TextureUsage::createCubeTextureAndIrradianceFromImage;
             } else {
-                return image::TextureUsage::createCubeTextureFromImageWithoutIrradiance;
+                return image::TextureUsage::createCubeTextureFromImage;
             }
         case BUMP_TEXTURE:
             return image::TextureUsage::createNormalTextureFromBumpImage;
@@ -177,14 +178,24 @@ gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(QImage&& srcIma
     return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing);
 }
 
-gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
+gpu::TexturePointer TextureUsage::createCubeTextureAndIrradianceFromImage(QImage&& srcImage, const std::string& srcImageName,
                                                              bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing);
+    return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, CUBE_GENERATE_IRRADIANCE, abortProcessing);
 }
 
-gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(QImage&& srcImage, const std::string& srcImageName,
+gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
                                                                               bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing);
+    return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, CUBE_DEFAULT, abortProcessing);
+}
+
+gpu::TexturePointer TextureUsage::createGGXConvolvedCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
+                                                           bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing) {
+    return processCubeTextureColorFromImage(std::move(image), srcImageName, compress, target, CUBE_GGX_CONVOLVE, abortProcessing);
+}
+
+gpu::TexturePointer TextureUsage::createGGXConvolvedCubeTextureAndIrradianceFromImage(QImage&& image, const std::string& srcImageName,
+                                                                        bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing) {
+    return processCubeTextureColorFromImage(std::move(image), srcImageName, compress, target, CUBE_GENERATE_IRRADIANCE | CUBE_GGX_CONVOLVE, abortProcessing);
 }
 
 static float denormalize(float value, const float minValue) {
@@ -206,11 +217,17 @@ static uint32 packR11G11B10F(const glm::vec3& color) {
     return glm::packF2x11_1x10(ucolor);
 }
 
-static std::function<uint32(const glm::vec3&)> getHDRPackingFunction(const gpu::Element& format) {
+static uint32 packUnorm4x8(const glm::vec3& color) {
+    return glm::packUnorm4x8(glm::vec4(color, 1.0f));
+}
+
+static std::function<uint32(const glm::vec3&)> getPackingFunction(const gpu::Element& format) {
     if (format == gpu::Element::COLOR_RGB9E5) {
         return glm::packF3x9_E1x5;
     } else if (format == gpu::Element::COLOR_R11G11B10) {
         return packR11G11B10F;
+    } else if (format == gpu::Element::COLOR_RGBA_32 || format == gpu::Element::COLOR_SRGBA_32 || format == gpu::Element::COLOR_BGRA_32 || format == gpu::Element::COLOR_SBGRA_32) {
+        return packUnorm4x8;
     } else {
         qCWarning(imagelogging) << "Unknown handler format";
         Q_UNREACHABLE();
@@ -219,21 +236,27 @@ static std::function<uint32(const glm::vec3&)> getHDRPackingFunction(const gpu::
 }
 
 std::function<uint32(const glm::vec3&)> getHDRPackingFunction() {
-    return getHDRPackingFunction(HDR_FORMAT);
+    return getPackingFunction(HDR_FORMAT);
 }
 
-std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction() {
-    if (HDR_FORMAT == gpu::Element::COLOR_RGB9E5) {
+std::function<glm::vec3(gpu::uint32)> getUnpackingFunction(const gpu::Element& format) {
+    if (format == gpu::Element::COLOR_RGB9E5) {
         return glm::unpackF3x9_E1x5;
-    } else if (HDR_FORMAT == gpu::Element::COLOR_R11G11B10) {
+    } else if (format == gpu::Element::COLOR_R11G11B10) {
         return glm::unpackF2x11_1x10;
+    } else if (format == gpu::Element::COLOR_RGBA_32 || format == gpu::Element::COLOR_SRGBA_32 || format == gpu::Element::COLOR_BGRA_32 || format == gpu::Element::COLOR_SBGRA_32) {
+        return glm::unpackUnorm4x8;
     } else {
-        qCWarning(imagelogging) << "Unknown HDR encoding format in QImage";
+        qCWarning(imagelogging) << "Unknown handler format";
         Q_UNREACHABLE();
         return nullptr;
     }
 }
 
+std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction() {
+    return getUnpackingFunction(HDR_FORMAT);
+}
+
 QImage processRawImageData(QIODevice& content, const std::string& filename) {
     // Help the QImage loader by extracting the image file format from the url filename ext.
     // Some tga are not created properly without it.
@@ -440,7 +463,7 @@ struct OutputHandler : public nvtt::OutputHandler {
 
 struct PackedFloatOutputHandler : public OutputHandler {
     PackedFloatOutputHandler(gpu::Texture* texture, int face, gpu::Element format) : OutputHandler(texture, face) {
-        _packFunc = getHDRPackingFunction(format);
+        _packFunc = getPackingFunction(format);
     }
 
     virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override {
@@ -498,6 +521,43 @@ public:
     }
 };
 
+void convertToFloat(const unsigned char* source, int width, int height, int lineStride, gpu::Element sourceFormat, std::vector<glm::vec4>& output) {
+    std::vector<glm::vec4>::iterator outputIt;
+    auto unpackFunc = getUnpackingFunction(sourceFormat);
+
+    output.resize(width * height);
+    outputIt = output.begin();
+    for (auto lineNb = 0; lineNb < height; lineNb++) {
+        const uint32* srcPixelIt = reinterpret_cast<const uint32*>(source + lineNb * lineStride);
+        const uint32* srcPixelEnd = srcPixelIt + width;
+
+        while (srcPixelIt < srcPixelEnd) {
+            *outputIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f);
+            ++srcPixelIt;
+            ++outputIt;
+        }
+    }
+    assert(outputIt == output.end());
+}
+
+void convertFromFloat(unsigned char* output, int width, int height, int lineStride, gpu::Element outputFormat, const std::vector<glm::vec4>& source) {
+    std::vector<glm::vec4>::const_iterator sourceIt;
+    auto packFunc = getPackingFunction(outputFormat);
+
+    sourceIt = source.begin();
+    for (auto lineNb = 0; lineNb < height; lineNb++) {
+        uint32* outPixelIt = reinterpret_cast<uint32*>(output + lineNb * lineStride);
+        uint32* outPixelEnd = outPixelIt + width;
+
+        while (outPixelIt < outPixelEnd) {
+            *outPixelIt = packFunc(*sourceIt);
+            ++outPixelIt;
+            ++sourceIt;
+        }
+    }
+    assert(sourceIt == source.end());
+}
+
 void generateHDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target, 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
@@ -509,7 +569,6 @@ void generateHDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target
     std::vector<glm::vec4> data;
     std::vector<glm::vec4>::iterator dataIt;
     auto mipFormat = texture->getStoredMipFormat();
-    std::function<glm::vec3(uint32)> unpackFunc = getHDRUnpackingFunction();
 
     nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F;
     nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
@@ -535,19 +594,7 @@ void generateHDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target
         return;
     }
 
-    data.resize(width * height);
-    dataIt = data.begin();
-    for (auto lineNb = 0; lineNb < height; lineNb++) {
-        const uint32* srcPixelIt = reinterpret_cast<const uint32*>(localCopy.constScanLine(lineNb));
-        const uint32* srcPixelEnd = srcPixelIt + width;
-
-        while (srcPixelIt < srcPixelEnd) {
-            *dataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f);
-            ++srcPixelIt;
-            ++dataIt;
-        }
-    }
-    assert(dataIt == data.end());
+    convertToFloat(localCopy.bits(), width, height, localCopy.bytesPerLine(), HDR_FORMAT, data);
 
     // 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.
@@ -785,22 +832,74 @@ void generateLDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target
 
 #endif
 
-void generateMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic<bool>& abortProcessing = false, int face = -1) {
-#if CPU_MIPMAPS
-    PROFILE_RANGE(resource_parse, "generateMips");
+void generateMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic<bool>& abortProcessing = false, int face = -1, bool forceCPUBuild = false) {
+    if (forceCPUBuild || CPU_MIPMAPS) {
+        PROFILE_RANGE(resource_parse, "generateMips");
 
-    if (target == BackendTarget::GLES32) {
-        generateLDRMips(texture, std::move(image), target, abortProcessing, face);
-    } else {
-        if (image.format() == QIMAGE_HDRFORMAT) {
-            generateHDRMips(texture, std::move(image), target, abortProcessing, face);
-        } else {
+        if (target == BackendTarget::GLES32) {
             generateLDRMips(texture, std::move(image), target, abortProcessing, face);
+        } else {
+            if (image.format() == QIMAGE_HDRFORMAT) {
+                generateHDRMips(texture, std::move(image), target, abortProcessing, face);
+            } else {
+                generateLDRMips(texture, std::move(image), target, abortProcessing, face);
+            }
+        }
+    } else {
+        texture->setAutoGenerateMips(true);
+    }
+}
+
+void convolveFaceWithGGX(const CubeMap& source, int face, const std::atomic<bool>& abortProcessing) {
+
+}
+
+void convolveWithGGX(gpu::Texture* texture, BackendTarget target, const std::atomic<bool>& abortProcessing = false) {
+    PROFILE_RANGE(resource_parse, "convolveWithGGX");
+    CubeMap source(texture->getWidth(), texture->getHeight(), texture->getNumMips());
+    gpu::uint16 mipLevel;
+    int face;
+    const auto textureFormat = texture->getTexelFormat();
+
+    // Convert all mip data to float as source
+    for (mipLevel = 0; mipLevel < source.getMipCount(); ++mipLevel) {
+        auto mipDims = texture->evalMipDimensions(mipLevel);
+        auto& mip = source.editMip(mipLevel);
+
+        for (face = 0; face < 6; face++) {
+            auto sourcePixels = texture->accessStoredMipFace(mipLevel, face)->data();
+            convertToFloat(sourcePixels, mipDims.x, mipDims.y, sizeof(uint32)*mipDims.x, textureFormat, mip[face]);
+            if (abortProcessing.load()) {
+                return;
+            }
         }
     }
-#else
-    texture->setAutoGenerateMips(true);
-#endif
+
+    for (face = 0; face < 6; face++) {
+        convolveFaceWithGGX(source, face, abortProcessing);
+    }
+
+    if (!abortProcessing) {
+        // Convert all mip data back from float
+        unsigned char* convertedPixels = new unsigned char[texture->getWidth() * texture->getHeight() * sizeof(uint32)];
+
+        for (mipLevel = 0; mipLevel < source.getMipCount(); ++mipLevel) {
+            auto mipDims = texture->evalMipDimensions(mipLevel);
+            auto mipSize = texture->evalMipFaceSize(mipLevel);
+            auto& mip = source.getMip(mipLevel);
+
+            for (face = 0; face < 6; face++) {
+                convertFromFloat(convertedPixels, mipDims.x, mipDims.y, sizeof(uint32)*mipDims.x, textureFormat, mip[face]);
+                texture->assignStoredMipFace(mipLevel, face, mipSize, convertedPixels);
+                if (abortProcessing.load()) {
+                    delete[] convertedPixels;
+                    return;
+                }
+            }
+        }
+
+        delete[] convertedPixels;
+    }
 }
 
 void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAsMask) {
@@ -1407,7 +1506,7 @@ QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format) {
 }
 
 gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName,
-                                                                   bool compress, BackendTarget target, bool generateIrradiance,
+                                                                   bool compress, BackendTarget target, int options,
                                                                    const std::atomic<bool>& abortProcessing) {
     PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage");
 
@@ -1492,7 +1591,7 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI
         theTexture->setStoredMipFormat(formatMip);
 
         // Generate irradiance while we are at it
-        if (generateIrradiance) {
+        if (options & CUBE_GENERATE_IRRADIANCE) {
             PROFILE_RANGE(resource_parse, "generateIrradiance");
             gpu::Element irradianceFormat;
             // TODO: we could locally compress the irradiance texture on Android, but we don't need to
@@ -1516,7 +1615,12 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI
         }
 
         for (uint8 face = 0; face < faces.size(); ++face) {
-            generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face);
+            // Force building the mip maps right now on CPU if we are convolving for GGX later on
+            generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face, (options & CUBE_GGX_CONVOLVE) == CUBE_GGX_CONVOLVE);
+        }
+
+        if (options & CUBE_GGX_CONVOLVE) {
+            convolveWithGGX(theTexture.get(), target, abortProcessing);
         }
     }
 
diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h
index b816edac39..9c27b0cf3c 100644
--- a/libraries/image/src/image/Image.h
+++ b/libraries/image/src/image/Image.h
@@ -72,8 +72,12 @@ gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::st
                                                    bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
 gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
                                                bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName,
-                                                                bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
+gpu::TexturePointer createCubeTextureAndIrradianceFromImage(QImage&& image, const std::string& srcImageName,
+                                                            bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
+gpu::TexturePointer createGGXConvolvedCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
+                                                           bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
+gpu::TexturePointer createGGXConvolvedCubeTextureAndIrradianceFromImage(QImage&& image, const std::string& srcImageName,
+                                                                        bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
 gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName,
                                                    bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing); 
 gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress,
@@ -82,8 +86,14 @@ gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const
                                                        gpu::BackendTarget target, bool isBumpMap, const std::atomic<bool>& abortProcessing);
 gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress,
                                                        gpu::BackendTarget target, bool isInvertedPixels, const std::atomic<bool>& abortProcessing);
+
+enum CubeTextureOptions {
+    CUBE_DEFAULT = 0x0,
+    CUBE_GENERATE_IRRADIANCE = 0x1,
+    CUBE_GGX_CONVOLVE = 0x2
+};
 gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress,
-                                                     gpu::BackendTarget target, bool generateIrradiance, const std::atomic<bool>& abortProcessing);
+                                                     gpu::BackendTarget target, int option, const std::atomic<bool>& abortProcessing);
 
 } // namespace TextureUsage
 

From 4a2323f3c2323f700be53c8f5ee12b1f99b4f4b0 Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Wed, 27 Mar 2019 17:43:26 +0100
Subject: [PATCH 03/23] Just need to write correct textureLod equivalent on CPU
 cube map

---
 libraries/image/src/image/CubeMap.cpp         | 214 ++++++++++++++++++
 libraries/image/src/image/CubeMap.h           |  10 +
 libraries/image/src/image/Image.cpp           |  19 +-
 .../render-utils/src/AntialiasingEffect.cpp   |  29 +--
 libraries/shared/src/RandomAndNoise.h         |  47 ++++
 5 files changed, 280 insertions(+), 39 deletions(-)
 create mode 100644 libraries/shared/src/RandomAndNoise.h

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index 303cb98fe7..acd8d6fb85 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -10,6 +10,16 @@
 //
 #include "CubeMap.h"
 
+#include <cmath>
+#include <tbb/parallel_for.h>
+#include <tbb/blocked_range2d.h>
+
+#include "RandomAndNoise.h"
+
+#ifndef M_PI
+#define M_PI    3.14159265359
+#endif
+
 using namespace image;
 
 CubeMap::CubeMap(int width, int height, int mipCount) :
@@ -26,3 +36,207 @@ CubeMap::CubeMap(int width, int height, int mipCount) :
         }
     }
 }
+
+glm::vec4 CubeMap::fetchLod(const glm::vec3& dir, float lod) const {
+    // TODO
+    return glm::vec4(0.0f);
+}
+
+static glm::vec3 sampleGGX(const glm::vec2& Xi, const float roughness) {
+    const float a = roughness * roughness;
+
+    float phi = (float)(2.0 * M_PI * Xi.x);
+    float cosTheta = (float)(std::sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)));
+    float sinTheta = (float)(std::sqrt(1.0 - cosTheta * cosTheta));
+
+    // from spherical coordinates to cartesian coordinates
+    glm::vec3 H;
+    H.x = std::cos(phi) * sinTheta;
+    H.y = std::sin(phi) * sinTheta;
+    H.z = cosTheta;
+
+    return H;
+}
+
+static float evaluateGGX(float NdotH, float roughness) {
+    float alpha = roughness * roughness;
+    float alphaSquared = alpha * alpha;
+    float denom = (float)(NdotH * NdotH * (alphaSquared - 1.0) + 1.0);
+    return alphaSquared / (denom * denom);
+}
+
+struct CubeMap::GGXSamples {
+    float invTotalWeight;
+    std::vector<glm::vec4> points;
+};
+
+void CubeMap::generateGGXSamples(GGXSamples& data, float roughness, const int resolution) {
+    glm::vec2 xi;
+    glm::vec3 L;
+    glm::vec3 H;
+    const float saTexel = (float)(4.0 * M_PI / (6.0 * resolution * resolution));
+    const float mipBias = 3.0f;
+    const auto sampleCount = data.points.size();
+    const auto hammersleySequenceLength = data.points.size();
+    int sampleIndex = 0;
+    int hammersleySampleIndex = 0;
+    float NdotL;
+
+    data.invTotalWeight = 0.0f;
+
+    // Do some computation in tangent space
+    while (sampleIndex < sampleCount) {
+        if (hammersleySampleIndex < hammersleySequenceLength) {
+            xi = evaluateHammersley((int)hammersleySampleIndex, (int)hammersleySequenceLength);
+            H = sampleGGX(xi, roughness);
+            L = H * (2.0f * H.z) - glm::vec3(0.0f, 0.0f, 1.0f);
+            NdotL = L.z;
+            hammersleySampleIndex++;
+        } else {
+            NdotL = -1.0f;
+        }
+
+        while (NdotL <= 0.0f) {
+            // Create a purely random sample
+            xi.x = rand() / float(RAND_MAX);
+            xi.y = rand() / float(RAND_MAX);
+            H = sampleGGX(xi, roughness);
+            L = H * (2.0f * H.z) - glm::vec3(0.0f, 0.0f, 1.0f);
+            NdotL = L.z;
+        }
+
+        float NdotH = std::max(0.0f, H.z);
+        float HdotV = NdotH;
+        float D = evaluateGGX(NdotH, roughness);
+        float pdf = (D * NdotH / (4.0f * HdotV)) + 0.0001f;
+        float saSample = 1.0f / (float(sampleCount) * pdf + 0.0001f);
+        float mipLevel = std::max(0.5f * log2(saSample / saTexel) + mipBias, 0.0f);
+
+        auto& sample = data.points[sampleIndex];
+        sample.x = L.x;
+        sample.y = L.y;
+        sample.z = L.z;
+        sample.w = mipLevel;
+
+        data.invTotalWeight += NdotL;
+
+        sampleIndex++;
+    }
+    data.invTotalWeight = 1.0f / data.invTotalWeight;
+}
+
+void CubeMap::convolveForGGX(CubeMap& output, const std::atomic<bool>& abortProcessing) const {
+    // This should match fragment.glsl values, too
+    static const float ROUGHNESS_1_MIP_RESOLUTION = 1.5f;
+    static const gpu::uint16 MAX_SAMPLE_COUNT = 4000;
+
+    const auto mipCount = getMipCount();
+    GGXSamples params;
+
+    params.points.reserve(MAX_SAMPLE_COUNT);
+
+    for (gpu::uint16 mipLevel = 0; mipLevel < mipCount; ++mipLevel) {
+        // This is the inverse code found in fragment.glsl in evaluateAmbientLighting
+        float levelAlpha = float(mipLevel) / (mipCount - ROUGHNESS_1_MIP_RESOLUTION);
+        float mipRoughness = levelAlpha * (1.0f + 2.0f * levelAlpha) / 3.0f;
+        mipRoughness = std::max(1e-3f, mipRoughness);
+        mipRoughness = std::min(1.0f, mipRoughness);
+
+        params.points.resize(std::min<size_t>(MAX_SAMPLE_COUNT, 1U + size_t(4000 * mipRoughness * mipRoughness)));
+        generateGGXSamples(params, mipRoughness, _width);
+
+        for (int face = 0; face < 6; face++) {
+            convolveMipFaceForGGX(params, output, mipLevel, face, abortProcessing);
+            if (abortProcessing.load()) {
+                return;
+            }
+        }
+    }
+}
+
+void CubeMap::convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output, gpu::uint16 mipLevel, int face, const std::atomic<bool>& abortProcessing) const {
+    static const glm::vec3 NORMALS[24] = {
+        // POSITIVE X
+        glm::vec3(1.0f, 1.0f, 1.0f),
+        glm::vec3(1.0f, 1.0f, -1.0f),
+        glm::vec3(1.0f, -1.0f, 1.0f),
+        glm::vec3(1.0f, -1.0f, -1.0f),
+        // NEGATIVE X
+        glm::vec3(-1.0f, 1.0f, -1.0f),
+        glm::vec3(-1.0f, 1.0f, 1.0f),
+        glm::vec3(-1.0f, -1.0f, -1.0f),
+        glm::vec3(-1.0f, -1.0f, 1.0f),
+        // POSITIVE Y
+        glm::vec3(-1.0f, 1.0f, -1.0f),
+        glm::vec3(1.0f, 1.0f, -1.0f),
+        glm::vec3(-1.0f, 1.0f, 1.0f),
+        glm::vec3(1.0f, 1.0f, 1.0f),
+        // NEGATIVE Y
+        glm::vec3(-1.0f, -1.0f, 1.0f),
+        glm::vec3(1.0f, -1.0f, 1.0f),
+        glm::vec3(-1.0f, -1.0f, -1.0f),
+        glm::vec3(1.0f, -1.0f, -1.0f),
+        // POSITIVE Z
+        glm::vec3(-1.0f, 1.0f, 1.0f),
+        glm::vec3(1.0f, 1.0f, 1.0f),
+        glm::vec3(-1.0f, -1.0f, 1.0f),
+        glm::vec3(1.0f, -1.0f, 1.0f),
+        // NEGATIVE Z
+        glm::vec3(1.0f, 1.0f, -1.0f),
+        glm::vec3(-1.0f, 1.0f, -1.0f),
+        glm::vec3(1.0f, -1.0f, -1.0f),
+        glm::vec3(-1.0f, -1.0f, -1.0f)
+    };
+
+    const glm::vec3* faceNormals = NORMALS + face * 4;
+    const glm::vec3 deltaXNormalLo = faceNormals[1] - faceNormals[0];
+    const glm::vec3 deltaXNormalHi = faceNormals[3] - faceNormals[2];
+    auto& outputFace = output._mips[mipLevel][face];
+
+    tbb::parallel_for(tbb::blocked_range2d<int, int>(0, _width, 16, 0, _height, 16), [&](const tbb::blocked_range2d<int, int>& range) {
+        auto rowRange = range.rows();
+        auto colRange = range.cols();
+
+        for (auto x = rowRange.begin(); x < rowRange.end(); x++) {
+            const float xAlpha = (x + 0.5f) / _width;
+            const glm::vec3 normalYLo = faceNormals[0] + deltaXNormalLo * xAlpha;
+            const glm::vec3 normalYHi = faceNormals[2] + deltaXNormalHi * xAlpha;
+            const glm::vec3 deltaYNormal = normalYHi - normalYLo;
+
+            for (auto y = colRange.begin(); y < colRange.end(); y++) {
+                const float yAlpha = (y + 0.5f) / _width;
+                // Interpolate normal for this pixel
+                const glm::vec3 normal = glm::normalize(normalYLo + deltaYNormal * yAlpha);
+
+                outputFace[x + y * _width] = computeConvolution(normal, samples);
+            }
+
+            if (abortProcessing.load()) {
+                break;
+            }
+        }
+    });
+}
+
+glm::vec4 CubeMap::computeConvolution(const glm::vec3& N, const GGXSamples& samples) const {
+    // from tangent-space vector to world-space
+    glm::vec3 bitangent = abs(N.z) < 0.999 ? glm::vec3(0.0, 0.0, 1.0) : glm::vec3(1.0, 0.0, 0.0);
+    glm::vec3 tangent = glm::normalize(glm::cross(bitangent, N));
+    bitangent = glm::cross(N, tangent);
+
+    const size_t sampleCount = samples.points.size();
+    glm::vec4 prefilteredColor = glm::vec4(0.0f);
+
+    for (int i = 0; i < sampleCount; ++i) {
+        const auto& sample = samples.points[i];
+        glm::vec3 L(sample.x, sample.y, sample.z);
+        float NdotL = L.z;
+        float mipLevel = sample.w;
+        // Now back to world space
+        L = tangent * L.x + bitangent * L.y + N * L.z;
+        prefilteredColor += fetchLod(L, mipLevel) * NdotL;
+    }
+    prefilteredColor = prefilteredColor * samples.invTotalWeight;
+    prefilteredColor.a = 1.0f;
+    return prefilteredColor;
+}
\ No newline at end of file
diff --git a/libraries/image/src/image/CubeMap.h b/libraries/image/src/image/CubeMap.h
index 05a571cafc..231db7d76f 100644
--- a/libraries/image/src/image/CubeMap.h
+++ b/libraries/image/src/image/CubeMap.h
@@ -16,6 +16,7 @@
 #include <glm/vec4.hpp>
 #include <vector>
 #include <array>
+#include <atomic>
 
 namespace image {
 
@@ -31,11 +32,20 @@ namespace image {
         Faces& editMip(gpu::uint16 mipLevel) { return _mips[mipLevel]; }
         const Faces& getMip(gpu::uint16 mipLevel) const { return _mips[mipLevel]; }
 
+        void convolveForGGX(CubeMap& output, const std::atomic<bool>& abortProcessing) const;
+        glm::vec4 fetchLod(const glm::vec3& dir, float lod) const;
+
     private:
 
+        struct GGXSamples;
+
         int _width;
         int _height;
         std::vector<Faces> _mips;
+
+        static void generateGGXSamples(GGXSamples& data, float roughness, const int resolution);
+        void convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output, gpu::uint16 mipLevel, int face, const std::atomic<bool>& abortProcessing) const;
+        glm::vec4 computeConvolution(const glm::vec3& normal, const GGXSamples& samples) const;
     };
 
 }
diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp
index 6e7e08ea89..7131871937 100644
--- a/libraries/image/src/image/Image.cpp
+++ b/libraries/image/src/image/Image.cpp
@@ -850,13 +850,10 @@ void generateMips(gpu::Texture* texture, QImage&& image, BackendTarget target, c
     }
 }
 
-void convolveFaceWithGGX(const CubeMap& source, int face, const std::atomic<bool>& abortProcessing) {
-
-}
-
-void convolveWithGGX(gpu::Texture* texture, BackendTarget target, const std::atomic<bool>& abortProcessing = false) {
-    PROFILE_RANGE(resource_parse, "convolveWithGGX");
+void convolveForGGX(gpu::Texture* texture, BackendTarget target, const std::atomic<bool>& abortProcessing = false) {
+    PROFILE_RANGE(resource_parse, "convolveForGGX");
     CubeMap source(texture->getWidth(), texture->getHeight(), texture->getNumMips());
+    CubeMap output(texture->getWidth(), texture->getHeight(), texture->getNumMips());
     gpu::uint16 mipLevel;
     int face;
     const auto textureFormat = texture->getTexelFormat();
@@ -875,18 +872,16 @@ void convolveWithGGX(gpu::Texture* texture, BackendTarget target, const std::ato
         }
     }
 
-    for (face = 0; face < 6; face++) {
-        convolveFaceWithGGX(source, face, abortProcessing);
-    }
+    source.convolveForGGX(output, abortProcessing);
 
     if (!abortProcessing) {
         // Convert all mip data back from float
         unsigned char* convertedPixels = new unsigned char[texture->getWidth() * texture->getHeight() * sizeof(uint32)];
 
-        for (mipLevel = 0; mipLevel < source.getMipCount(); ++mipLevel) {
+        for (mipLevel = 0; mipLevel < output.getMipCount(); ++mipLevel) {
             auto mipDims = texture->evalMipDimensions(mipLevel);
             auto mipSize = texture->evalMipFaceSize(mipLevel);
-            auto& mip = source.getMip(mipLevel);
+            auto& mip = output.getMip(mipLevel);
 
             for (face = 0; face < 6; face++) {
                 convertFromFloat(convertedPixels, mipDims.x, mipDims.y, sizeof(uint32)*mipDims.x, textureFormat, mip[face]);
@@ -1620,7 +1615,7 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI
         }
 
         if (options & CUBE_GGX_CONVOLVE) {
-            convolveWithGGX(theTexture.get(), target, abortProcessing);
+            convolveForGGX(theTexture.get(), target, abortProcessing);
         }
     }
 
diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp
index 17c13df19a..f30e67a979 100644
--- a/libraries/render-utils/src/AntialiasingEffect.cpp
+++ b/libraries/render-utils/src/AntialiasingEffect.cpp
@@ -26,7 +26,7 @@
 #include "ViewFrustum.h"
 #include "GeometryCache.h"
 #include "FramebufferCache.h"
-
+#include "RandomAndNoise.h"
 
 namespace ru {
     using render_utils::slot::texture::Texture;
@@ -359,36 +359,11 @@ int JitterSampleConfig::play() {
     return _state;
 }
 
-template <int B> 
-class Halton {
-public:
-
-    float eval(int index) const {
-        float f = 1.0f;
-        float r = 0.0f;
-        float invB = 1.0f / (float)B;
-        index++; // Indices start at 1, not 0
-
-        while (index > 0) {
-            f = f * invB;
-            r = r + f * (float)(index % B);
-            index = index / B;
-
-        }
-
-        return r;
-    }
-
-};
-
-
 JitterSample::SampleSequence::SampleSequence(){
     // Halton sequence (2,3)
-    Halton<2> genX;
-    Halton<3> genY;
 
     for (int i = 0; i < SEQUENCE_LENGTH; i++) {
-        offsets[i] = glm::vec2(genX.eval(i), genY.eval(i));
+        offsets[i] = glm::vec2(evaluateHalton<2>(i), evaluateHalton<3>(i));
         offsets[i] -= vec2(0.5f);
     }
     offsets[SEQUENCE_LENGTH] = glm::vec2(0.0f);
diff --git a/libraries/shared/src/RandomAndNoise.h b/libraries/shared/src/RandomAndNoise.h
new file mode 100644
index 0000000000..c69c186159
--- /dev/null
+++ b/libraries/shared/src/RandomAndNoise.h
@@ -0,0 +1,47 @@
+//
+//  RandomAndNoise.h
+//
+//  Created by Olivier Prat on 05/16/18.
+//  Copyright 2018 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#ifndef RANDOM_AND_NOISE_H
+#define RANDOM_AND_NOISE_H
+
+#include <glm/vec2.hpp>
+
+// Low discrepancy Halton sequence generator
+template <int B>
+float evaluateHalton(int index) {
+    float f = 1.0f;
+    float r = 0.0f;
+    float invB = 1.0f / (float)B;
+    index++; // Indices start at 1, not 0
+
+    while (index > 0) {
+        f = f * invB;
+        r = r + f * (float)(index % B);
+        index = index / B;
+
+    }
+
+    return r;
+}
+
+inline float getRadicalInverseVdC(uint32_t bits) {
+    bits = (bits << 16u) | (bits >> 16u);
+    bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+    bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+    bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+    bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+    return float(bits) * 2.3283064365386963e-10f; // / 0x100000000\n"
+}
+
+// Low discrepancy Hammersley 2D sequence generator
+inline glm::vec2 evaluateHammersley(int k, const int sequenceLength) {
+    return glm::vec2(float(k) / float(sequenceLength), getRadicalInverseVdC(k));
+}
+
+#endif
\ No newline at end of file

From 5bf3cdd5927918e423639d02151193d57fc4d4bb Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Thu, 28 Mar 2019 11:59:21 +0100
Subject: [PATCH 04/23] Working on cubemap seams

---
 libraries/image/src/image/CubeMap.cpp | 200 +++++++++++++++++++++-----
 libraries/image/src/image/CubeMap.h   |  46 +++++-
 libraries/image/src/image/Image.cpp   |  28 ++--
 libraries/image/src/image/Image.h     |   6 +-
 4 files changed, 221 insertions(+), 59 deletions(-)

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index acd8d6fb85..852852e0a1 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -15,6 +15,7 @@
 #include <tbb/blocked_range2d.h>
 
 #include "RandomAndNoise.h"
+#include "Image.h"
 
 #ifndef M_PI
 #define M_PI    3.14159265359
@@ -22,14 +23,160 @@
 
 using namespace image;
 
-CubeMap::CubeMap(int width, int height, int mipCount) :
-    _width(width), _height(height) {
+static const glm::vec3 FACE_NORMALS[24] = {
+    // POSITIVE X
+    glm::vec3(1.0f, 1.0f, 1.0f),
+    glm::vec3(1.0f, 1.0f, -1.0f),
+    glm::vec3(1.0f, -1.0f, 1.0f),
+    glm::vec3(1.0f, -1.0f, -1.0f),
+    // NEGATIVE X
+    glm::vec3(-1.0f, 1.0f, -1.0f),
+    glm::vec3(-1.0f, 1.0f, 1.0f),
+    glm::vec3(-1.0f, -1.0f, -1.0f),
+    glm::vec3(-1.0f, -1.0f, 1.0f),
+    // POSITIVE Y
+    glm::vec3(-1.0f, 1.0f, -1.0f),
+    glm::vec3(1.0f, 1.0f, -1.0f),
+    glm::vec3(-1.0f, 1.0f, 1.0f),
+    glm::vec3(1.0f, 1.0f, 1.0f),
+    // NEGATIVE Y
+    glm::vec3(-1.0f, -1.0f, 1.0f),
+    glm::vec3(1.0f, -1.0f, 1.0f),
+    glm::vec3(-1.0f, -1.0f, -1.0f),
+    glm::vec3(1.0f, -1.0f, -1.0f),
+    // POSITIVE Z
+    glm::vec3(-1.0f, 1.0f, 1.0f),
+    glm::vec3(1.0f, 1.0f, 1.0f),
+    glm::vec3(-1.0f, -1.0f, 1.0f),
+    glm::vec3(1.0f, -1.0f, 1.0f),
+    // NEGATIVE Z
+    glm::vec3(1.0f, 1.0f, -1.0f),
+    glm::vec3(-1.0f, 1.0f, -1.0f),
+    glm::vec3(1.0f, -1.0f, -1.0f),
+    glm::vec3(-1.0f, -1.0f, -1.0f)
+};
+
+CubeMap::CubeMap(int width, int height, int mipCount) {
+    reset(width, height, mipCount);
+}
+
+CubeMap::CubeMap(gpu::TexturePointer texture, const std::atomic<bool>& abortProcessing) {
+    reset(texture->getWidth(), texture->getHeight(), texture->getNumMips());
+
+    const auto srcTextureFormat = texture->getTexelFormat();
+
+    for (gpu::uint16 mipLevel = 0; mipLevel < texture->getNumMips(); ++mipLevel) {
+        auto mipDims = texture->evalMipDimensions(mipLevel);
+        auto destLineStride = getFaceLineStride(mipLevel);
+
+        for (face = 0; face < 6; face++) {
+            auto sourcePixels = texture->accessStoredMipFace(mipLevel, face)->data();
+            auto destPixels = editFace(mipLevel, face);
+
+            convertToFloat(sourcePixels, mipDims.x, mipDims.y, sizeof(uint32)*mipDims.x, srcTextureFormat, destPixels, destLineStride);
+            if (abortProcessing.load()) {
+                return;
+            }
+        }
+
+        // Now copy edge rows and columns from neighbouring faces to fix
+        // seam filtering issues
+        seamColumnAndRow(mipLevel, gpu::Texture::CUBE_FACE_TOP_POS_Y, mipDims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, -1, -1);
+        seamColumnAndRow(mipLevel, gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, mipDims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, mipDims.y, 1);
+        seamColumnAndColumn(mipLevel, gpu::Texture::CUBE_FACE_FRONT_NEG_Z, 0, gpu::Texture::CUBE_FACE_RIGHT_POS_X, mipDims.x, 1);
+        seamColumnAndColumn(mipLevel, gpu::Texture::CUBE_FACE_BACK_POS_Z, mipDims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, -1, 1);
+
+        seamRowAndRow(mipLevel, gpu::Texture::CUBE_FACE_BACK_POS_Z, -1, gpu::Texture::CUBE_FACE_TOP_POS_Y, mipDims.y, 1);
+        seamRowAndRow(mipLevel, gpu::Texture::CUBE_FACE_BACK_POS_Z, mipDims.y, gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, -1, 1);
+        seamColumnAndColumn(mipLevel, gpu::Texture::CUBE_FACE_BACK_POS_Z, -1, gpu::Texture::CUBE_FACE_LEFT_NEG_X, mipDims.x, 1);
+
+        seamRowAndRow(mipLevel, gpu::Texture::CUBE_FACE_TOP_POS_Y, -1, gpu::Texture::CUBE_FACE_FRONT_NEG_Z, -1, -1);
+        seamColumnAndRow(mipLevel, gpu::Texture::CUBE_FACE_TOP_POS_Y, -1, gpu::Texture::CUBE_FACE_LEFT_NEG_X, -1, 1);
+
+        seamColumnAndColumn(mipLevel, gpu::Texture::CUBE_FACE_LEFT_NEG_X, -1, gpu::Texture::CUBE_FACE_FRONT_NEG_Z, mipDims.x, 1);
+        seamColumnAndRow(mipLevel, gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, -1, gpu::Texture::CUBE_FACE_LEFT_NEG_X, mipDims.y, -1);
+
+        seamRowAndRow(mipLevel, gpu::Texture::CUBE_FACE_FRONT_NEG_Z, mipDims.y, gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, mipDims.y, -1);
+
+        // Duplicate corner pixels
+        for (face = 0; face < 6; face++) {
+            auto& pixels = _mips[mipLevel][face];
+
+            pixels[0] = pixels[1];
+            pixels[mipDims.x+1] = pixels[mipDims.x];
+            pixels[(mipDims.y+1)*(mipDims.x+2)] = pixels[(mipDims.y+1)*(mipDims.x+2)+1];
+            pixels[(mipDims.y+2)*(mipDims.x+2)-1] = pixels[(mipDims.y+2)*(mipDims.x+2)-2];
+        }
+    }
+}
+
+inline static std::pair<int,int> getSrcAndDst(int dim, int value) {
+    int src;
+    int dst;
+
+    if (value < 0) {
+        src = 1;
+        dst = 0;
+    } else if (value >= dim) {
+        src = dim;
+        dst = dim+1;
+    }
+    return std::make_pair(src, dst);
+}
+
+void CubeMap::seamColumnAndColumn(gpu::uint16 mipLevel, int face0, int col0, int face1, int col1, int inc) {
+    auto mipDims = getMipDimensions(mipLevel);
+    auto coords0 = getSrcAndDst(mipDims.x, col0);
+    auto coords1 = getSrcAndDst(mipDims.x, col1);
+
+    copyColumnToColumn(mipLevel, face0, coords0.first, face1, coords1.second, inc);
+    copyColumnToColumn(mipLevel, face1, coords1.first, face0, coords0.second, inc);
+}
+
+void CubeMap::seamColumnAndRow(gpu::uint16 mipLevel, int face0, int col0, int face1, int row1, int inc) {
+    auto mipDims = getMipDimensions(mipLevel);
+    auto coords0 = getSrcAndDst(mipDims.x, col0);
+    auto coords1 = getSrcAndDst(mipDims.y, row1);
+
+    copyColumnToRow(mipLevel, face0, coords0.first, face1, coords1.second, inc);
+    copyRowToColumn(mipLevel, face1, coords1.first, face0, coords0.second, inc);
+}
+
+void CubeMap::seamRowAndRow(gpu::uint16 mipLevel, int face0, int row0, int face1, int row1, int inc) {
+    auto mipDims = getMipDimensions(mipLevel);
+    auto coords0 = getSrcAndDst(mipDims.y, row0);
+    auto coords1 = getSrcAndDst(mipDims.y, row1);
+
+    copyRowToRow(mipLevel, face0, coords0.first, face1, coords1.second, inc);
+    copyRowToRow(mipLevel, face1, coords1.first, face0, coords0.second, inc);
+}
+
+void CubeMap::copyColumnToColumn(gpu::uint16 mipLevel, int srcFace, int srcCol, int dstFace, int dstCol, int dstInc) {
+
+}
+
+void CubeMap::copyRowToRow(gpu::uint16 mipLevel, int srcFace, int srcRow, int dstFace, int dstRow, int dstInc) {
+
+}
+
+void CubeMap::copyColumnToRow(gpu::uint16 mipLevel, int srcFace, int srcCol, int dstFace, int dstRow, int dstInc) {
+
+}
+
+void CubeMap::copyRowToColumn(gpu::uint16 mipLevel, int srcFace, int srcRow, int dstFace, int dstCol, int dstInc) {
+
+}
+
+void CubeMap::reset(int width, int height, int mipCount) {
     assert(mipCount >0 && _width > 0 && _height > 0);
+    _width = width;
+    _height = height;
     _mips.resize(mipCount);
     for (auto mipLevel = 0; mipLevel < mipCount; mipLevel++) {
-        auto mipWidth = std::max(1, width >> mipLevel);
-        auto mipHeight = std::max(1, height >> mipLevel);
-        auto mipPixelCount = mipWidth * mipHeight;
+        auto mipDimensions = getMipDimensions(mipLevel);
+        // Add extra pixels on edges to perform edge seam fixup (we will duplicate pixels from
+        // neighbouring faces)
+        auto mipPixelCount = (mipDimensions.x+2) * (mipDimensions.y+2);
 
         for (auto& face : _mips[mipLevel]) {
             face.resize(mipPixelCount);
@@ -37,6 +184,14 @@ CubeMap::CubeMap(int width, int height, int mipCount) :
     }
 }
 
+glm::vec4* CubeMap::editFace(gpu::uint16 mipLevel, int face) {
+    return _mips[mipLevel][face].data() + 3 + _width;
+}
+
+const glm::vec4* CubeMap::getFace(gpu::uint16 mipLevel, int face) const;
+size_t CubeMap::getFaceLineStride(gpu::uint16 mipLevel) const;
+
+
 glm::vec4 CubeMap::fetchLod(const glm::vec3& dir, float lod) const {
     // TODO
     return glm::vec4(0.0f);
@@ -155,40 +310,7 @@ void CubeMap::convolveForGGX(CubeMap& output, const std::atomic<bool>& abortProc
 }
 
 void CubeMap::convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output, gpu::uint16 mipLevel, int face, const std::atomic<bool>& abortProcessing) const {
-    static const glm::vec3 NORMALS[24] = {
-        // POSITIVE X
-        glm::vec3(1.0f, 1.0f, 1.0f),
-        glm::vec3(1.0f, 1.0f, -1.0f),
-        glm::vec3(1.0f, -1.0f, 1.0f),
-        glm::vec3(1.0f, -1.0f, -1.0f),
-        // NEGATIVE X
-        glm::vec3(-1.0f, 1.0f, -1.0f),
-        glm::vec3(-1.0f, 1.0f, 1.0f),
-        glm::vec3(-1.0f, -1.0f, -1.0f),
-        glm::vec3(-1.0f, -1.0f, 1.0f),
-        // POSITIVE Y
-        glm::vec3(-1.0f, 1.0f, -1.0f),
-        glm::vec3(1.0f, 1.0f, -1.0f),
-        glm::vec3(-1.0f, 1.0f, 1.0f),
-        glm::vec3(1.0f, 1.0f, 1.0f),
-        // NEGATIVE Y
-        glm::vec3(-1.0f, -1.0f, 1.0f),
-        glm::vec3(1.0f, -1.0f, 1.0f),
-        glm::vec3(-1.0f, -1.0f, -1.0f),
-        glm::vec3(1.0f, -1.0f, -1.0f),
-        // POSITIVE Z
-        glm::vec3(-1.0f, 1.0f, 1.0f),
-        glm::vec3(1.0f, 1.0f, 1.0f),
-        glm::vec3(-1.0f, -1.0f, 1.0f),
-        glm::vec3(1.0f, -1.0f, 1.0f),
-        // NEGATIVE Z
-        glm::vec3(1.0f, 1.0f, -1.0f),
-        glm::vec3(-1.0f, 1.0f, -1.0f),
-        glm::vec3(1.0f, -1.0f, -1.0f),
-        glm::vec3(-1.0f, -1.0f, -1.0f)
-    };
-
-    const glm::vec3* faceNormals = NORMALS + face * 4;
+    const glm::vec3* faceNormals = FACE_NORMALS + face * 4;
     const glm::vec3 deltaXNormalLo = faceNormals[1] - faceNormals[0];
     const glm::vec3 deltaXNormalHi = faceNormals[3] - faceNormals[2];
     auto& outputFace = output._mips[mipLevel][face];
diff --git a/libraries/image/src/image/CubeMap.h b/libraries/image/src/image/CubeMap.h
index 231db7d76f..578cb65af5 100644
--- a/libraries/image/src/image/CubeMap.h
+++ b/libraries/image/src/image/CubeMap.h
@@ -12,7 +12,7 @@
 #ifndef hifi_image_CubeMap_h
 #define hifi_image_CubeMap_h
 
-#include <gpu/Forward.h>
+#include <gpu/Texture.h>
 #include <glm/vec4.hpp>
 #include <vector>
 #include <array>
@@ -22,15 +22,34 @@ namespace image {
 
     class CubeMap {
     public:
-        
-        using Face = std::vector<glm::vec4>;
-        using Faces = std::array<Face, 6>;
-
+ 
         CubeMap(int width, int height, int mipCount);
+        CubeMap(gpu::TexturePointer texture, const std::atomic<bool>& abortProcessing = false);
+
+        void reset(int width, int height, int mipCount);
 
         gpu::uint16 getMipCount() const { return (gpu::uint16)_mips.size(); }
-        Faces& editMip(gpu::uint16 mipLevel) { return _mips[mipLevel]; }
-        const Faces& getMip(gpu::uint16 mipLevel) const { return _mips[mipLevel]; }
+        int getMipWidth(gpu::uint16 mipLevel) const {
+            return std::max(1, _width >> mipLevel);
+        }
+        int getMipHeight(gpu::uint16 mipLevel) const {
+            return std::max(1, _height >> mipLevel);
+        }
+        gpu::Vec2i getMipDimensions(gpu::uint16 mipLevel) const {
+            return gpu::Vec2i(getMipWidth(mipLevel), getMipHeight(mipLevel));
+        }
+
+        glm::vec4* editFace(gpu::uint16 mipLevel, int face) {
+            return _mips[mipLevel][face].data() + getFaceLineStride(mipLevel) + 1;
+        }
+
+        const glm::vec4* getFace(gpu::uint16 mipLevel, int face) const {
+            return _mips[mipLevel][face].data() + getFaceLineStride(mipLevel) + 1;
+        }
+
+        size_t getFaceLineStride(gpu::uint16 mipLevel) const {
+            return getMipWidth(mipLevel)+2;
+        }
 
         void convolveForGGX(CubeMap& output, const std::atomic<bool>& abortProcessing) const;
         glm::vec4 fetchLod(const glm::vec3& dir, float lod) const;
@@ -39,6 +58,9 @@ namespace image {
 
         struct GGXSamples;
 
+        using Face = std::vector<glm::vec4>;
+        using Faces = std::array<Face, 6>;
+
         int _width;
         int _height;
         std::vector<Faces> _mips;
@@ -46,6 +68,16 @@ namespace image {
         static void generateGGXSamples(GGXSamples& data, float roughness, const int resolution);
         void convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output, gpu::uint16 mipLevel, int face, const std::atomic<bool>& abortProcessing) const;
         glm::vec4 computeConvolution(const glm::vec3& normal, const GGXSamples& samples) const;
+
+        void seamColumnAndColumn(gpu::uint16 mipLevel, int face0, int col0, int face1, int col1, int inc);
+        void seamColumnAndRow(gpu::uint16 mipLevel, int face0, int col0, int face1, int row1, int inc);
+        void seamRowAndRow(gpu::uint16 mipLevel, int face0, int row0, int face1, int row1, int inc);
+
+        void copyColumnToColumn(gpu::uint16 mipLevel, int srcFace, int srcCol, int dstFace, int dstCol, int dstInc);
+        void copyColumnToRow(gpu::uint16 mipLevel, int srcFace, int srcCol, int dstFace, int dstRow, int dstInc);
+        void copyRowToRow(gpu::uint16 mipLevel, int srcFace, int srcRow, int dstFace, int dstRow, int dstInc);
+        void copyRowToColumn(gpu::uint16 mipLevel, int srcFace, int srcRow, int dstFace, int dstCol, int dstInc);
+
     };
 
 }
diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp
index 7131871937..3d4dfa8c40 100644
--- a/libraries/image/src/image/Image.cpp
+++ b/libraries/image/src/image/Image.cpp
@@ -521,14 +521,15 @@ public:
     }
 };
 
-void convertToFloat(const unsigned char* source, int width, int height, int lineStride, gpu::Element sourceFormat, std::vector<glm::vec4>& output) {
-    std::vector<glm::vec4>::iterator outputIt;
+void image::convertToFloat(const unsigned char* source, int width, int height, int srcLineByteStride, gpu::Element sourceFormat, 
+                           glm::vec4* output, int outputLinePixelStride) {
+    glm::vec4* outputIt;
     auto unpackFunc = getUnpackingFunction(sourceFormat);
 
-    output.resize(width * height);
-    outputIt = output.begin();
+    outputLinePixelStride -= width;
+    outputIt = output;
     for (auto lineNb = 0; lineNb < height; lineNb++) {
-        const uint32* srcPixelIt = reinterpret_cast<const uint32*>(source + lineNb * lineStride);
+        const uint32* srcPixelIt = reinterpret_cast<const uint32*>(source + lineNb * srcLineByteStride);
         const uint32* srcPixelEnd = srcPixelIt + width;
 
         while (srcPixelIt < srcPixelEnd) {
@@ -536,17 +537,19 @@ void convertToFloat(const unsigned char* source, int width, int height, int line
             ++srcPixelIt;
             ++outputIt;
         }
+        outputIt += outputLinePixelStride;
     }
-    assert(outputIt == output.end());
 }
 
-void convertFromFloat(unsigned char* output, int width, int height, int lineStride, gpu::Element outputFormat, const std::vector<glm::vec4>& source) {
-    std::vector<glm::vec4>::const_iterator sourceIt;
+void image::convertFromFloat(unsigned char* output, int width, int height, int outputLineByteStride, gpu::Element outputFormat, 
+                             const glm::vec4* source, int srcLinePixelStride) {
+    const glm::vec4* sourceIt;
     auto packFunc = getPackingFunction(outputFormat);
 
-    sourceIt = source.begin();
+    srcLinePixelStride -= width;
+    sourceIt = source;
     for (auto lineNb = 0; lineNb < height; lineNb++) {
-        uint32* outPixelIt = reinterpret_cast<uint32*>(output + lineNb * lineStride);
+        uint32* outPixelIt = reinterpret_cast<uint32*>(output + lineNb * outputLineByteStride);
         uint32* outPixelEnd = outPixelIt + width;
 
         while (outPixelIt < outPixelEnd) {
@@ -554,8 +557,8 @@ void convertFromFloat(unsigned char* output, int width, int height, int lineStri
             ++outPixelIt;
             ++sourceIt;
         }
+        sourceIt += srcLinePixelStride;
     }
-    assert(sourceIt == source.end());
 }
 
 void generateHDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic<bool>& abortProcessing, int face) {
@@ -594,7 +597,8 @@ void generateHDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target
         return;
     }
 
-    convertToFloat(localCopy.bits(), width, height, localCopy.bytesPerLine(), HDR_FORMAT, data);
+    data.resize(width * height);
+    convertToFloat(localCopy.bits(), width, height, localCopy.bytesPerLine(), HDR_FORMAT, data.data(), width);
 
     // 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.
diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h
index 9c27b0cf3c..df113c9eff 100644
--- a/libraries/image/src/image/Image.h
+++ b/libraries/image/src/image/Image.h
@@ -27,7 +27,11 @@ extern const QImage::Format QIMAGE_HDRFORMAT;
 
 std::function<gpu::uint32(const glm::vec3&)> getHDRPackingFunction();
 std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction();
-
+void convertToFloat(const unsigned char* source, int width, int height, int srcLineByteStride, gpu::Element sourceFormat, 
+                    glm::vec4* output, int outputLinePixelStride);
+void convertFromFloat(unsigned char* output, int width, int height, int outputLineByteStride, gpu::Element outputFormat, 
+                      const glm::vec4* source, int srcLinePixelStride);
+                      
 namespace TextureUsage {
 
 enum Type {

From 2397d5919f95192757b1f9ae5dd113949bdaefb8 Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Thu, 28 Mar 2019 15:15:51 +0100
Subject: [PATCH 05/23] Finished convolution code

---
 libraries/image/src/image/CubeMap.cpp | 422 ++++++++++++++++++++------
 libraries/image/src/image/CubeMap.h   |  15 +-
 libraries/image/src/image/Image.cpp   |  50 +--
 libraries/image/src/image/Image.h     |   8 +-
 4 files changed, 340 insertions(+), 155 deletions(-)

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index 852852e0a1..1b337e5b81 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -56,117 +56,231 @@ static const glm::vec3 FACE_NORMALS[24] = {
     glm::vec3(-1.0f, -1.0f, -1.0f)
 };
 
+struct CubeFaceMip {
+    CubeFaceMip(gpu::uint16 level, const CubeMap* cubemap) {
+        _dims = cubemap->getMipDimensions(level);
+        _lineStride = _dims.x + 2;
+    }
+
+    gpu::Vec2i _dims;
+    int _lineStride;
+};
+
+class CubeMap::ConstMip : public CubeFaceMip {
+public:
+
+    ConstMip(gpu::uint16 level, const CubeMap* cubemap) : 
+        CubeFaceMip(level, cubemap), _faces(cubemap->_mips[level]) {
+    }
+
+    glm::vec4 fetch(int face, glm::vec2 uv) const {
+        glm::vec2 coordFrac = uv * glm::vec2(_dims) + 0.5f;
+        glm::vec2 coords = glm::floor(coordFrac);
+
+        coordFrac -= coords;
+
+        const auto* pixels = _faces[face].data();
+        gpu::Vec2i loCoords(coords);
+        const int offset = loCoords.x + loCoords.y * _lineStride;
+        glm::vec4 colorLL = pixels[offset];
+        glm::vec4 colorHL = pixels[offset +1 ];
+        glm::vec4 colorLH = pixels[offset + _lineStride];
+        glm::vec4 colorHH = pixels[offset + 1 + _lineStride];
+
+        colorLL += (colorHL - colorLL) * coordFrac.x;
+        colorLH += (colorHH - colorLH) * coordFrac.x;
+        return colorLL + (colorLH - colorLL) * coordFrac.y;
+    }
+
+private:
+
+    const Faces& _faces;
+
+};
+
+class CubeMap::Mip : public CubeFaceMip {
+public:
+
+    Mip(gpu::uint16 level, CubeMap* cubemap) :
+        CubeFaceMip(level, cubemap), _faces(cubemap->_mips[level]) {
+    }
+
+    void applySeams() {
+        // Copy edge rows and columns from neighbouring faces to fix seam filtering issues
+        seamColumnAndRow(gpu::Texture::CUBE_FACE_TOP_POS_Y, _dims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, -1, -1);
+        seamColumnAndRow(gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, _dims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, _dims.y, 1);
+        seamColumnAndColumn(gpu::Texture::CUBE_FACE_FRONT_NEG_Z, 0, gpu::Texture::CUBE_FACE_RIGHT_POS_X, _dims.x, 1);
+        seamColumnAndColumn(gpu::Texture::CUBE_FACE_BACK_POS_Z, _dims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, -1, 1);
+
+        seamRowAndRow(gpu::Texture::CUBE_FACE_BACK_POS_Z, -1, gpu::Texture::CUBE_FACE_TOP_POS_Y, _dims.y, 1);
+        seamRowAndRow(gpu::Texture::CUBE_FACE_BACK_POS_Z, _dims.y, gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, -1, 1);
+        seamColumnAndColumn(gpu::Texture::CUBE_FACE_BACK_POS_Z, -1, gpu::Texture::CUBE_FACE_LEFT_NEG_X, _dims.x, 1);
+
+        seamRowAndRow(gpu::Texture::CUBE_FACE_TOP_POS_Y, -1, gpu::Texture::CUBE_FACE_FRONT_NEG_Z, -1, -1);
+        seamColumnAndRow(gpu::Texture::CUBE_FACE_TOP_POS_Y, -1, gpu::Texture::CUBE_FACE_LEFT_NEG_X, -1, 1);
+
+        seamColumnAndColumn(gpu::Texture::CUBE_FACE_LEFT_NEG_X, -1, gpu::Texture::CUBE_FACE_FRONT_NEG_Z, _dims.x, 1);
+        seamColumnAndRow(gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, -1, gpu::Texture::CUBE_FACE_LEFT_NEG_X, _dims.y, -1);
+
+        seamRowAndRow(gpu::Texture::CUBE_FACE_FRONT_NEG_Z, _dims.y, gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, _dims.y, -1);
+
+        // Duplicate corner pixels
+        for (int face = 0; face < 6; face++) {
+            auto& pixels = _faces[face];
+
+            pixels[0] = pixels[1];
+            pixels[_dims.x + 1] = pixels[_dims.x];
+            pixels[(_dims.y + 1)*(_dims.x + 2)] = pixels[(_dims.y + 1)*(_dims.x + 2) + 1];
+            pixels[(_dims.y + 2)*(_dims.x + 2) - 1] = pixels[(_dims.y + 2)*(_dims.x + 2) - 2];
+        }
+    }
+
+private:
+
+    Faces& _faces;
+
+    static std::pair<int, int> getSrcAndDst(int dim, int value) {
+        int src;
+        int dst;
+
+        if (value < 0) {
+            src = 1;
+            dst = 0;
+        } else if (value >= dim) {
+            src = dim;
+            dst = dim + 1;
+        }
+        return std::make_pair(src, dst);
+    }
+
+    void seamColumnAndColumn(int face0, int col0, int face1, int col1, int inc) {
+        auto coords0 = getSrcAndDst(_dims.x, col0);
+        auto coords1 = getSrcAndDst(_dims.x, col1);
+
+        copyColumnToColumn(face0, coords0.first, face1, coords1.second, inc);
+        copyColumnToColumn(face1, coords1.first, face0, coords0.second, inc);
+    }
+
+    void seamColumnAndRow(int face0, int col0, int face1, int row1, int inc) {
+        auto coords0 = getSrcAndDst(_dims.x, col0);
+        auto coords1 = getSrcAndDst(_dims.y, row1);
+
+        copyColumnToRow(face0, coords0.first, face1, coords1.second, inc);
+        copyRowToColumn(face1, coords1.first, face0, coords0.second, inc);
+    }
+
+    void seamRowAndRow(int face0, int row0, int face1, int row1, int inc) {
+        auto coords0 = getSrcAndDst(_dims.y, row0);
+        auto coords1 = getSrcAndDst(_dims.y, row1);
+
+        copyRowToRow(face0, coords0.first, face1, coords1.second, inc);
+        copyRowToRow(face1, coords1.first, face0, coords0.second, inc);
+    }
+
+    inline static void copy(const glm::vec4* srcFirst, const glm::vec4* srcLast, int srcStride, glm::vec4* dstBegin, int dstStride) {
+        while (srcFirst <= srcLast) {
+            *dstBegin = *srcFirst;
+            srcFirst += srcStride;
+            dstBegin += dstStride;
+        }
+    }
+
+    void copyColumnToColumn(int srcFace, int srcCol, int dstFace, int dstCol, const int dstInc) {
+        const auto lastOffset = _lineStride * (_dims.y - 1);
+        auto srcFirst = _faces[srcFace].data() + srcCol + _lineStride;
+        auto srcLast = srcFirst + lastOffset;
+
+        auto dstFirst = _faces[dstFace].data() + dstCol + _lineStride;
+        auto dstLast = dstFirst + lastOffset;
+        const auto dstStride = _lineStride * dstInc;
+
+        if (dstInc < 0) {
+            std::swap(dstFirst, dstLast);
+        }
+
+        copy(srcFirst, srcLast, _lineStride, dstFirst, dstStride);
+    }
+
+    void copyRowToRow(int srcFace, int srcRow, int dstFace, int dstRow, const int dstInc) {
+        const auto lastOffset =(_dims.x - 1);
+        auto srcFirst = _faces[srcFace].data() + srcRow * _lineStride + 1;
+        auto srcLast = srcFirst + lastOffset;
+
+        auto dstFirst = _faces[dstFace].data() + dstRow * _lineStride + 1;
+        auto dstLast = dstFirst + lastOffset;
+
+        if (dstInc < 0) {
+            std::swap(dstFirst, dstLast);
+        }
+
+        copy(srcFirst, srcLast, 1, dstFirst, dstInc);
+    }
+
+    void copyColumnToRow(int srcFace, int srcCol, int dstFace, int dstRow, int dstInc) {
+        const auto srcLastOffset = _lineStride * (_dims.y - 1);
+        auto srcFirst = _faces[srcFace].data() + srcCol + _lineStride;
+        auto srcLast = srcFirst + srcLastOffset;
+
+        const auto dstLastOffset = (_dims.x - 1);
+        auto dstFirst = _faces[dstFace].data() + dstRow * _lineStride + 1;
+        auto dstLast = dstFirst + dstLastOffset;
+
+        if (dstInc < 0) {
+            std::swap(dstFirst, dstLast);
+        }
+
+        copy(srcFirst, srcLast, _lineStride, dstFirst, dstInc);
+    }
+
+    void copyRowToColumn(int srcFace, int srcRow, int dstFace, int dstCol, int dstInc) {
+        const auto srcLastOffset = (_dims.x - 1);
+        auto srcFirst = _faces[srcFace].data() + srcRow * _lineStride + 1;
+        auto srcLast = srcFirst + srcLastOffset;
+
+        const auto dstLastOffset = _lineStride * (_dims.y - 1);
+        auto dstFirst = _faces[dstFace].data() + dstCol + _lineStride;
+        auto dstLast = dstFirst + dstLastOffset;
+        const auto dstStride = _lineStride * dstInc;
+
+        if (dstInc < 0) {
+            std::swap(dstFirst, dstLast);
+        }
+
+        copy(srcFirst, srcLast, 1, dstFirst, dstStride);
+    }
+};
+
 CubeMap::CubeMap(int width, int height, int mipCount) {
     reset(width, height, mipCount);
 }
 
-CubeMap::CubeMap(gpu::TexturePointer texture, const std::atomic<bool>& abortProcessing) {
+CubeMap::CubeMap(gpu::Texture* texture, const std::atomic<bool>& abortProcessing) {
     reset(texture->getWidth(), texture->getHeight(), texture->getNumMips());
 
     const auto srcTextureFormat = texture->getTexelFormat();
+    int face;
 
     for (gpu::uint16 mipLevel = 0; mipLevel < texture->getNumMips(); ++mipLevel) {
         auto mipDims = texture->evalMipDimensions(mipLevel);
-        auto destLineStride = getFaceLineStride(mipLevel);
+        auto srcLineStride = (int) (sizeof(gpu::uint32)*mipDims.x);
+        auto dstLineStride = getFaceLineStride(mipLevel);
 
         for (face = 0; face < 6; face++) {
             auto sourcePixels = texture->accessStoredMipFace(mipLevel, face)->data();
             auto destPixels = editFace(mipLevel, face);
 
-            convertToFloat(sourcePixels, mipDims.x, mipDims.y, sizeof(uint32)*mipDims.x, srcTextureFormat, destPixels, destLineStride);
+            convertToFloat(sourcePixels, mipDims.x, mipDims.y, srcLineStride, srcTextureFormat, destPixels, dstLineStride);
             if (abortProcessing.load()) {
                 return;
             }
         }
 
-        // Now copy edge rows and columns from neighbouring faces to fix
-        // seam filtering issues
-        seamColumnAndRow(mipLevel, gpu::Texture::CUBE_FACE_TOP_POS_Y, mipDims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, -1, -1);
-        seamColumnAndRow(mipLevel, gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, mipDims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, mipDims.y, 1);
-        seamColumnAndColumn(mipLevel, gpu::Texture::CUBE_FACE_FRONT_NEG_Z, 0, gpu::Texture::CUBE_FACE_RIGHT_POS_X, mipDims.x, 1);
-        seamColumnAndColumn(mipLevel, gpu::Texture::CUBE_FACE_BACK_POS_Z, mipDims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, -1, 1);
+        Mip mip(mipLevel, this);
 
-        seamRowAndRow(mipLevel, gpu::Texture::CUBE_FACE_BACK_POS_Z, -1, gpu::Texture::CUBE_FACE_TOP_POS_Y, mipDims.y, 1);
-        seamRowAndRow(mipLevel, gpu::Texture::CUBE_FACE_BACK_POS_Z, mipDims.y, gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, -1, 1);
-        seamColumnAndColumn(mipLevel, gpu::Texture::CUBE_FACE_BACK_POS_Z, -1, gpu::Texture::CUBE_FACE_LEFT_NEG_X, mipDims.x, 1);
-
-        seamRowAndRow(mipLevel, gpu::Texture::CUBE_FACE_TOP_POS_Y, -1, gpu::Texture::CUBE_FACE_FRONT_NEG_Z, -1, -1);
-        seamColumnAndRow(mipLevel, gpu::Texture::CUBE_FACE_TOP_POS_Y, -1, gpu::Texture::CUBE_FACE_LEFT_NEG_X, -1, 1);
-
-        seamColumnAndColumn(mipLevel, gpu::Texture::CUBE_FACE_LEFT_NEG_X, -1, gpu::Texture::CUBE_FACE_FRONT_NEG_Z, mipDims.x, 1);
-        seamColumnAndRow(mipLevel, gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, -1, gpu::Texture::CUBE_FACE_LEFT_NEG_X, mipDims.y, -1);
-
-        seamRowAndRow(mipLevel, gpu::Texture::CUBE_FACE_FRONT_NEG_Z, mipDims.y, gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, mipDims.y, -1);
-
-        // Duplicate corner pixels
-        for (face = 0; face < 6; face++) {
-            auto& pixels = _mips[mipLevel][face];
-
-            pixels[0] = pixels[1];
-            pixels[mipDims.x+1] = pixels[mipDims.x];
-            pixels[(mipDims.y+1)*(mipDims.x+2)] = pixels[(mipDims.y+1)*(mipDims.x+2)+1];
-            pixels[(mipDims.y+2)*(mipDims.x+2)-1] = pixels[(mipDims.y+2)*(mipDims.x+2)-2];
-        }
+        mip.applySeams();
     }
 }
 
-inline static std::pair<int,int> getSrcAndDst(int dim, int value) {
-    int src;
-    int dst;
-
-    if (value < 0) {
-        src = 1;
-        dst = 0;
-    } else if (value >= dim) {
-        src = dim;
-        dst = dim+1;
-    }
-    return std::make_pair(src, dst);
-}
-
-void CubeMap::seamColumnAndColumn(gpu::uint16 mipLevel, int face0, int col0, int face1, int col1, int inc) {
-    auto mipDims = getMipDimensions(mipLevel);
-    auto coords0 = getSrcAndDst(mipDims.x, col0);
-    auto coords1 = getSrcAndDst(mipDims.x, col1);
-
-    copyColumnToColumn(mipLevel, face0, coords0.first, face1, coords1.second, inc);
-    copyColumnToColumn(mipLevel, face1, coords1.first, face0, coords0.second, inc);
-}
-
-void CubeMap::seamColumnAndRow(gpu::uint16 mipLevel, int face0, int col0, int face1, int row1, int inc) {
-    auto mipDims = getMipDimensions(mipLevel);
-    auto coords0 = getSrcAndDst(mipDims.x, col0);
-    auto coords1 = getSrcAndDst(mipDims.y, row1);
-
-    copyColumnToRow(mipLevel, face0, coords0.first, face1, coords1.second, inc);
-    copyRowToColumn(mipLevel, face1, coords1.first, face0, coords0.second, inc);
-}
-
-void CubeMap::seamRowAndRow(gpu::uint16 mipLevel, int face0, int row0, int face1, int row1, int inc) {
-    auto mipDims = getMipDimensions(mipLevel);
-    auto coords0 = getSrcAndDst(mipDims.y, row0);
-    auto coords1 = getSrcAndDst(mipDims.y, row1);
-
-    copyRowToRow(mipLevel, face0, coords0.first, face1, coords1.second, inc);
-    copyRowToRow(mipLevel, face1, coords1.first, face0, coords0.second, inc);
-}
-
-void CubeMap::copyColumnToColumn(gpu::uint16 mipLevel, int srcFace, int srcCol, int dstFace, int dstCol, int dstInc) {
-
-}
-
-void CubeMap::copyRowToRow(gpu::uint16 mipLevel, int srcFace, int srcRow, int dstFace, int dstRow, int dstInc) {
-
-}
-
-void CubeMap::copyColumnToRow(gpu::uint16 mipLevel, int srcFace, int srcCol, int dstFace, int dstRow, int dstInc) {
-
-}
-
-void CubeMap::copyRowToColumn(gpu::uint16 mipLevel, int srcFace, int srcRow, int dstFace, int dstCol, int dstInc) {
-
-}
-
 void CubeMap::reset(int width, int height, int mipCount) {
     assert(mipCount >0 && _width > 0 && _height > 0);
     _width = width;
@@ -184,17 +298,123 @@ void CubeMap::reset(int width, int height, int mipCount) {
     }
 }
 
-glm::vec4* CubeMap::editFace(gpu::uint16 mipLevel, int face) {
-    return _mips[mipLevel][face].data() + 3 + _width;
+void CubeMap::copyTo(gpu::Texture* texture, const std::atomic<bool>& abortProcessing) const {
+    assert(_width == texture->getWidth() && _height == texture->getHeight() && texture->getNumMips() == _mips.size());
+
+    // Convert all mip data back from float
+    unsigned char* convertedPixels = new unsigned char[_width * _height * sizeof(gpu::uint32)];
+    const auto textureFormat = texture->getTexelFormat();
+
+    for (gpu::uint16 mipLevel = 0; mipLevel < texture->getNumMips(); ++mipLevel) {
+        auto mipDims = texture->evalMipDimensions(mipLevel);
+        auto mipSize = texture->evalMipFaceSize(mipLevel);
+        auto srcLineStride = getFaceLineStride(mipLevel);
+        auto dstLineStride = (int)(sizeof(gpu::uint32)*mipDims.x);
+
+        for (auto face = 0; face < 6; face++) {
+            auto srcPixels = getFace(mipLevel, face);
+
+            convertFromFloat(convertedPixels, mipDims.x, mipDims.y, dstLineStride, textureFormat, srcPixels, srcLineStride);
+            texture->assignStoredMipFace(mipLevel, face, mipSize, convertedPixels);
+            if (abortProcessing.load()) {
+                delete[] convertedPixels;
+                return;
+            }
+        }
+    }
+
+    delete[] convertedPixels;
 }
 
-const glm::vec4* CubeMap::getFace(gpu::uint16 mipLevel, int face) const;
-size_t CubeMap::getFaceLineStride(gpu::uint16 mipLevel) const;
+void CubeMap::getFaceUV(const glm::vec3& dir, int* index, glm::vec2* uv) {
+    // Taken from https://en.wikipedia.org/wiki/Cube_mapping
+    float absX = std::abs(dir.x);
+    float absY = std::abs(dir.y);
+    float absZ = std::abs(dir.z);
 
+    auto isXPositive = dir.x > 0;
+    auto isYPositive = dir.y > 0;
+    auto isZPositive = dir.z > 0;
+
+    float maxAxis, uc, vc;
+
+    // POSITIVE X
+    if (isXPositive && absX >= absY && absX >= absZ) {
+        // u (0 to 1) goes from +z to -z
+        // v (0 to 1) goes from -y to +y
+        maxAxis = absX;
+        uc = -dir.z;
+        vc = -dir.y;
+        *index = 0;
+    }
+    // NEGATIVE X
+    else if (!isXPositive && absX >= absY && absX >= absZ) {
+        // u (0 to 1) goes from -z to +z
+        // v (0 to 1) goes from -y to +y
+        maxAxis = absX;
+        uc = dir.z;
+        vc = -dir.y;
+        *index = 1;
+    }
+    // POSITIVE Y
+    else if (isYPositive && absY >= absX && absY >= absZ) {
+        // u (0 to 1) goes from -x to +x
+        // v (0 to 1) goes from +z to -z
+        maxAxis = absY;
+        uc = dir.x;
+        vc = dir.z;
+        *index = 2;
+    }
+    // NEGATIVE Y
+    else if (!isYPositive && absY >= absX && absY >= absZ) {
+        // u (0 to 1) goes from -x to +x
+        // v (0 to 1) goes from -z to +z
+        maxAxis = absY;
+        uc = dir.x;
+        vc = -dir.z;
+        *index = 3;
+    }
+    // POSITIVE Z
+    else if (isZPositive && absZ >= absX && absZ >= absY) {
+        // u (0 to 1) goes from -x to +x
+        // v (0 to 1) goes from -y to +y
+        maxAxis = absZ;
+        uc = dir.x;
+        vc = -dir.y;
+        *index = 4;
+    }
+    // NEGATIVE Z
+    else if (!isZPositive && absZ >= absX && absZ >= absY) {
+        // u (0 to 1) goes from +x to -x
+        // v (0 to 1) goes from -y to +y
+        maxAxis = absZ;
+        uc = -dir.x;
+        vc = -dir.y;
+        *index = 5;
+    }
+
+    // Convert range from -1 to 1 to 0 to 1
+    uv->x = 0.5f * (uc / maxAxis + 1.0f);
+    uv->y = 0.5f * (vc / maxAxis + 1.0f);
+}
 
 glm::vec4 CubeMap::fetchLod(const glm::vec3& dir, float lod) const {
-    // TODO
-    return glm::vec4(0.0f);
+    gpu::uint16 loLevel = (gpu::uint16)std::floor(lod);
+    gpu::uint16 hiLevel = (gpu::uint16)std::ceil(lod);
+    float lodFrac = lod - (float)loLevel;
+    ConstMip loMip(loLevel, this);
+    ConstMip hiMip(hiLevel, this);
+    int face;
+    glm::vec2 uv;
+    glm::vec4 loColor;
+    glm::vec4 hiColor;
+
+    getFaceUV(dir, &face, &uv);
+
+    loColor = loMip.fetch(face, uv);
+    hiColor = hiMip.fetch(face, uv);
+
+    return loColor + (hiColor - loColor) * lodFrac;
 }
 
 static glm::vec3 sampleGGX(const glm::vec2& Xi, const float roughness) {
@@ -283,7 +503,7 @@ void CubeMap::generateGGXSamples(GGXSamples& data, float roughness, const int re
 void CubeMap::convolveForGGX(CubeMap& output, const std::atomic<bool>& abortProcessing) const {
     // This should match fragment.glsl values, too
     static const float ROUGHNESS_1_MIP_RESOLUTION = 1.5f;
-    static const gpu::uint16 MAX_SAMPLE_COUNT = 4000;
+    static const size_t MAX_SAMPLE_COUNT = 4000;
 
     const auto mipCount = getMipCount();
     GGXSamples params;
@@ -294,10 +514,17 @@ void CubeMap::convolveForGGX(CubeMap& output, const std::atomic<bool>& abortProc
         // This is the inverse code found in fragment.glsl in evaluateAmbientLighting
         float levelAlpha = float(mipLevel) / (mipCount - ROUGHNESS_1_MIP_RESOLUTION);
         float mipRoughness = levelAlpha * (1.0f + 2.0f * levelAlpha) / 3.0f;
+
         mipRoughness = std::max(1e-3f, mipRoughness);
         mipRoughness = std::min(1.0f, mipRoughness);
 
-        params.points.resize(std::min<size_t>(MAX_SAMPLE_COUNT, 1U + size_t(4000 * mipRoughness * mipRoughness)));
+        size_t mipTotalPixelCount = getMipWidth(mipLevel) * getMipHeight(mipLevel) * 6;
+        size_t sampleCount = 1U + size_t(4000 * mipRoughness * mipRoughness);
+
+        sampleCount = std::min(sampleCount, 2 * mipTotalPixelCount);
+        sampleCount = std::min(MAX_SAMPLE_COUNT, 4 * mipTotalPixelCount);
+
+        params.points.resize(sampleCount);
         generateGGXSamples(params, mipRoughness, _width);
 
         for (int face = 0; face < 6; face++) {
@@ -313,7 +540,8 @@ void CubeMap::convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output,
     const glm::vec3* faceNormals = FACE_NORMALS + face * 4;
     const glm::vec3 deltaXNormalLo = faceNormals[1] - faceNormals[0];
     const glm::vec3 deltaXNormalHi = faceNormals[3] - faceNormals[2];
-    auto& outputFace = output._mips[mipLevel][face];
+    auto outputFacePixels = output.editFace(mipLevel, face);
+    auto outputLineStride = output.getFaceLineStride(mipLevel);
 
     tbb::parallel_for(tbb::blocked_range2d<int, int>(0, _width, 16, 0, _height, 16), [&](const tbb::blocked_range2d<int, int>& range) {
         auto rowRange = range.rows();
@@ -330,7 +558,7 @@ void CubeMap::convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output,
                 // Interpolate normal for this pixel
                 const glm::vec3 normal = glm::normalize(normalYLo + deltaYNormal * yAlpha);
 
-                outputFace[x + y * _width] = computeConvolution(normal, samples);
+                outputFacePixels[x + y * outputLineStride] = computeConvolution(normal, samples);
             }
 
             if (abortProcessing.load()) {
diff --git a/libraries/image/src/image/CubeMap.h b/libraries/image/src/image/CubeMap.h
index 578cb65af5..0d926cdf44 100644
--- a/libraries/image/src/image/CubeMap.h
+++ b/libraries/image/src/image/CubeMap.h
@@ -24,9 +24,10 @@ namespace image {
     public:
  
         CubeMap(int width, int height, int mipCount);
-        CubeMap(gpu::TexturePointer texture, const std::atomic<bool>& abortProcessing = false);
+        CubeMap(gpu::Texture* texture, const std::atomic<bool>& abortProcessing = false);
 
         void reset(int width, int height, int mipCount);
+        void copyTo(gpu::Texture* texture, const std::atomic<bool>& abortProcessing = false) const;
 
         gpu::uint16 getMipCount() const { return (gpu::uint16)_mips.size(); }
         int getMipWidth(gpu::uint16 mipLevel) const {
@@ -57,6 +58,8 @@ namespace image {
     private:
 
         struct GGXSamples;
+        class Mip;
+        class ConstMip;
 
         using Face = std::vector<glm::vec4>;
         using Faces = std::array<Face, 6>;
@@ -65,19 +68,11 @@ namespace image {
         int _height;
         std::vector<Faces> _mips;
 
+        static void getFaceUV(const glm::vec3& dir, int* index, glm::vec2* uv);
         static void generateGGXSamples(GGXSamples& data, float roughness, const int resolution);
         void convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output, gpu::uint16 mipLevel, int face, const std::atomic<bool>& abortProcessing) const;
         glm::vec4 computeConvolution(const glm::vec3& normal, const GGXSamples& samples) const;
 
-        void seamColumnAndColumn(gpu::uint16 mipLevel, int face0, int col0, int face1, int col1, int inc);
-        void seamColumnAndRow(gpu::uint16 mipLevel, int face0, int col0, int face1, int row1, int inc);
-        void seamRowAndRow(gpu::uint16 mipLevel, int face0, int row0, int face1, int row1, int inc);
-
-        void copyColumnToColumn(gpu::uint16 mipLevel, int srcFace, int srcCol, int dstFace, int dstCol, int dstInc);
-        void copyColumnToRow(gpu::uint16 mipLevel, int srcFace, int srcCol, int dstFace, int dstRow, int dstInc);
-        void copyRowToRow(gpu::uint16 mipLevel, int srcFace, int srcRow, int dstFace, int dstRow, int dstInc);
-        void copyRowToColumn(gpu::uint16 mipLevel, int srcFace, int srcRow, int dstFace, int dstCol, int dstInc);
-
     };
 
 }
diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp
index 3d4dfa8c40..e99cf90e0b 100644
--- a/libraries/image/src/image/Image.cpp
+++ b/libraries/image/src/image/Image.cpp
@@ -521,8 +521,8 @@ public:
     }
 };
 
-void image::convertToFloat(const unsigned char* source, int width, int height, int srcLineByteStride, gpu::Element sourceFormat, 
-                           glm::vec4* output, int outputLinePixelStride) {
+void image::convertToFloat(const unsigned char* source, int width, int height, size_t srcLineByteStride, gpu::Element sourceFormat,
+                           glm::vec4* output, size_t outputLinePixelStride) {
     glm::vec4* outputIt;
     auto unpackFunc = getUnpackingFunction(sourceFormat);
 
@@ -541,8 +541,8 @@ void image::convertToFloat(const unsigned char* source, int width, int height, i
     }
 }
 
-void image::convertFromFloat(unsigned char* output, int width, int height, int outputLineByteStride, gpu::Element outputFormat, 
-                             const glm::vec4* source, int srcLinePixelStride) {
+void image::convertFromFloat(unsigned char* output, int width, int height, size_t outputLineByteStride, gpu::Element outputFormat,
+                             const glm::vec4* source, size_t srcLinePixelStride) {
     const glm::vec4* sourceIt;
     auto packFunc = getPackingFunction(outputFormat);
 
@@ -856,49 +856,11 @@ void generateMips(gpu::Texture* texture, QImage&& image, BackendTarget target, c
 
 void convolveForGGX(gpu::Texture* texture, BackendTarget target, const std::atomic<bool>& abortProcessing = false) {
     PROFILE_RANGE(resource_parse, "convolveForGGX");
-    CubeMap source(texture->getWidth(), texture->getHeight(), texture->getNumMips());
+    CubeMap source(texture, abortProcessing);
     CubeMap output(texture->getWidth(), texture->getHeight(), texture->getNumMips());
-    gpu::uint16 mipLevel;
-    int face;
-    const auto textureFormat = texture->getTexelFormat();
-
-    // Convert all mip data to float as source
-    for (mipLevel = 0; mipLevel < source.getMipCount(); ++mipLevel) {
-        auto mipDims = texture->evalMipDimensions(mipLevel);
-        auto& mip = source.editMip(mipLevel);
-
-        for (face = 0; face < 6; face++) {
-            auto sourcePixels = texture->accessStoredMipFace(mipLevel, face)->data();
-            convertToFloat(sourcePixels, mipDims.x, mipDims.y, sizeof(uint32)*mipDims.x, textureFormat, mip[face]);
-            if (abortProcessing.load()) {
-                return;
-            }
-        }
-    }
 
     source.convolveForGGX(output, abortProcessing);
-
-    if (!abortProcessing) {
-        // Convert all mip data back from float
-        unsigned char* convertedPixels = new unsigned char[texture->getWidth() * texture->getHeight() * sizeof(uint32)];
-
-        for (mipLevel = 0; mipLevel < output.getMipCount(); ++mipLevel) {
-            auto mipDims = texture->evalMipDimensions(mipLevel);
-            auto mipSize = texture->evalMipFaceSize(mipLevel);
-            auto& mip = output.getMip(mipLevel);
-
-            for (face = 0; face < 6; face++) {
-                convertFromFloat(convertedPixels, mipDims.x, mipDims.y, sizeof(uint32)*mipDims.x, textureFormat, mip[face]);
-                texture->assignStoredMipFace(mipLevel, face, mipSize, convertedPixels);
-                if (abortProcessing.load()) {
-                    delete[] convertedPixels;
-                    return;
-                }
-            }
-        }
-
-        delete[] convertedPixels;
-    }
+    output.copyTo(texture, abortProcessing);
 }
 
 void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAsMask) {
diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h
index df113c9eff..4df7674e08 100644
--- a/libraries/image/src/image/Image.h
+++ b/libraries/image/src/image/Image.h
@@ -27,10 +27,10 @@ extern const QImage::Format QIMAGE_HDRFORMAT;
 
 std::function<gpu::uint32(const glm::vec3&)> getHDRPackingFunction();
 std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction();
-void convertToFloat(const unsigned char* source, int width, int height, int srcLineByteStride, gpu::Element sourceFormat, 
-                    glm::vec4* output, int outputLinePixelStride);
-void convertFromFloat(unsigned char* output, int width, int height, int outputLineByteStride, gpu::Element outputFormat, 
-                      const glm::vec4* source, int srcLinePixelStride);
+void convertToFloat(const unsigned char* source, int width, int height, size_t srcLineByteStride, gpu::Element sourceFormat, 
+                    glm::vec4* output, size_t outputLinePixelStride);
+void convertFromFloat(unsigned char* output, int width, int height, size_t outputLineByteStride, gpu::Element outputFormat,
+                      const glm::vec4* source, size_t srcLinePixelStride);
                       
 namespace TextureUsage {
 

From cef9e454d5bc3f2e89af392409d357a31e9d7d28 Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Thu, 28 Mar 2019 16:44:22 +0100
Subject: [PATCH 06/23] Working beta pipeline

---
 libraries/image/src/image/CubeMap.cpp         |  2 +-
 libraries/image/src/image/Image.cpp           |  8 +-
 libraries/image/src/image/Image.h             |  8 +-
 .../src/DeferredLightingEffect.cpp            | 16 ++--
 .../render-utils/src/DeferredLightingEffect.h |  3 +-
 libraries/render-utils/src/LightAmbient.slh   | 13 +++-
 tools/oven/src/ui/SkyboxBakeWidget.cpp        | 74 +++++++++++++------
 tools/oven/src/ui/SkyboxBakeWidget.h          |  4 +
 8 files changed, 82 insertions(+), 46 deletions(-)

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index 1b337e5b81..a8aba0454c 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -501,7 +501,7 @@ void CubeMap::generateGGXSamples(GGXSamples& data, float roughness, const int re
 }
 
 void CubeMap::convolveForGGX(CubeMap& output, const std::atomic<bool>& abortProcessing) const {
-    // This should match fragment.glsl values, too
+    // This should match the value in the getMipLevelFromRoughness function (LightAmbient.slh)
     static const float ROUGHNESS_1_MIP_RESOLUTION = 1.5f;
     static const size_t MAX_SAMPLE_COUNT = 4000;
 
diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp
index e99cf90e0b..53a76e3b0f 100644
--- a/libraries/image/src/image/Image.cpp
+++ b/libraries/image/src/image/Image.cpp
@@ -105,9 +105,9 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con
             return image::TextureUsage::createCubeTextureFromImage;
         case AMBIENT_TEXTURE:
             if (options.value("generateIrradiance", true).toBool()) {
-                return image::TextureUsage::createCubeTextureAndIrradianceFromImage;
+                return image::TextureUsage::createAmbientCubeTextureAndIrradianceFromImage;
             } else {
-                return image::TextureUsage::createCubeTextureFromImage;
+                return image::TextureUsage::createAmbientCubeTextureFromImage;
             }
         case BUMP_TEXTURE:
             return image::TextureUsage::createNormalTextureFromBumpImage;
@@ -188,12 +188,12 @@ gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage,
     return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, CUBE_DEFAULT, abortProcessing);
 }
 
-gpu::TexturePointer TextureUsage::createGGXConvolvedCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
+gpu::TexturePointer TextureUsage::createAmbientCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
                                                            bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing) {
     return processCubeTextureColorFromImage(std::move(image), srcImageName, compress, target, CUBE_GGX_CONVOLVE, abortProcessing);
 }
 
-gpu::TexturePointer TextureUsage::createGGXConvolvedCubeTextureAndIrradianceFromImage(QImage&& image, const std::string& srcImageName,
+gpu::TexturePointer TextureUsage::createAmbientCubeTextureAndIrradianceFromImage(QImage&& image, const std::string& srcImageName,
                                                                         bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing) {
     return processCubeTextureColorFromImage(std::move(image), srcImageName, compress, target, CUBE_GENERATE_IRRADIANCE | CUBE_GGX_CONVOLVE, abortProcessing);
 }
diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h
index 4df7674e08..237dfcc6e7 100644
--- a/libraries/image/src/image/Image.h
+++ b/libraries/image/src/image/Image.h
@@ -78,10 +78,10 @@ gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string
                                                bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
 gpu::TexturePointer createCubeTextureAndIrradianceFromImage(QImage&& image, const std::string& srcImageName,
                                                             bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createGGXConvolvedCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
-                                                           bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createGGXConvolvedCubeTextureAndIrradianceFromImage(QImage&& image, const std::string& srcImageName,
-                                                                        bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
+gpu::TexturePointer createAmbientCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
+                                                      bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
+gpu::TexturePointer createAmbientCubeTextureAndIrradianceFromImage(QImage&& image, const std::string& srcImageName,
+                                                                   bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
 gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName,
                                                    bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing); 
 gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress,
diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp
index b936060741..2b19101653 100644
--- a/libraries/render-utils/src/DeferredLightingEffect.cpp
+++ b/libraries/render-utils/src/DeferredLightingEffect.cpp
@@ -642,8 +642,6 @@ void RenderDeferred::run(const RenderContextPointer& renderContext, const Inputs
     config->setGPUBatchRunTime(_gpuTimer->getGPUAverage(), _gpuTimer->getBatchAverage());
 }
 
-
-
 void DefaultLightingSetup::run(const RenderContextPointer& renderContext) {
 
     if (!_defaultLight || !_defaultBackground) {
@@ -655,21 +653,21 @@ void DefaultLightingSetup::run(const RenderContextPointer& renderContext) {
                 defaultSkyboxURL, image::TextureUsage::SKY_TEXTURE);
         }
 
-        if (!_defaultSkyboxAmbientTexture) {
+        if (!_defaultAmbientNetworkTexture) {
             PROFILE_RANGE(render, "Process Default Ambient map");
-            _defaultSkyboxAmbientTexture = DependencyManager::get<TextureCache>()->getTexture(
+            _defaultAmbientNetworkTexture = DependencyManager::get<TextureCache>()->getTexture(
                 defaultSkyboxURL, image::TextureUsage::AMBIENT_TEXTURE);
         }
 
         if (_defaultSkyboxNetworkTexture && _defaultSkyboxNetworkTexture->isLoaded() && _defaultSkyboxNetworkTexture->getGPUTexture()) {
-            _defaultSkybox->setCubemap(_defaultSkyboxAmbientTexture);
+            _defaultSkybox->setCubemap(_defaultSkyboxNetworkTexture->getGPUTexture());
         } else {
             // Don't do anything until the skybox has loaded
             return;
         }
 
-        if (_defaultSkyboxAmbientTexture && _defaultSkyboxAmbientTexture->isLoaded() && _defaultSkyboxAmbientTexture->getGPUTexture()) {
-            _defaultSkyboxAmbientTexture = _defaultSkyboxAmbientTexture->getGPUTexture();
+        if (_defaultAmbientNetworkTexture && _defaultAmbientNetworkTexture->isLoaded() && _defaultAmbientNetworkTexture->getGPUTexture()) {
+            _defaultAmbientTexture = _defaultAmbientNetworkTexture->getGPUTexture();
         } else {
             // Don't do anything until the ambient box has been loaded
             return;
@@ -688,8 +686,8 @@ void DefaultLightingSetup::run(const RenderContextPointer& renderContext) {
             lp->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset::OLD_TOWN_SQUARE);
 
             lp->setAmbientIntensity(0.5f);
-            lp->setAmbientMap(_defaultSkyboxAmbientTexture);
-            auto irradianceSH = _defaultSkyboxAmbientTexture->getIrradiance();
+            lp->setAmbientMap(_defaultAmbientTexture);
+            auto irradianceSH = _defaultAmbientTexture->getIrradiance();
             if (irradianceSH) {
                 lp->setAmbientSphere((*irradianceSH));
             }
diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h
index f4935000ef..1cc6ca4767 100644
--- a/libraries/render-utils/src/DeferredLightingEffect.h
+++ b/libraries/render-utils/src/DeferredLightingEffect.h
@@ -212,7 +212,8 @@ protected:
     HazeStage::Index _defaultHazeID{ HazeStage::INVALID_INDEX };
     graphics::SkyboxPointer _defaultSkybox { new ProceduralSkybox() };
     NetworkTexturePointer _defaultSkyboxNetworkTexture;
-    gpu::TexturePointer _defaultSkyboxAmbientTexture;
+    NetworkTexturePointer _defaultAmbientNetworkTexture;
+    gpu::TexturePointer _defaultAmbientTexture;
 };
 
 #endif // hifi_DeferredLightingEffect_h
diff --git a/libraries/render-utils/src/LightAmbient.slh b/libraries/render-utils/src/LightAmbient.slh
index 4ea9c0cd4c..0c7130b110 100644
--- a/libraries/render-utils/src/LightAmbient.slh
+++ b/libraries/render-utils/src/LightAmbient.slh
@@ -36,6 +36,13 @@ vec3 fresnelSchlickAmbient(vec3 fresnelColor, float ndotd, float gloss) {
 <$declareSkyboxMap()$>
 <@endif@>
 
+float getMipLevelFromRoughness(float roughness, float lodCount) {
+    // This should match the value in the CubeMap::convolveForGGX method (CubeMap.cpp)
+    float ROUGHNESS_1_MIP_RESOLUTION = 1.5;
+    float deltaLod = lodCount - ROUGHNESS_1_MIP_RESOLUTION;
+    return (sqrt(6.0*roughness+0.25)-0.5)*deltaLod*0.5;
+}
+
 vec3 evalAmbientSpecularIrradiance(LightAmbient ambient, SurfaceData surface, vec3 lightDir) {
     vec3 specularLight;
     <@if supportIfAmbientMapElseAmbientSphere@>
@@ -43,10 +50,10 @@ vec3 evalAmbientSpecularIrradiance(LightAmbient ambient, SurfaceData surface, ve
             <@endif@>
             <@if supportAmbientMap@>
         {
-            float levels = getLightAmbientMapNumMips(ambient);
-            float m = 12.0 / (1.0+11.0*surface.roughness);
-            float lod = levels - m;
+            float levelCount = getLightAmbientMapNumMips(ambient);
+            float lod = getMipLevelFromRoughness(surface.roughness, levelCount);
             lod = max(lod, 0.0);
+
             specularLight = evalSkyboxLight(lightDir, lod).xyz;
         }
     <@endif@>
diff --git a/tools/oven/src/ui/SkyboxBakeWidget.cpp b/tools/oven/src/ui/SkyboxBakeWidget.cpp
index 113346c5e7..2bea38b571 100644
--- a/tools/oven/src/ui/SkyboxBakeWidget.cpp
+++ b/tools/oven/src/ui/SkyboxBakeWidget.cpp
@@ -17,6 +17,7 @@
 #include <QtWidgets/QLineEdit>
 #include <QtWidgets/QMessageBox>
 #include <QtWidgets/QPushButton>
+#include <QtWidgets/QCheckBox>
 #include <QtWidgets/QStackedWidget>
 
 #include <QtCore/QDir>
@@ -61,6 +62,15 @@ void SkyboxBakeWidget::setupUI() {
     // start a new row for next component
     ++rowIndex;
 
+    // setup a section to enable Ambient map baking
+    _ambientMapBox = new QCheckBox("Bake ambient map(s)");
+    _ambientMapBox->setChecked(false);
+
+    gridLayout->addWidget(_ambientMapBox, rowIndex, 1);
+
+    // start a new row for next component
+    ++rowIndex;
+
     // setup a section to choose the output directory
     QLabel* outputDirectoryLabel = new QLabel("Output Directory");
 
@@ -176,51 +186,67 @@ void SkyboxBakeWidget::bakeButtonClicked() {
 
         // if the URL doesn't have a scheme, assume it is a local file
         if (skyboxToBakeURL.scheme() != "http" && skyboxToBakeURL.scheme() != "https" && skyboxToBakeURL.scheme() != "ftp") {
-            skyboxToBakeURL.setScheme("file");
+            skyboxToBakeURL = QUrl::fromLocalFile(fileURLString);
         }
 
         // everything seems to be in place, kick off a bake for this skybox now
-        auto baker = std::unique_ptr<TextureBaker> {
-            new TextureBaker(skyboxToBakeURL, image::TextureUsage::SKY_TEXTURE, outputDirectory.absolutePath())
-        };
+        addBaker(new TextureBaker(skyboxToBakeURL, image::TextureUsage::SKY_TEXTURE, outputDirectory.absolutePath()),
+                 outputDirectory);
 
-        // move the baker to a worker thread
-        baker->moveToThread(Oven::instance().getNextWorkerThread());
+        if (_ambientMapBox->isChecked()) {
+            QString ambientMapBaseFilename;
+            QString urlPath = skyboxToBakeURL.path();
+            auto urlParts = urlPath.split('.');
 
-        // invoke the bake method on the baker thread
-        QMetaObject::invokeMethod(baker.get(), "bake");
+            urlParts.front() += "-ambient";
+            ambientMapBaseFilename = QUrl(urlParts.front()).fileName();
 
-        // make sure we hear about the results of this baker when it is done
-        connect(baker.get(), &TextureBaker::finished, this, &SkyboxBakeWidget::handleFinishedBaker);
-
-        // add a pending row to the results window to show that this bake is in process
-        auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow();
-        auto resultsRow = resultsWindow->addPendingResultRow(skyboxToBakeURL.fileName(), outputDirectory);
-
-        // keep a unique_ptr to this baker
-        // and remember the row that represents it in the results table
-        _bakers.emplace_back(std::move(baker), resultsRow);
+            // we need to bake the corresponding ambient map too
+            addBaker(new TextureBaker(skyboxToBakeURL, image::TextureUsage::AMBIENT_TEXTURE, outputDirectory.absolutePath(), QString(), ambientMapBaseFilename),
+                     outputDirectory);
+        }
     }
 }
 
+void SkyboxBakeWidget::addBaker(TextureBaker* baker, const QDir& outputDirectory) {
+    auto textureBaker = std::unique_ptr<TextureBaker>{ baker };
+
+    // move the textureBaker to a worker thread
+    textureBaker->moveToThread(Oven::instance().getNextWorkerThread());
+
+    // invoke the bake method on the textureBaker thread
+    QMetaObject::invokeMethod(textureBaker.get(), "bake");
+
+    // make sure we hear about the results of this textureBaker when it is done
+    connect(textureBaker.get(), &TextureBaker::finished, this, &SkyboxBakeWidget::handleFinishedBaker);
+
+    // add a pending row to the results window to show that this bake is in process
+    auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow();
+    auto resultsRow = resultsWindow->addPendingResultRow(baker->getBaseFilename(), outputDirectory);
+
+    // keep a unique_ptr to this textureBaker
+    // and remember the row that represents it in the results table
+    _bakers.emplace_back(std::move(textureBaker), resultsRow);
+}
+
 void SkyboxBakeWidget::handleFinishedBaker() {
-    if (auto baker = qobject_cast<TextureBaker*>(sender())) {
+    if (auto textureBaker = qobject_cast<TextureBaker*>(sender())) {
         // add the results of this bake to the results window
-        auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) {
-            return value.first.get() == baker;
+        auto it = std::find_if(_bakers.begin(), _bakers.end(), [textureBaker](const BakerRowPair& value) {
+            return value.first.get() == textureBaker;
         });
 
         if (it != _bakers.end()) {
             auto resultRow = it->second;
             auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow();
 
-            if (baker->hasErrors()) {
-                resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n"));
+            if (textureBaker->hasErrors()) {
+                resultsWindow->changeStatusForRow(resultRow, textureBaker->getErrors().join("\n"));
             } else {
                 resultsWindow->changeStatusForRow(resultRow, "Success");
             }
 
-            // drop our strong pointer to the baker now that we are done with it
+            // drop our strong pointer to the textureBaker now that we are done with it
             _bakers.erase(it);
         }
     }
diff --git a/tools/oven/src/ui/SkyboxBakeWidget.h b/tools/oven/src/ui/SkyboxBakeWidget.h
index f00ab07f33..f560964649 100644
--- a/tools/oven/src/ui/SkyboxBakeWidget.h
+++ b/tools/oven/src/ui/SkyboxBakeWidget.h
@@ -21,6 +21,7 @@
 #include "BakeWidget.h"
 
 class QLineEdit;
+class QCheckBox;
 
 class SkyboxBakeWidget : public BakeWidget {
     Q_OBJECT
@@ -42,9 +43,12 @@ private:
 
     QLineEdit* _selectionLineEdit;
     QLineEdit* _outputDirLineEdit;
+    QCheckBox* _ambientMapBox;
 
     Setting::Handle<QString> _exportDirectory;
     Setting::Handle<QString> _selectionStartDirectory;
+
+    void addBaker(TextureBaker* baker, const QDir& outputDir);
 };
 
 #endif // hifi_SkyboxBakeWidget_h

From 5f6f178438e4836909b57ec8080d3a1028f83bd3 Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Thu, 28 Mar 2019 18:50:12 +0100
Subject: [PATCH 07/23] Fixed compression when convolving

---
 libraries/image/src/image/CubeMap.cpp | 165 +++++++++++++++++++-------
 libraries/image/src/image/CubeMap.h   |   6 +-
 libraries/image/src/image/Image.cpp   | 147 ++++++++++++-----------
 libraries/image/src/image/Image.h     |  13 ++
 4 files changed, 216 insertions(+), 115 deletions(-)

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index a8aba0454c..68fc6fe848 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -16,6 +16,9 @@
 
 #include "RandomAndNoise.h"
 #include "Image.h"
+#include "ImageLogging.h"
+
+#include <nvtt/nvtt.h>
 
 #ifndef M_PI
 #define M_PI    3.14159265359
@@ -57,11 +60,16 @@ static const glm::vec3 FACE_NORMALS[24] = {
 };
 
 struct CubeFaceMip {
+
     CubeFaceMip(gpu::uint16 level, const CubeMap* cubemap) {
         _dims = cubemap->getMipDimensions(level);
         _lineStride = _dims.x + 2;
     }
 
+    CubeFaceMip(const CubeFaceMip& other) : _dims(other._dims), _lineStride(other._lineStride) {
+
+    }
+
     gpu::Vec2i _dims;
     int _lineStride;
 };
@@ -101,10 +109,13 @@ private:
 class CubeMap::Mip : public CubeFaceMip {
 public:
 
-    Mip(gpu::uint16 level, CubeMap* cubemap) :
+    explicit Mip(gpu::uint16 level, CubeMap* cubemap) :
         CubeFaceMip(level, cubemap), _faces(cubemap->_mips[level]) {
     }
 
+    Mip(const Mip& other) : CubeFaceMip(other), _faces(other._faces) {
+    }
+
     void applySeams() {
         // Copy edge rows and columns from neighbouring faces to fix seam filtering issues
         seamColumnAndRow(gpu::Texture::CUBE_FACE_TOP_POS_Y, _dims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, -1, -1);
@@ -139,6 +150,14 @@ private:
 
     Faces& _faces;
 
+    inline static void copy(const glm::vec4* srcFirst, const glm::vec4* srcLast, int srcStride, glm::vec4* dstBegin, int dstStride) {
+        while (srcFirst <= srcLast) {
+            *dstBegin = *srcFirst;
+            srcFirst += srcStride;
+            dstBegin += dstStride;
+        }
+    }
+
     static std::pair<int, int> getSrcAndDst(int dim, int value) {
         int src;
         int dst;
@@ -177,14 +196,6 @@ private:
         copyRowToRow(face1, coords1.first, face0, coords0.second, inc);
     }
 
-    inline static void copy(const glm::vec4* srcFirst, const glm::vec4* srcLast, int srcStride, glm::vec4* dstBegin, int dstStride) {
-        while (srcFirst <= srcLast) {
-            *dstBegin = *srcFirst;
-            srcFirst += srcStride;
-            dstBegin += dstStride;
-        }
-    }
-
     void copyColumnToColumn(int srcFace, int srcCol, int dstFace, int dstCol, const int dstInc) {
         const auto lastOffset = _lineStride * (_dims.y - 1);
         auto srcFirst = _faces[srcFace].data() + srcCol + _lineStride;
@@ -254,33 +265,83 @@ CubeMap::CubeMap(int width, int height, int mipCount) {
     reset(width, height, mipCount);
 }
 
-CubeMap::CubeMap(gpu::Texture* texture, const std::atomic<bool>& abortProcessing) {
-    reset(texture->getWidth(), texture->getHeight(), texture->getNumMips());
+struct CubeMap::MipMapOutputHandler : public nvtt::OutputHandler {
+    MipMapOutputHandler(CubeMap* cube) : _cubemap(cube) {}
+
+    void beginImage(int size, int width, int height, int depth, int face, int miplevel) override {
+        _data = _cubemap->editFace(miplevel, face);
+        _current = _data;
+    }
+
+    bool writeData(const void* data, int size) override {
+        assert((size % sizeof(glm::vec4)) == 0);
+        memcpy(_current, data, size);
+        _current += size / sizeof(glm::vec4);
+        return true;
+    }
+
+    void endImage() override {
+        _data = nullptr;
+        _current = nullptr;
+    }
+
+    CubeMap* _cubemap{ nullptr };
+    glm::vec4* _data{ nullptr };
+    glm::vec4* _current{ nullptr };
+};
+
+CubeMap::CubeMap(const std::vector<QImage>& faces, gpu::Element srcTextureFormat, int mipCount, const std::atomic<bool>& abortProcessing) {
+    reset(faces.front().width(), faces.front().height(), mipCount);
 
-    const auto srcTextureFormat = texture->getTexelFormat();
     int face;
 
-    for (gpu::uint16 mipLevel = 0; mipLevel < texture->getNumMips(); ++mipLevel) {
-        auto mipDims = texture->evalMipDimensions(mipLevel);
-        auto srcLineStride = (int) (sizeof(gpu::uint32)*mipDims.x);
-        auto dstLineStride = getFaceLineStride(mipLevel);
-
-        for (face = 0; face < 6; face++) {
-            auto sourcePixels = texture->accessStoredMipFace(mipLevel, face)->data();
-            auto destPixels = editFace(mipLevel, face);
-
-            convertToFloat(sourcePixels, mipDims.x, mipDims.y, srcLineStride, srcTextureFormat, destPixels, dstLineStride);
-            if (abortProcessing.load()) {
-                return;
-            }
+    struct MipMapErrorHandler : public nvtt::ErrorHandler {
+        virtual void error(nvtt::Error e) override {
+            qCWarning(imagelogging) << "Texture mip map creation error:" << nvtt::errorString(e);
         }
+    };
 
+    // Compute mips
+    for (face = 0; face < 6; face++) {
+        auto sourcePixels = faces[face].bits();
+        auto floatPixels = editFace(0, face);
+
+        convertToFloat(sourcePixels, _width, _height, faces[face].bytesPerLine(), srcTextureFormat, floatPixels, _width);
+
+        nvtt::Surface surface;
+        surface.setImage(nvtt::InputFormat_RGBA_32F, _width, _height, 1, floatPixels);
+        surface.setAlphaMode(nvtt::AlphaMode_None);
+        surface.setWrapMode(nvtt::WrapMode_Clamp);
+
+        auto mipLevel = 0;
+        copyFace(_width, _height, reinterpret_cast<const glm::vec4*>(surface.data()), surface.width(), editFace(0, face), getFaceLineStride(0));
+
+        while (surface.canMakeNextMipmap() && !abortProcessing.load()) {
+            surface.buildNextMipmap(nvtt::MipmapFilter_Box);
+            mipLevel++;
+
+            copyFace(surface.width(), surface.height(), reinterpret_cast<const glm::vec4*>(surface.data()), surface.width(), editFace(mipLevel, face), getFaceLineStride(mipLevel));
+        }
+    }
+
+    if (abortProcessing.load()) {
+        return;
+    }
+
+    for (gpu::uint16 mipLevel = 0; mipLevel < mipCount; ++mipLevel) {
         Mip mip(mipLevel, this);
-
         mip.applySeams();
     }
 }
 
+void CubeMap::copyFace(int width, int height, const glm::vec4* source, int srcLineStride, glm::vec4* dest, int dstLineStride) {
+    for (int y = 0; y < height; y++) {
+        std::copy(source, source + width, dest);
+        source += srcLineStride;
+        dest += dstLineStride;
+    }
+}
+
 void CubeMap::reset(int width, int height, int mipCount) {
     assert(mipCount >0 && _width > 0 && _height > 0);
     _width = width;
@@ -301,29 +362,45 @@ void CubeMap::reset(int width, int height, int mipCount) {
 void CubeMap::copyTo(gpu::Texture* texture, const std::atomic<bool>& abortProcessing) const {
     assert(_width == texture->getWidth() && _height == texture->getHeight() && texture->getNumMips() == _mips.size());
 
-    // Convert all mip data back from float
-    unsigned char* convertedPixels = new unsigned char[_width * _height * sizeof(gpu::uint32)];
-    const auto textureFormat = texture->getTexelFormat();
+    struct CompressionpErrorHandler : public nvtt::ErrorHandler {
+        virtual void error(nvtt::Error e) override {
+            qCWarning(imagelogging) << "Texture compression error:" << nvtt::errorString(e);
+        }
+    };
 
-    for (gpu::uint16 mipLevel = 0; mipLevel < texture->getNumMips(); ++mipLevel) {
-        auto mipDims = texture->evalMipDimensions(mipLevel);
-        auto mipSize = texture->evalMipFaceSize(mipLevel);
-        auto srcLineStride = getFaceLineStride(mipLevel);
-        auto dstLineStride = (int)(sizeof(gpu::uint32)*mipDims.x);
+    CompressionpErrorHandler errorHandler;
+    nvtt::OutputOptions outputOptions;
+    outputOptions.setOutputHeader(false);
+    outputOptions.setErrorHandler(&errorHandler);
 
-        for (auto face = 0; face < 6; face++) {
-            auto srcPixels = getFace(mipLevel, face);
+    nvtt::Surface surface;
+    surface.setAlphaMode(nvtt::AlphaMode_None);
+    surface.setWrapMode(nvtt::WrapMode_Clamp);
 
-            convertFromFloat(convertedPixels, mipDims.x, mipDims.y, dstLineStride, textureFormat, srcPixels, srcLineStride);
-            texture->assignStoredMipFace(mipLevel, face, mipSize, convertedPixels);
-            if (abortProcessing.load()) {
-                delete[] convertedPixels;
-                return;
-            }
+    glm::vec4* packedPixels = new glm::vec4[_width * _height];
+    for (int face = 0; face < 6; face++) {
+        nvtt::CompressionOptions compressionOptions;
+        std::unique_ptr<nvtt::OutputHandler> outputHandler{ getNVTTCompressionOutputHandler(texture, face, compressionOptions) };
+
+        outputOptions.setOutputHandler(outputHandler.get());
+
+        SequentialTaskDispatcher dispatcher(abortProcessing);
+        nvtt::Context context;
+        context.setTaskDispatcher(&dispatcher);
+
+        for (gpu::uint16 mipLevel = 0; mipLevel < _mips.size() && !abortProcessing.load(); mipLevel++) {
+            auto mipDims = getMipDimensions(mipLevel);
+
+            copyFace(mipDims.x, mipDims.y, getFace(mipLevel, face), getFaceLineStride(mipLevel), packedPixels, mipDims.x);
+            surface.setImage(nvtt::InputFormat_RGBA_32F, mipDims.x, mipDims.y, 1, packedPixels);
+            context.compress(surface, face, mipLevel, compressionOptions, outputOptions);
+        }
+
+        if (abortProcessing.load()) {
+            break;
         }
     }
-
-    delete[] convertedPixels;
+    delete[] packedPixels;
 }
 
 void CubeMap::getFaceUV(const glm::vec3& dir, int* index, glm::vec2* uv) {
diff --git a/libraries/image/src/image/CubeMap.h b/libraries/image/src/image/CubeMap.h
index 0d926cdf44..17bc5642eb 100644
--- a/libraries/image/src/image/CubeMap.h
+++ b/libraries/image/src/image/CubeMap.h
@@ -18,13 +18,15 @@
 #include <array>
 #include <atomic>
 
+#include <QImage>
+
 namespace image {
 
     class CubeMap {
     public:
  
         CubeMap(int width, int height, int mipCount);
-        CubeMap(gpu::Texture* texture, const std::atomic<bool>& abortProcessing = false);
+        CubeMap(const std::vector<QImage>& faces, gpu::Element faceFormat, int mipCount, const std::atomic<bool>& abortProcessing = false);
 
         void reset(int width, int height, int mipCount);
         void copyTo(gpu::Texture* texture, const std::atomic<bool>& abortProcessing = false) const;
@@ -58,6 +60,7 @@ namespace image {
     private:
 
         struct GGXSamples;
+        struct MipMapOutputHandler;
         class Mip;
         class ConstMip;
 
@@ -70,6 +73,7 @@ namespace image {
 
         static void getFaceUV(const glm::vec3& dir, int* index, glm::vec2* uv);
         static void generateGGXSamples(GGXSamples& data, float roughness, const int resolution);
+        static void copyFace(int width, int height, const glm::vec4* source, int srcLineStride, glm::vec4* dest, int dstLineStride);
         void convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output, gpu::uint16 mipLevel, int face, const std::atomic<bool>& abortProcessing) const;
         glm::vec4 computeConvolution(const glm::vec3& normal, const GGXSamples& samples) const;
 
diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp
index 53a76e3b0f..8877176699 100644
--- a/libraries/image/src/image/Image.cpp
+++ b/libraries/image/src/image/Image.cpp
@@ -35,7 +35,6 @@
 using namespace gpu;
 
 #define CPU_MIPMAPS 1
-#include <nvtt/nvtt.h>
 
 #undef _CRT_SECURE_NO_WARNINGS
 #include <Etc2/Etc.h>
@@ -50,7 +49,7 @@ std::atomic<size_t> RECTIFIED_TEXTURE_COUNT{ 0 };
 
 // we use a ref here to work around static order initialization
 // possibly causing the element not to be constructed yet
-static const auto& HDR_FORMAT = gpu::Element::COLOR_R11G11B10;
+static const auto& GPUTEXTURE_HDRFORMAT = gpu::Element::COLOR_R11G11B10;
 const QImage::Format image::QIMAGE_HDRFORMAT = QImage::Format_RGB30;
 
 uint rectifyDimension(const uint& dimension) {
@@ -236,7 +235,7 @@ static std::function<uint32(const glm::vec3&)> getPackingFunction(const gpu::Ele
 }
 
 std::function<uint32(const glm::vec3&)> getHDRPackingFunction() {
-    return getPackingFunction(HDR_FORMAT);
+    return getPackingFunction(GPUTEXTURE_HDRFORMAT);
 }
 
 std::function<glm::vec3(gpu::uint32)> getUnpackingFunction(const gpu::Element& format) {
@@ -254,7 +253,7 @@ std::function<glm::vec3(gpu::uint32)> getUnpackingFunction(const gpu::Element& f
 }
 
 std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction() {
-    return getUnpackingFunction(HDR_FORMAT);
+    return getUnpackingFunction(GPUTEXTURE_HDRFORMAT);
 }
 
 QImage processRawImageData(QIODevice& content, const std::string& filename) {
@@ -504,22 +503,18 @@ struct MyErrorHandler : public nvtt::ErrorHandler {
     }
 };
 
-class SequentialTaskDispatcher : public nvtt::TaskDispatcher {
-public:
-    SequentialTaskDispatcher(const std::atomic<bool>& abortProcessing) : _abortProcessing(abortProcessing) {};
+SequentialTaskDispatcher::SequentialTaskDispatcher(const std::atomic<bool>& abortProcessing) : _abortProcessing(abortProcessing) {
+}
 
-    const std::atomic<bool>& _abortProcessing;
-
-    virtual void dispatch(nvtt::Task* task, void* context, int count) override {
-        for (int i = 0; i < count; i++) {
-            if (!_abortProcessing.load()) {
-                task(context, i);
-            } else {
-                break;
-            }
+void SequentialTaskDispatcher::dispatch(nvtt::Task* task, void* context, int count) {
+    for (int i = 0; i < count; i++) {
+        if (!_abortProcessing.load()) {
+            task(context, i);
+        } else {
+            break;
         }
     }
-};
+}
 
 void image::convertToFloat(const unsigned char* source, int width, int height, size_t srcLineByteStride, gpu::Element sourceFormat,
                            glm::vec4* output, size_t outputLinePixelStride) {
@@ -561,6 +556,40 @@ void image::convertFromFloat(unsigned char* output, int width, int height, size_
     }
 }
 
+nvtt::OutputHandler* getNVTTCompressionOutputHandler(gpu::Texture* outputTexture, int face, nvtt::CompressionOptions& compressionOptions) {
+    auto outputFormat = outputTexture->getStoredMipFormat();
+
+    nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F;
+    nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
+    nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
+
+    compressionOptions.setQuality(nvtt::Quality_Production);
+
+    // TODO: gles: generate ETC mips instead?
+    if (outputFormat == gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB) {
+        compressionOptions.setFormat(nvtt::Format_BC6);
+    } else if (outputFormat == gpu::Element::COLOR_RGB9E5) {
+        compressionOptions.setFormat(nvtt::Format_RGB);
+        compressionOptions.setPixelType(nvtt::PixelType_Float);
+        compressionOptions.setPixelFormat(32, 32, 32, 0);
+    } else if (outputFormat == gpu::Element::COLOR_R11G11B10) {
+        compressionOptions.setFormat(nvtt::Format_RGB);
+        compressionOptions.setPixelType(nvtt::PixelType_Float);
+        compressionOptions.setPixelFormat(32, 32, 32, 0);
+    } else {
+        qCWarning(imagelogging) << "Unknown mip format";
+        Q_UNREACHABLE();
+        return nullptr;
+    }
+
+    if (outputFormat == gpu::Element::COLOR_RGB9E5 || outputFormat == gpu::Element::COLOR_R11G11B10) {
+        // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats
+        return new PackedFloatOutputHandler(outputTexture, face, outputFormat);
+    } else {
+        return new OutputHandler(outputTexture, face);
+    }
+}
+
 void generateHDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target, 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
@@ -577,47 +606,23 @@ void generateHDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target
     nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
     nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
 
-    nvtt::CompressionOptions compressionOptions;
-    compressionOptions.setQuality(nvtt::Quality_Production);
-
-    // TODO: gles: generate ETC mips instead?
-    if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB) {
-        compressionOptions.setFormat(nvtt::Format_BC6);
-    } else if (mipFormat == gpu::Element::COLOR_RGB9E5) {
-        compressionOptions.setFormat(nvtt::Format_RGB);
-        compressionOptions.setPixelType(nvtt::PixelType_Float);
-        compressionOptions.setPixelFormat(32, 32, 32, 0);
-    } else if (mipFormat == gpu::Element::COLOR_R11G11B10) {
-        compressionOptions.setFormat(nvtt::Format_RGB);
-        compressionOptions.setPixelType(nvtt::PixelType_Float);
-        compressionOptions.setPixelFormat(32, 32, 32, 0);
-    } else {
-        qCWarning(imagelogging) << "Unknown mip format";
-        Q_UNREACHABLE();
-        return;
-    }
-
     data.resize(width * height);
-    convertToFloat(localCopy.bits(), width, height, localCopy.bytesPerLine(), HDR_FORMAT, data.data(), width);
+    convertToFloat(localCopy.bits(), width, height, localCopy.bytesPerLine(), GPUTEXTURE_HDRFORMAT, data.data(), width);
 
     // 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;
+
+    nvtt::CompressionOptions compressionOptions;
+    std::unique_ptr<nvtt::OutputHandler> outputHandler{ getNVTTCompressionOutputHandler(texture, face, compressionOptions) };
+
     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;
@@ -836,27 +841,27 @@ void generateLDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target
 
 #endif
 
-void generateMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic<bool>& abortProcessing = false, int face = -1, bool forceCPUBuild = false) {
-    if (forceCPUBuild || CPU_MIPMAPS) {
-        PROFILE_RANGE(resource_parse, "generateMips");
+void generateMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic<bool>& abortProcessing = false, int face = -1) {
+#if CPU_MIPMAPS
+    PROFILE_RANGE(resource_parse, "generateMips");
 
-        if (target == BackendTarget::GLES32) {
-            generateLDRMips(texture, std::move(image), target, abortProcessing, face);
-        } else {
-            if (image.format() == QIMAGE_HDRFORMAT) {
-                generateHDRMips(texture, std::move(image), target, abortProcessing, face);
-            } else {
-                generateLDRMips(texture, std::move(image), target, abortProcessing, face);
-            }
-        }
+    if (target == BackendTarget::GLES32) {
+        generateLDRMips(texture, std::move(image), target, abortProcessing, face);
     } else {
-        texture->setAutoGenerateMips(true);
+        if (image.format() == QIMAGE_HDRFORMAT) {
+            generateHDRMips(texture, std::move(image), target, abortProcessing, face);
+        } else {
+            generateLDRMips(texture, std::move(image), target, abortProcessing, face);
+        }
     }
+#else
+    texture->setAutoGenerateMips(true);
+#endif
 }
 
-void convolveForGGX(gpu::Texture* texture, BackendTarget target, const std::atomic<bool>& abortProcessing = false) {
+void convolveForGGX(const std::vector<QImage>& faces, gpu::Element faceFormat, gpu::Texture* texture, const std::atomic<bool>& abortProcessing = false) {
     PROFILE_RANGE(resource_parse, "convolveForGGX");
-    CubeMap source(texture, abortProcessing);
+    CubeMap source(faces, faceFormat, texture->getNumMips(), abortProcessing);
     CubeMap output(texture->getWidth(), texture->getHeight(), texture->getNumMips());
 
     source.convolveForGGX(output, abortProcessing);
@@ -1488,7 +1493,7 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI
     if (targetCubemapFormat == QIMAGE_HDRFORMAT && image.format() != targetCubemapFormat) {
         // If the target format is HDR but the image isn't, we need to convert the
         // image to HDR.
-        image = convertToHDRFormat(std::move(image), HDR_FORMAT);
+        image = convertToHDRFormat(std::move(image), GPUTEXTURE_HDRFORMAT);
     } else if (image.format() == QIMAGE_HDRFORMAT && image.format() != targetCubemapFormat) {
         // If the target format isn't HDR (such as on GLES) but the image is, we need to
         // convert the image to LDR
@@ -1504,7 +1509,7 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI
             formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB;
         }
     } else {
-        formatGPU = HDR_FORMAT;
+        formatGPU = GPUTEXTURE_HDRFORMAT;
     }
 
     formatMip = formatGPU;
@@ -1559,7 +1564,7 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI
             if (target == BackendTarget::GLES32) {
                 irradianceFormat = gpu::Element::COLOR_SRGBA_32;
             } else {
-                irradianceFormat = HDR_FORMAT;
+                irradianceFormat = GPUTEXTURE_HDRFORMAT;
             }
 
             auto irradianceTexture = gpu::Texture::createCube(irradianceFormat, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
@@ -1575,14 +1580,16 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI
             theTexture->overrideIrradiance(irradiance);
         }
 
-        for (uint8 face = 0; face < faces.size(); ++face) {
-            // Force building the mip maps right now on CPU if we are convolving for GGX later on
-            generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face, (options & CUBE_GGX_CONVOLVE) == CUBE_GGX_CONVOLVE);
+        if (options & CUBE_GGX_CONVOLVE) {
+            convolveForGGX(faces, GPUTEXTURE_HDRFORMAT, theTexture.get(), abortProcessing);
+        } else {
+            // Create mip maps and compress to final format in one go
+            for (uint8 face = 0; face < faces.size(); ++face) {
+                // Force building the mip maps right now on CPU if we are convolving for GGX later on
+                generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face);
+            }
         }
 
-        if (options & CUBE_GGX_CONVOLVE) {
-            convolveForGGX(theTexture.get(), target, abortProcessing);
-        }
     }
 
     return theTexture;
diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h
index 237dfcc6e7..e925718347 100644
--- a/libraries/image/src/image/Image.h
+++ b/libraries/image/src/image/Image.h
@@ -14,6 +14,7 @@
 
 #include <QVariant>
 #include <QImage>
+#include <nvtt/nvtt.h>
 
 #include <gpu/Texture.h>
 
@@ -107,6 +108,18 @@ gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::
                                  int maxNumPixels, TextureUsage::Type textureType,
                                  bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing = false);
 
+#if defined(NVTT_API)
+class SequentialTaskDispatcher : public nvtt::TaskDispatcher {
+public:
+    SequentialTaskDispatcher(const std::atomic<bool>& abortProcessing);
+
+    const std::atomic<bool>& _abortProcessing;
+
+    void dispatch(nvtt::Task* task, void* context, int count) override;
+};
+
+nvtt::OutputHandler* getNVTTCompressionOutputHandler(gpu::Texture* outputTexture, int face, nvtt::CompressionOptions& compressOptions);
+#endif
 } // namespace image
 
 #endif // hifi_image_Image_h

From 745d41e67997357158e6431236b0f4c0fb957d7c Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Mon, 1 Apr 2019 10:08:35 +0200
Subject: [PATCH 08/23] Renamed Image to TextureProcessing

---
 libraries/image/src/image/{Image.cpp => TextureProcessing.cpp} | 0
 libraries/image/src/image/{Image.h => TextureProcessing.h}     | 0
 2 files changed, 0 insertions(+), 0 deletions(-)
 rename libraries/image/src/image/{Image.cpp => TextureProcessing.cpp} (100%)
 rename libraries/image/src/image/{Image.h => TextureProcessing.h} (100%)

diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/TextureProcessing.cpp
similarity index 100%
rename from libraries/image/src/image/Image.cpp
rename to libraries/image/src/image/TextureProcessing.cpp
diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/TextureProcessing.h
similarity index 100%
rename from libraries/image/src/image/Image.h
rename to libraries/image/src/image/TextureProcessing.h

From 7aaf3da11e19d6e002794407048b57e4b544e73d Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Mon, 1 Apr 2019 10:44:33 +0200
Subject: [PATCH 09/23] Before merge with HDR

---
 libraries/image/src/image/Image.cpp           |   78 +
 libraries/image/src/image/Image.h             |   98 +
 .../image/src/image/TextureProcessing.cpp     | 1598 -----------------
 libraries/image/src/image/TextureProcessing.h |  125 --
 4 files changed, 176 insertions(+), 1723 deletions(-)
 create mode 100644 libraries/image/src/image/Image.cpp
 create mode 100644 libraries/image/src/image/Image.h
 delete mode 100644 libraries/image/src/image/TextureProcessing.cpp
 delete mode 100644 libraries/image/src/image/TextureProcessing.h

diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp
new file mode 100644
index 0000000000..25e9ac3f59
--- /dev/null
+++ b/libraries/image/src/image/Image.cpp
@@ -0,0 +1,78 @@
+#include "Image.h"
+#include "ImageLogging.h"
+#include "TextureProcessing.h"
+
+#include <nvtt/nvtt.h>
+
+using namespace image;
+
+Image Image::getScaled(glm::uvec2 dstSize, AspectRatioMode ratioMode, TransformationMode transformMode) const {
+    if (_data.format() == Image::Format_PACKED_FLOAT) {
+        // Start by converting to full float
+        glm::vec4* floatPixels = new glm::vec4[getWidth()*getHeight()];
+        auto unpackFunc = getHDRUnpackingFunction();
+        auto floatDataIt = floatPixels;
+        for (auto lineNb = 0; lineNb < getHeight(); lineNb++) {
+            const glm::uint32* srcPixelIt = reinterpret_cast<const glm::uint32*>(getScanLine(lineNb));
+            const glm::uint32* srcPixelEnd = srcPixelIt + getWidth();
+
+            while (srcPixelIt < srcPixelEnd) {
+                *floatDataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f);
+                ++srcPixelIt;
+                ++floatDataIt;
+            }
+        }
+
+        // Perform filtered resize with NVTT
+        static_assert(sizeof(glm::vec4) == 4 * sizeof(float), "Assuming glm::vec4 holds 4 floats");
+        nvtt::Surface surface;
+        surface.setImage(nvtt::InputFormat_RGBA_32F, getWidth(), getHeight(), 1, floatPixels);
+        delete[] floatPixels;
+
+        nvtt::ResizeFilter filter = nvtt::ResizeFilter_Kaiser;
+        if (transformMode == Qt::TransformationMode::FastTransformation) {
+            filter = nvtt::ResizeFilter_Box;
+        }
+        surface.resize(dstSize.x, dstSize.y, 1, nvtt::ResizeFilter_Box);
+
+        // And convert back to original format
+        QImage resizedImage((int)dstSize.x, (int)dstSize.y, (QImage::Format)Image::Format_PACKED_FLOAT);
+
+        auto packFunc = getHDRPackingFunction();
+        auto srcRedIt = reinterpret_cast<const float*>(surface.channel(0));
+        auto srcGreenIt = reinterpret_cast<const float*>(surface.channel(1));
+        auto srcBlueIt = reinterpret_cast<const float*>(surface.channel(2));
+        for (auto lineNb = 0; lineNb < dstSize.y; lineNb++) {
+            glm::uint32* dstPixelIt = reinterpret_cast<glm::uint32*>(resizedImage.scanLine(lineNb));
+            glm::uint32* dstPixelEnd = dstPixelIt + dstSize.x;
+
+            while (dstPixelIt < dstPixelEnd) {
+                *dstPixelIt = packFunc(glm::vec3(*srcRedIt, *srcGreenIt, *srcBlueIt));
+                ++srcRedIt;
+                ++srcGreenIt;
+                ++srcBlueIt;
+                ++dstPixelIt;
+            }
+        }
+        return resizedImage;
+    } else {
+        return _data.scaled(fromGlm(dstSize), ratioMode, transformMode);
+    }
+}
+
+Image Image::getConvertedToFormat(Format newFormat) const {
+    assert(getFormat() != Format_PACKED_FLOAT);
+    return _data.convertToFormat((QImage::Format)newFormat);
+}
+
+void Image::invertPixels() {
+    _data.invertPixels(QImage::InvertRgba);
+}
+
+Image Image::getSubImage(QRect rect) const {
+    return _data.copy(rect);
+}
+
+Image Image::getMirrored(bool horizontal, bool vertical) const {
+    return _data.mirrored(horizontal, vertical);
+}
diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h
new file mode 100644
index 0000000000..bfecf4f2a1
--- /dev/null
+++ b/libraries/image/src/image/Image.h
@@ -0,0 +1,98 @@
+#pragma once
+//
+//  Image.h
+//  image/src/Image
+//
+//  Created by Olivier Prat on 29/3/2019.
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_image_Image_h
+#define hifi_image_Image_h
+
+#include <QImage>
+
+#include "ColorChannel.h"
+
+#include <glm/fwd.hpp>
+#include <glm/vec2.hpp>
+#include <GLMHelpers.h>
+
+namespace image {
+
+    class Image {
+    public:
+
+        enum Format {
+            Format_Invalid = QImage::Format_Invalid,
+            Format_Mono = QImage::Format_Mono,
+            Format_MonoLSB = QImage::Format_MonoLSB,
+            Format_Indexed8 = QImage::Format_Indexed8,
+            Format_RGB32 = QImage::Format_RGB32,
+            Format_ARGB32 = QImage::Format_ARGB32,
+            Format_ARGB32_Premultiplied = QImage::Format_ARGB32_Premultiplied,
+            Format_RGB16 = QImage::Format_RGB16,
+            Format_ARGB8565_Premultiplied = QImage::Format_ARGB8565_Premultiplied,
+            Format_RGB666 = QImage::Format_RGB666,
+            Format_ARGB6666_Premultiplied = QImage::Format_ARGB6666_Premultiplied,
+            Format_RGB555 = QImage::Format_RGB555,
+            Format_ARGB8555_Premultiplied = QImage::Format_ARGB8555_Premultiplied,
+            Format_RGB888 = QImage::Format_RGB888,
+            Format_RGB444 = QImage::Format_RGB444,
+            Format_ARGB4444_Premultiplied = QImage::Format_ARGB4444_Premultiplied,
+            Format_RGBX8888 = QImage::Format_RGBX8888,
+            Format_RGBA8888 = QImage::Format_RGBA8888,
+            Format_RGBA8888_Premultiplied = QImage::Format_RGBA8888_Premultiplied,
+            Format_Grayscale8 = QImage::Format_Grayscale8,
+            Format_R11G11B10F = QImage::Format_RGB30,
+            Format_PACKED_FLOAT = Format_R11G11B10F
+        };
+
+        using AspectRatioMode = Qt::AspectRatioMode;
+        using TransformationMode = Qt::TransformationMode;
+
+        Image() {}
+        Image(int width, int height, Format format) : _data(width, height, (QImage::Format)format) {}
+        Image(const QImage& data) : _data(data) {}
+        void operator=(const QImage& image) {
+            _data = image;
+        }
+
+        bool isNull() const { return _data.isNull(); }
+
+        Format getFormat() const { return (Format)_data.format(); }
+        bool hasAlphaChannel() const { return _data.hasAlphaChannel(); }
+
+        glm::uint32 getWidth() const { return (glm::uint32)_data.width(); }
+        glm::uint32 getHeight() const { return (glm::uint32)_data.height(); }
+        glm::uvec2 getSize() const { return toGlm(_data.size()); }
+        size_t getByteCount() const { return _data.byteCount(); }
+
+        QRgb getPixel(int x, int y) const { return _data.pixel(x, y); }
+        void setPixel(int x, int y, QRgb value) {
+            _data.setPixel(x, y, value);
+        }
+
+        glm::uint8* editScanLine(int y) { return _data.scanLine(y); }
+        const glm::uint8* getScanLine(int y) const { return _data.scanLine(y); }
+        const glm::uint8* getBits() const { return _data.constBits(); }
+
+        Image getScaled(glm::uvec2 newSize, AspectRatioMode ratioMode, TransformationMode transformationMode = Qt::SmoothTransformation) const;
+        Image getConvertedToFormat(Format newFormat) const;
+        Image getSubImage(QRect rect) const;
+        Image getMirrored(bool horizontal, bool vertical) const;
+
+        // Inplace transformations
+        void invertPixels();
+
+    private:
+
+        QImage _data;
+    };
+
+} // namespace image
+
+#endif // hifi_image_Image_h
diff --git a/libraries/image/src/image/TextureProcessing.cpp b/libraries/image/src/image/TextureProcessing.cpp
deleted file mode 100644
index 8877176699..0000000000
--- a/libraries/image/src/image/TextureProcessing.cpp
+++ /dev/null
@@ -1,1598 +0,0 @@
-//
-//  Image.cpp
-//  image/src/image
-//
-//  Created by Clement Brisset on 4/5/2017.
-//  Copyright 2017 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-#include "Image.h"
-
-#include <glm/gtc/packing.hpp>
-
-#include <QtCore/QtGlobal>
-#include <QUrl>
-#include <QImage>
-#include <QRgb>
-#include <QBuffer>
-#include <QImageReader>
-
-#include <Finally.h>
-#include <Profile.h>
-#include <StatTracker.h>
-#include <GLMHelpers.h>
-
-#include "TGAReader.h"
-#if !defined(Q_OS_ANDROID)
-#include "OpenEXRReader.h"
-#endif
-#include "ImageLogging.h"
-#include "CubeMap.h"
-
-using namespace gpu;
-
-#define CPU_MIPMAPS 1
-
-#undef _CRT_SECURE_NO_WARNINGS
-#include <Etc2/Etc.h>
-#include <Etc2/EtcFilter.h>
-
-static const glm::uvec2 SPARSE_PAGE_SIZE(128);
-static const glm::uvec2 MAX_TEXTURE_SIZE_GLES(2048);
-static const glm::uvec2 MAX_TEXTURE_SIZE_GL(4096);
-bool DEV_DECIMATE_TEXTURES = false;
-std::atomic<size_t> DECIMATED_TEXTURE_COUNT{ 0 };
-std::atomic<size_t> RECTIFIED_TEXTURE_COUNT{ 0 };
-
-// we use a ref here to work around static order initialization
-// possibly causing the element not to be constructed yet
-static const auto& GPUTEXTURE_HDRFORMAT = gpu::Element::COLOR_R11G11B10;
-const QImage::Format image::QIMAGE_HDRFORMAT = QImage::Format_RGB30;
-
-uint rectifyDimension(const uint& dimension) {
-    if (dimension == 0) {
-        return 0;
-    }
-    if (dimension < SPARSE_PAGE_SIZE.x) {
-        uint newSize = SPARSE_PAGE_SIZE.x;
-        while (dimension <= newSize / 2) {
-            newSize /= 2;
-        }
-        return newSize;
-    } else {
-        uint pages = (dimension / SPARSE_PAGE_SIZE.x) + (dimension % SPARSE_PAGE_SIZE.x == 0 ? 0 : 1);
-        return pages * SPARSE_PAGE_SIZE.x;
-    }
-}
-
-glm::uvec2 rectifySize(const glm::uvec2& size) {
-    return { rectifyDimension(size.x), rectifyDimension(size.y) };
-}
-
-
-namespace image {
-
-const QStringList getSupportedFormats() {
-    auto formats = QImageReader::supportedImageFormats();
-    QStringList stringFormats;
-    std::transform(formats.begin(), formats.end(), std::back_inserter(stringFormats),
-                   [](QByteArray& format) -> QString { return format; });
-    return stringFormats;
-}
-
-
-// On GLES, we don't use HDR skyboxes
-QImage::Format cubeMapFormatForTarget(BackendTarget target) {
-    if (target == BackendTarget::GLES32) {
-        return QImage::Format_RGB32;
-    }
-    return QIMAGE_HDRFORMAT;
-}
-
-TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) {
-    switch (type) {
-        case ALBEDO_TEXTURE:
-            return image::TextureUsage::createAlbedoTextureFromImage;
-        case EMISSIVE_TEXTURE:
-            return image::TextureUsage::createEmissiveTextureFromImage;
-        case LIGHTMAP_TEXTURE:
-            return image::TextureUsage::createLightmapTextureFromImage;
-        case SKY_TEXTURE:
-            return image::TextureUsage::createCubeTextureFromImage;
-        case AMBIENT_TEXTURE:
-            if (options.value("generateIrradiance", true).toBool()) {
-                return image::TextureUsage::createAmbientCubeTextureAndIrradianceFromImage;
-            } else {
-                return image::TextureUsage::createAmbientCubeTextureFromImage;
-            }
-        case BUMP_TEXTURE:
-            return image::TextureUsage::createNormalTextureFromBumpImage;
-        case NORMAL_TEXTURE:
-            return image::TextureUsage::createNormalTextureFromNormalImage;
-        case ROUGHNESS_TEXTURE:
-            return image::TextureUsage::createRoughnessTextureFromImage;
-        case GLOSS_TEXTURE:
-            return image::TextureUsage::createRoughnessTextureFromGlossImage;
-        case SPECULAR_TEXTURE:
-            return image::TextureUsage::createMetallicTextureFromImage;
-        case STRICT_TEXTURE:
-            return image::TextureUsage::createStrict2DTextureFromImage;
-
-        case DEFAULT_TEXTURE:
-        default:
-            return image::TextureUsage::create2DTextureFromImage;
-    }
-}
-
-gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
-                                                                 bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing);
-}
-
-gpu::TexturePointer TextureUsage::create2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
-                                                           bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing);
-}
-
-gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
-                                                               bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing);
-}
-
-gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
-                                                                 bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing);
-}
-
-gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
-                                                                 bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing);
-}
-
-gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(QImage&& srcImage, const std::string& srcImageName,
-                                                                     bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing);
-}
-
-gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(QImage&& srcImage, const std::string& srcImageName,
-                                                                   bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing);
-}
-
-gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
-                                                                  bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing);
-}
-
-gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(QImage&& srcImage, const std::string& srcImageName,
-                                                                       bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing);
-}
-
-gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
-                                                                 bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing);
-}
-
-gpu::TexturePointer TextureUsage::createCubeTextureAndIrradianceFromImage(QImage&& srcImage, const std::string& srcImageName,
-                                                             bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, CUBE_GENERATE_IRRADIANCE, abortProcessing);
-}
-
-gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName,
-                                                                              bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, CUBE_DEFAULT, abortProcessing);
-}
-
-gpu::TexturePointer TextureUsage::createAmbientCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
-                                                           bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return processCubeTextureColorFromImage(std::move(image), srcImageName, compress, target, CUBE_GGX_CONVOLVE, abortProcessing);
-}
-
-gpu::TexturePointer TextureUsage::createAmbientCubeTextureAndIrradianceFromImage(QImage&& image, const std::string& srcImageName,
-                                                                        bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return processCubeTextureColorFromImage(std::move(image), srcImageName, compress, target, CUBE_GENERATE_IRRADIANCE | CUBE_GGX_CONVOLVE, abortProcessing);
-}
-
-static float denormalize(float value, const float minValue) {
-    return value < minValue ? 0.0f : value;
-}
-
-static 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;
-    static const auto maxValue = 6.50e4f;
-    glm::vec3 ucolor;
-    ucolor.r = denormalize(color.r, minValue);
-    ucolor.g = denormalize(color.g, minValue);
-    ucolor.b = denormalize(color.b, minValue);
-    ucolor.r = std::min(ucolor.r, maxValue);
-    ucolor.g = std::min(ucolor.g, maxValue);
-    ucolor.b = std::min(ucolor.b, maxValue);
-    return glm::packF2x11_1x10(ucolor);
-}
-
-static uint32 packUnorm4x8(const glm::vec3& color) {
-    return glm::packUnorm4x8(glm::vec4(color, 1.0f));
-}
-
-static std::function<uint32(const glm::vec3&)> getPackingFunction(const gpu::Element& format) {
-    if (format == gpu::Element::COLOR_RGB9E5) {
-        return glm::packF3x9_E1x5;
-    } else if (format == gpu::Element::COLOR_R11G11B10) {
-        return packR11G11B10F;
-    } else if (format == gpu::Element::COLOR_RGBA_32 || format == gpu::Element::COLOR_SRGBA_32 || format == gpu::Element::COLOR_BGRA_32 || format == gpu::Element::COLOR_SBGRA_32) {
-        return packUnorm4x8;
-    } else {
-        qCWarning(imagelogging) << "Unknown handler format";
-        Q_UNREACHABLE();
-        return nullptr;
-    }
-}
-
-std::function<uint32(const glm::vec3&)> getHDRPackingFunction() {
-    return getPackingFunction(GPUTEXTURE_HDRFORMAT);
-}
-
-std::function<glm::vec3(gpu::uint32)> getUnpackingFunction(const gpu::Element& format) {
-    if (format == gpu::Element::COLOR_RGB9E5) {
-        return glm::unpackF3x9_E1x5;
-    } else if (format == gpu::Element::COLOR_R11G11B10) {
-        return glm::unpackF2x11_1x10;
-    } else if (format == gpu::Element::COLOR_RGBA_32 || format == gpu::Element::COLOR_SRGBA_32 || format == gpu::Element::COLOR_BGRA_32 || format == gpu::Element::COLOR_SBGRA_32) {
-        return glm::unpackUnorm4x8;
-    } else {
-        qCWarning(imagelogging) << "Unknown handler format";
-        Q_UNREACHABLE();
-        return nullptr;
-    }
-}
-
-std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction() {
-    return getUnpackingFunction(GPUTEXTURE_HDRFORMAT);
-}
-
-QImage processRawImageData(QIODevice& content, const std::string& filename) {
-    // 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);
-    // Remove possible query part of the filename if it comes from an URL
-    filenameExtension = filenameExtension.substr(0, filenameExtension.find_first_of('?'));
-    if (!content.isReadable()) {
-        content.open(QIODevice::ReadOnly);
-    } else {
-        content.reset();
-    }
-
-    if (filenameExtension == "tga") {
-        QImage image = image::readTGA(content);
-        if (!image.isNull()) {
-            return image;
-        }
-        content.reset();
-    } 
-#if !defined(Q_OS_ANDROID)
-    else if (filenameExtension == "exr") {
-        QImage image = image::readOpenEXR(content, filename);
-        if (!image.isNull()) {
-            return image;
-        }
-    }
-#endif
-
-    QImageReader imageReader(&content, filenameExtension.c_str());
-
-    if (imageReader.canRead()) {
-        return imageReader.read();
-    } else {
-        // Extension could be incorrect, try to detect the format from the content
-        QImageReader newImageReader;
-        newImageReader.setDecideFormatFromContent(true);
-        content.reset();
-        newImageReader.setDevice(&content);
-
-        if (newImageReader.canRead()) {
-            return newImageReader.read();
-        }
-    }
-
-    return QImage();
-}
-
-void mapToRedChannel(QImage& image, ColorChannel sourceChannel) {
-    // Change format of image so we know exactly how to process it
-    if (image.format() != QImage::Format_ARGB32) {
-        image = image.convertToFormat(QImage::Format_ARGB32);
-    }
-
-    for (int i = 0; i < image.height(); i++) {
-        QRgb* pixel = reinterpret_cast<QRgb*>(image.scanLine(i));
-        // Past end pointer
-        QRgb* lineEnd = pixel + image.width();
-
-        // Transfer channel data from source to target
-        for (; pixel < lineEnd; pixel++) {
-            int colorValue;
-            switch (sourceChannel) {
-            case ColorChannel::RED:
-                colorValue = qRed(*pixel);
-                break;
-            case ColorChannel::GREEN:
-                colorValue = qGreen(*pixel);
-                break;
-            case ColorChannel::BLUE:
-                colorValue = qBlue(*pixel);
-                break;
-            case ColorChannel::ALPHA:
-                colorValue = qAlpha(*pixel);
-                break;
-            default:
-                colorValue = qRed(*pixel);
-                break;
-            }
-
-            // Dump the color in the red channel, ignore the rest
-            *pixel = qRgba(colorValue, 0, 0, 255);
-        }
-    }
-}
-
-gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& filename, ColorChannel sourceChannel,
-                                 int maxNumPixels, TextureUsage::Type textureType,
-                                 bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-
-    QImage image = processRawImageData(*content.get(), filename);
-    // Texture content can take up a lot of memory. Here we release our ownership of that content
-    // in case it can be released.
-    content.reset();
-
-    int imageWidth = image.width();
-    int imageHeight = image.height();
-
-    // Validate that the image loaded
-    if (imageWidth == 0 || imageHeight == 0 || image.format() == QImage::Format_Invalid) {
-        QString reason(image.format() == QImage::Format_Invalid ? "(Invalid Format)" : "(Size is invalid)");
-        qCWarning(imagelogging) << "Failed to load:" << qPrintable(reason);
-        return nullptr;
-    }
-
-    // Validate the image is less than _maxNumPixels, and downscale if necessary
-    if (imageWidth * imageHeight > maxNumPixels) {
-        float scaleFactor = sqrtf(maxNumPixels / (float)(imageWidth * imageHeight));
-        int originalWidth = imageWidth;
-        int originalHeight = imageHeight;
-        imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f);
-        imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f);
-        image = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
-        qCDebug(imagelogging).nospace() << "Downscaled " << " (" <<
-            QSize(originalWidth, originalHeight) << " to " <<
-            QSize(imageWidth, imageHeight) << ")";
-    }
-
-    // Re-map to image with single red channel texture if requested
-    if (sourceChannel != ColorChannel::NONE) {
-        mapToRedChannel(image, sourceChannel);
-    }
-    
-    auto loader = TextureUsage::getTextureLoaderForType(textureType);
-    auto texture = loader(std::move(image), filename, compress, target, abortProcessing);
-
-    return texture;
-}
-
-QImage processSourceImage(QImage&& srcImage, bool cubemap, BackendTarget target) {
-    PROFILE_RANGE(resource_parse, "processSourceImage");
-
-    // 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;
-
-    const auto maxTextureSize = target == BackendTarget::GLES32 ? MAX_TEXTURE_SIZE_GLES : MAX_TEXTURE_SIZE_GL;
-    while (glm::any(glm::greaterThan(targetSize, maxTextureSize))) {
-        targetSize /= 2;
-    }
-    if (targetSize != srcImageSize) {
-        ++DECIMATED_TEXTURE_COUNT;
-    }
-
-    if (!cubemap) {
-        auto rectifiedSize = rectifySize(targetSize);
-        if (rectifiedSize != targetSize) {
-            ++RECTIFIED_TEXTURE_COUNT;
-            targetSize = rectifiedSize;
-        }
-    }
-
-    if (DEV_DECIMATE_TEXTURES && glm::all(glm::greaterThanEqual(targetSize / SPARSE_PAGE_SIZE, glm::uvec2(2)))) {
-        targetSize /= 2;
-    }
-
-    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 localCopy.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
-    }
-
-    return localCopy;
-}
-
-#if defined(NVTT_API)
-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;
-        _miplevel = miplevel;
-
-        _data = static_cast<gpu::Byte*>(malloc(size));
-        _current = _data;
-    }
-
-    virtual bool writeData(const void* data, int size) override {
-        assert(_current + size <= _data + _size);
-        memcpy(_current, data, size);
-        _current += size;
-        return true;
-    }
-
-    virtual void endImage() override {
-        if (_face >= 0) {
-            _texture->assignStoredMipFace(_miplevel, _face, _size, static_cast<const gpu::Byte*>(_data));
-        } else {
-            _texture->assignStoredMip(_miplevel, _size, static_cast<const gpu::Byte*>(_data));
-        }
-        free(_data);
-        _data = nullptr;
-    }
-
-    gpu::Byte* _data{ nullptr };
-    gpu::Byte* _current{ nullptr };
-    gpu::Texture* _texture{ nullptr };
-    int _miplevel = 0;
-    int _size = 0;
-    int _face = -1;
-};
-
-struct PackedFloatOutputHandler : public OutputHandler {
-    PackedFloatOutputHandler(gpu::Texture* texture, int face, gpu::Element format) : OutputHandler(texture, face) {
-        _packFunc = getPackingFunction(format);
-    }
-
-    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<uint32(const glm::vec3&)> _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);
-    }
-};
-
-SequentialTaskDispatcher::SequentialTaskDispatcher(const std::atomic<bool>& abortProcessing) : _abortProcessing(abortProcessing) {
-}
-
-void SequentialTaskDispatcher::dispatch(nvtt::Task* task, void* context, int count) {
-    for (int i = 0; i < count; i++) {
-        if (!_abortProcessing.load()) {
-            task(context, i);
-        } else {
-            break;
-        }
-    }
-}
-
-void image::convertToFloat(const unsigned char* source, int width, int height, size_t srcLineByteStride, gpu::Element sourceFormat,
-                           glm::vec4* output, size_t outputLinePixelStride) {
-    glm::vec4* outputIt;
-    auto unpackFunc = getUnpackingFunction(sourceFormat);
-
-    outputLinePixelStride -= width;
-    outputIt = output;
-    for (auto lineNb = 0; lineNb < height; lineNb++) {
-        const uint32* srcPixelIt = reinterpret_cast<const uint32*>(source + lineNb * srcLineByteStride);
-        const uint32* srcPixelEnd = srcPixelIt + width;
-
-        while (srcPixelIt < srcPixelEnd) {
-            *outputIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f);
-            ++srcPixelIt;
-            ++outputIt;
-        }
-        outputIt += outputLinePixelStride;
-    }
-}
-
-void image::convertFromFloat(unsigned char* output, int width, int height, size_t outputLineByteStride, gpu::Element outputFormat,
-                             const glm::vec4* source, size_t srcLinePixelStride) {
-    const glm::vec4* sourceIt;
-    auto packFunc = getPackingFunction(outputFormat);
-
-    srcLinePixelStride -= width;
-    sourceIt = source;
-    for (auto lineNb = 0; lineNb < height; lineNb++) {
-        uint32* outPixelIt = reinterpret_cast<uint32*>(output + lineNb * outputLineByteStride);
-        uint32* outPixelEnd = outPixelIt + width;
-
-        while (outPixelIt < outPixelEnd) {
-            *outPixelIt = packFunc(*sourceIt);
-            ++outPixelIt;
-            ++sourceIt;
-        }
-        sourceIt += srcLinePixelStride;
-    }
-}
-
-nvtt::OutputHandler* getNVTTCompressionOutputHandler(gpu::Texture* outputTexture, int face, nvtt::CompressionOptions& compressionOptions) {
-    auto outputFormat = outputTexture->getStoredMipFormat();
-
-    nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F;
-    nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
-    nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
-
-    compressionOptions.setQuality(nvtt::Quality_Production);
-
-    // TODO: gles: generate ETC mips instead?
-    if (outputFormat == gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB) {
-        compressionOptions.setFormat(nvtt::Format_BC6);
-    } else if (outputFormat == gpu::Element::COLOR_RGB9E5) {
-        compressionOptions.setFormat(nvtt::Format_RGB);
-        compressionOptions.setPixelType(nvtt::PixelType_Float);
-        compressionOptions.setPixelFormat(32, 32, 32, 0);
-    } else if (outputFormat == gpu::Element::COLOR_R11G11B10) {
-        compressionOptions.setFormat(nvtt::Format_RGB);
-        compressionOptions.setPixelType(nvtt::PixelType_Float);
-        compressionOptions.setPixelFormat(32, 32, 32, 0);
-    } else {
-        qCWarning(imagelogging) << "Unknown mip format";
-        Q_UNREACHABLE();
-        return nullptr;
-    }
-
-    if (outputFormat == gpu::Element::COLOR_RGB9E5 || outputFormat == gpu::Element::COLOR_R11G11B10) {
-        // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats
-        return new PackedFloatOutputHandler(outputTexture, face, outputFormat);
-    } else {
-        return new OutputHandler(outputTexture, face);
-    }
-}
-
-void generateHDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target, 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);
-
-    assert(localCopy.format() == cubeMapFormatForTarget(target));
-
-    const int width = localCopy.width(), height = localCopy.height();
-    std::vector<glm::vec4> data;
-    std::vector<glm::vec4>::iterator dataIt;
-    auto mipFormat = texture->getStoredMipFormat();
-
-    nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F;
-    nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
-    nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
-
-    data.resize(width * height);
-    convertToFloat(localCopy.bits(), width, height, localCopy.bytesPerLine(), GPUTEXTURE_HDRFORMAT, data.data(), width);
-
-    // 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);
-
-    nvtt::CompressionOptions compressionOptions;
-    std::unique_ptr<nvtt::OutputHandler> outputHandler{ getNVTTCompressionOutputHandler(texture, face, compressionOptions) };
-
-    MyErrorHandler errorHandler;
-    outputOptions.setErrorHandler(&errorHandler);
-    nvtt::Context context;
-    int mipLevel = 0;
-
-    outputOptions.setOutputHandler(outputHandler.get());
-
-    nvtt::Surface surface;
-    surface.setImage(inputFormat, width, height, 1, &(*data.begin()));
-    surface.setAlphaMode(alphaMode);
-    surface.setWrapMode(wrapMode);
-
-    SequentialTaskDispatcher dispatcher(abortProcessing);
-    nvtt::Compressor compressor;
-    context.setTaskDispatcher(&dispatcher);
-
-    context.compress(surface, face, mipLevel++, compressionOptions, outputOptions);
-    while (surface.canMakeNextMipmap() && !abortProcessing.load()) {
-        surface.buildNextMipmap(nvtt::MipmapFilter_Box);
-        context.compress(surface, face, mipLevel++, compressionOptions, outputOptions);
-    }
-}
-
-void generateLDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target, 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.format() != cubeMapFormatForTarget(target)) {
-        localCopy = localCopy.convertToFormat(QImage::Format_ARGB32);
-    }
-
-    const int width = localCopy.width(), height = localCopy.height();
-    auto mipFormat = texture->getStoredMipFormat();
-
-    if (target != BackendTarget::GLES32) {
-        const void* data = static_cast<const void*>(localCopy.constBits());
-        nvtt::TextureType textureType = nvtt::TextureType_2D;
-        nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB;
-        nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
-        nvtt::RoundMode roundMode = nvtt::RoundMode_None;
-        nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
-
-        float inputGamma = 2.2f;
-        float outputGamma = 2.2f;
-
-        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);
-        inputOptions.setAlphaMode(alphaMode);
-        inputOptions.setWrapMode(wrapMode);
-        inputOptions.setRoundMode(roundMode);
-
-        inputOptions.setMipmapGeneration(true);
-        inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box);
-
-        nvtt::CompressionOptions compressionOptions;
-        compressionOptions.setQuality(nvtt::Quality_Production);
-
-        if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) {
-            compressionOptions.setFormat(nvtt::Format_BC1);
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) {
-            alphaMode = nvtt::AlphaMode_Transparency;
-            compressionOptions.setFormat(nvtt::Format_BC1a);
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA) {
-            alphaMode = nvtt::AlphaMode_Transparency;
-            compressionOptions.setFormat(nvtt::Format_BC3);
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_RED) {
-            compressionOptions.setFormat(nvtt::Format_BC4);
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_XY) {
-            compressionOptions.setFormat(nvtt::Format_BC5);
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH) {
-            alphaMode = nvtt::AlphaMode_Transparency;
-            compressionOptions.setFormat(nvtt::Format_BC7);
-        } else if (mipFormat == gpu::Element::COLOR_RGBA_32) {
-            compressionOptions.setFormat(nvtt::Format_RGBA);
-            compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
-            compressionOptions.setPitchAlignment(4);
-            compressionOptions.setPixelFormat(32,
-                                              0x000000FF,
-                                              0x0000FF00,
-                                              0x00FF0000,
-                                              0xFF000000);
-            inputGamma = 1.0f;
-            outputGamma = 1.0f;
-        } else if (mipFormat == gpu::Element::COLOR_BGRA_32) {
-            compressionOptions.setFormat(nvtt::Format_RGBA);
-            compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
-            compressionOptions.setPitchAlignment(4);
-            compressionOptions.setPixelFormat(32,
-                                              0x00FF0000,
-                                              0x0000FF00,
-                                              0x000000FF,
-                                              0xFF000000);
-            inputGamma = 1.0f;
-            outputGamma = 1.0f;
-        } else if (mipFormat == gpu::Element::COLOR_SRGBA_32) {
-            compressionOptions.setFormat(nvtt::Format_RGBA);
-            compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
-            compressionOptions.setPitchAlignment(4);
-            compressionOptions.setPixelFormat(32,
-                                              0x000000FF,
-                                              0x0000FF00,
-                                              0x00FF0000,
-                                              0xFF000000);
-        } else if (mipFormat == gpu::Element::COLOR_SBGRA_32) {
-            compressionOptions.setFormat(nvtt::Format_RGBA);
-            compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
-            compressionOptions.setPitchAlignment(4);
-            compressionOptions.setPixelFormat(32,
-                                              0x00FF0000,
-                                              0x0000FF00,
-                                              0x000000FF,
-                                              0xFF000000);
-        } else if (mipFormat == gpu::Element::COLOR_R_8) {
-            compressionOptions.setFormat(nvtt::Format_RGB);
-            compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
-            compressionOptions.setPitchAlignment(4);
-            compressionOptions.setPixelFormat(8, 0, 0, 0);
-        } else if (mipFormat == gpu::Element::VEC2NU8_XY) {
-            inputOptions.setNormalMap(true);
-            compressionOptions.setFormat(nvtt::Format_RGBA);
-            compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
-            compressionOptions.setPitchAlignment(4);
-            compressionOptions.setPixelFormat(8, 8, 0, 0);
-        } else {
-            qCWarning(imagelogging) << "Unknown mip format";
-            Q_UNREACHABLE();
-            return;
-        }
-
-        nvtt::OutputOptions outputOptions;
-        outputOptions.setOutputHeader(false);
-        OutputHandler outputHandler(texture, face);
-        outputOptions.setOutputHandler(&outputHandler);
-        MyErrorHandler errorHandler;
-        outputOptions.setErrorHandler(&errorHandler);
-
-        SequentialTaskDispatcher dispatcher(abortProcessing);
-        nvtt::Compressor compressor;
-        compressor.setTaskDispatcher(&dispatcher);
-        compressor.process(inputOptions, compressionOptions, outputOptions);
-    } else {
-        int numMips = 1 + (int)log2(std::max(width, height));
-        Etc::RawImage *mipMaps = new Etc::RawImage[numMips];
-        Etc::Image::Format etcFormat = Etc::Image::Format::DEFAULT;
-
-        if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB) {
-            etcFormat = Etc::Image::Format::RGB8;
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB) {
-            etcFormat = Etc::Image::Format::SRGB8;
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA) {
-            etcFormat = Etc::Image::Format::RGB8A1;
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA) {
-            etcFormat = Etc::Image::Format::SRGB8A1;
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGBA) {
-            etcFormat = Etc::Image::Format::RGBA8;
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA) {
-            etcFormat = Etc::Image::Format::SRGBA8;
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED) {
-            etcFormat = Etc::Image::Format::R11;
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED_SIGNED) {
-            etcFormat = Etc::Image::Format::SIGNED_R11;
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY) {
-            etcFormat = Etc::Image::Format::RG11;
-        } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY_SIGNED) {
-            etcFormat = Etc::Image::Format::SIGNED_RG11;
-        } else {
-            qCWarning(imagelogging) << "Unknown mip format";
-            Q_UNREACHABLE();
-            return;
-        }
-
-        const Etc::ErrorMetric errorMetric = Etc::ErrorMetric::RGBA;
-        const float effort = 1.0f;
-        const int numEncodeThreads = 4;
-        int encodingTime;
-        const float MAX_COLOR = 255.0f;
-
-        std::vector<vec4> floatData;
-        floatData.resize(width * height);
-        for (int y = 0; y < height; y++) {
-            QRgb *line = (QRgb *)localCopy.scanLine(y);
-            for (int x = 0; x < width; x++) {
-                QRgb &pixel = line[x];
-                floatData[x + y * width] = vec4(qRed(pixel), qGreen(pixel), qBlue(pixel), qAlpha(pixel)) / MAX_COLOR;
-            }
-        }
-
-        // free up the memory afterward to avoid bloating the heap
-        localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one.
-
-        Etc::EncodeMipmaps(
-            (float *)floatData.data(), width, height,
-            etcFormat, errorMetric, effort,
-            numEncodeThreads, numEncodeThreads,
-            numMips, Etc::FILTER_WRAP_NONE,
-            mipMaps, &encodingTime
-        );
-
-        for (int i = 0; i < numMips; i++) {
-            if (mipMaps[i].paucEncodingBits.get()) {
-                if (face >= 0) {
-                    texture->assignStoredMipFace(i, face, mipMaps[i].uiEncodingBitsBytes, static_cast<const gpu::Byte*>(mipMaps[i].paucEncodingBits.get()));
-                } else {
-                    texture->assignStoredMip(i, mipMaps[i].uiEncodingBitsBytes, static_cast<const gpu::Byte*>(mipMaps[i].paucEncodingBits.get()));
-                }
-            }
-        }
-
-        delete[] mipMaps;
-    }
-}
-
-#endif
-
-void generateMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic<bool>& abortProcessing = false, int face = -1) {
-#if CPU_MIPMAPS
-    PROFILE_RANGE(resource_parse, "generateMips");
-
-    if (target == BackendTarget::GLES32) {
-        generateLDRMips(texture, std::move(image), target, abortProcessing, face);
-    } else {
-        if (image.format() == QIMAGE_HDRFORMAT) {
-            generateHDRMips(texture, std::move(image), target, abortProcessing, face);
-        } else {
-            generateLDRMips(texture, std::move(image), target, abortProcessing, face);
-        }
-    }
-#else
-    texture->setAutoGenerateMips(true);
-#endif
-}
-
-void convolveForGGX(const std::vector<QImage>& faces, gpu::Element faceFormat, gpu::Texture* texture, const std::atomic<bool>& abortProcessing = false) {
-    PROFILE_RANGE(resource_parse, "convolveForGGX");
-    CubeMap source(faces, faceFormat, texture->getNumMips(), abortProcessing);
-    CubeMap output(texture->getWidth(), texture->getHeight(), texture->getNumMips());
-
-    source.convolveForGGX(output, abortProcessing);
-    output.copyTo(texture, abortProcessing);
-}
-
-void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAsMask) {
-    PROFILE_RANGE(resource_parse, "processTextureAlpha");
-    validAlpha = false;
-    alphaAsMask = true;
-    const uint8 OPAQUE_ALPHA = 255;
-    const uint8 TRANSPARENT_ALPHA = 0;
-
-    // Figure out if we can use a mask for alpha or not
-    int numOpaques = 0;
-    int numTranslucents = 0;
-    const int NUM_PIXELS = srcImage.width() * srcImage.height();
-    const int MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK = (int)(0.05f * (float)(NUM_PIXELS));
-    const QRgb* data = reinterpret_cast<const QRgb*>(srcImage.constBits());
-    for (int i = 0; i < NUM_PIXELS; ++i) {
-        auto alpha = qAlpha(data[i]);
-        if (alpha == OPAQUE_ALPHA) {
-            numOpaques++;
-        } else if (alpha != TRANSPARENT_ALPHA) {
-            if (++numTranslucents > MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK) {
-                alphaAsMask = false;
-                break;
-            }
-        }
-    }
-    validAlpha = (numOpaques != NUM_PIXELS);
-}
-
-gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress,
-                                                                 BackendTarget target, bool isStrict, const std::atomic<bool>& abortProcessing) {
-    PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage");
-    QImage image = processSourceImage(std::move(srcImage), false, target);
-
-    bool validAlpha = image.hasAlphaChannel();
-    bool alphaAsMask = false;
-
-    if (image.format() != QImage::Format_ARGB32) {
-        image = image.convertToFormat(QImage::Format_ARGB32);
-    }
-
-    if (validAlpha) {
-        processTextureAlpha(image, validAlpha, alphaAsMask);
-    }
-
-    gpu::TexturePointer theTexture = nullptr;
-
-    if ((image.width() > 0) && (image.height() > 0)) {
-        gpu::Element formatMip;
-        gpu::Element formatGPU;
-        if (compress) {
-            if (target == BackendTarget::GLES32) {
-                // GLES does not support GL_BGRA
-                formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA;
-                formatMip = formatGPU;
-            } else {
-                if (validAlpha) {
-                    // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures
-                    // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts).
-                    formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGBA;
-                } else {
-                    formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB;
-                }
-                formatMip = formatGPU;
-            }
-        } else {
-            if (target == BackendTarget::GLES32) {
-            } else {
-                formatGPU = gpu::Element::COLOR_SRGBA_32;
-                formatMip = gpu::Element::COLOR_SBGRA_32;
-            }
-        }
-
-        if (isStrict) {
-            theTexture = gpu::Texture::createStrict(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
-        } else {
-            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);
-        auto usage = gpu::Texture::Usage::Builder().withColor();
-        if (validAlpha) {
-            usage.withAlpha();
-            if (alphaAsMask) {
-                usage.withAlphaMask();
-            }
-        }
-        theTexture->setUsage(usage.build());
-        theTexture->setStoredMipFormat(formatMip);
-        theTexture->assignStoredMip(0, image.byteCount(), image.constBits());
-        generateMips(theTexture.get(), std::move(image), target, abortProcessing);
-    }
-
-    return theTexture;
-}
-
-int clampPixelCoordinate(int coordinate, int maxCoordinate) {
-    return coordinate - ((int)(coordinate < 0) * coordinate) + ((int)(coordinate > maxCoordinate) * (maxCoordinate - coordinate));
-}
-
-const int RGBA_MAX = 255;
-
-// transform -1 - 1 to 0 - 255 (from sobel value to rgb)
-double mapComponent(double sobelValue) {
-    const double factor = RGBA_MAX / 2.0;
-    return (sobelValue + 1.0) * factor;
-}
-
-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 = localCopy.width();
-    int height = localCopy.height();
-
-    QImage result(width, height, QImage::Format_ARGB32);
-
-    for (int i = 0; i < width; i++) {
-        const int iNextClamped = clampPixelCoordinate(i + 1, width - 1);
-        const int iPrevClamped = clampPixelCoordinate(i - 1, width - 1);
-
-        for (int j = 0; j < height; j++) {
-            const int jNextClamped = clampPixelCoordinate(j + 1, height - 1);
-            const int jPrevClamped = clampPixelCoordinate(j - 1, height - 1);
-
-            // surrounding pixels
-            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
-            const double tl = qRed(topLeft);
-            const double t = qRed(top);
-            const double tr = qRed(topRight);
-            const double r = qRed(right);
-            const double br = qRed(bottomRight);
-            const double b = qRed(bottom);
-            const double bl = qRed(bottomLeft);
-            const double l = qRed(left);
-
-            // apply the sobel filter
-            const double dX = (tr + pStrength * r + br) - (tl + pStrength * l + bl);
-            const double dY = (bl + pStrength * b + br) - (tl + pStrength * t + tr);
-            const double dZ = RGBA_MAX / pStrength;
-
-            glm::vec3 v(dX, dY, dZ);
-            glm::normalize(v);
-
-            // convert to rgb from the value obtained computing the filter
-            QRgb qRgbValue = qRgba(mapComponent(v.z), mapComponent(v.y), mapComponent(v.x), 1.0);
-            result.setPixel(i, j, qRgbValue);
-        }
-    }
-
-    return result;
-}
-gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName,
-                                                                     bool compress, BackendTarget target, bool isBumpMap,
-                                                                     const std::atomic<bool>& abortProcessing) {
-    PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage");
-    QImage image = processSourceImage(std::move(srcImage), false, target);
-
-    if (isBumpMap) {
-        image = processBumpMap(std::move(image));
-    }
-
-    // Make sure the normal map source image is ARGB32
-    if (image.format() != QImage::Format_ARGB32) {
-        image = image.convertToFormat(QImage::Format_ARGB32);
-    }
-
-    gpu::TexturePointer theTexture = nullptr;
-    if ((image.width() > 0) && (image.height() > 0)) {
-        gpu::Element formatMip;
-        gpu::Element formatGPU;
-        if (compress) {
-            if (target == BackendTarget::GLES32) {
-                formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY;
-            } else {
-                formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY;
-            }
-        } else {
-            formatGPU = gpu::Element::VEC2NU8_XY;
-        }
-        formatMip = formatGPU;
-
-        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);
-        theTexture->assignStoredMip(0, image.byteCount(), image.constBits());
-        generateMips(theTexture.get(), std::move(image), target, abortProcessing);
-    }
-
-    return theTexture;
-}
-
-gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName,
-                                                                     bool compress, BackendTarget target, bool isInvertedPixels,
-                                                                     const std::atomic<bool>& abortProcessing) {
-    PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage");
-    QImage image = processSourceImage(std::move(srcImage), false, target);
-
-    if (image.format() != QImage::Format_ARGB32) {
-        image = image.convertToFormat(QImage::Format_ARGB32);
-    }
-
-    if (isInvertedPixels) {
-        // Gloss turned into Rough
-        image.invertPixels(QImage::InvertRgba);
-    }
-
-    gpu::TexturePointer theTexture = nullptr;
-    if ((image.width() > 0) && (image.height() > 0)) {
-        gpu::Element formatMip;
-        gpu::Element formatGPU;
-        if (compress) {
-            if (target == BackendTarget::GLES32) {
-                formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED;
-            } else {
-                formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED;
-            }
-        } else {
-            formatGPU = gpu::Element::COLOR_R_8;
-        }
-        formatMip = formatGPU;
-
-        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);
-        theTexture->assignStoredMip(0, image.byteCount(), image.constBits());
-        generateMips(theTexture.get(), std::move(image), target, abortProcessing);
-    }
-
-    return theTexture;  
-}
-
-class CubeLayout {
-public:
-
-    enum SourceProjection {
-        FLAT = 0,
-        EQUIRECTANGULAR,
-    };
-    int _type = FLAT;
-    int _widthRatio = 1;
-    int _heightRatio = 1;
-
-    class Face {
-    public:
-        int _x = 0;
-        int _y = 0;
-        bool _horizontalMirror = false;
-        bool _verticalMirror = false;
-
-        Face() {}
-        Face(int x, int y, bool horizontalMirror, bool verticalMirror) : _x(x), _y(y), _horizontalMirror(horizontalMirror), _verticalMirror(verticalMirror) {}
-    };
-
-    Face _faceXPos;
-    Face _faceXNeg;
-    Face _faceYPos;
-    Face _faceYNeg;
-    Face _faceZPos;
-    Face _faceZNeg;
-
-    CubeLayout(int wr, int hr, Face fXP, Face fXN, Face fYP, Face fYN, Face fZP, Face fZN) :
-        _type(FLAT),
-        _widthRatio(wr),
-        _heightRatio(hr),
-        _faceXPos(fXP),
-        _faceXNeg(fXN),
-        _faceYPos(fYP),
-        _faceYNeg(fYN),
-        _faceZPos(fZP),
-        _faceZNeg(fZN) {}
-
-    CubeLayout(int wr, int hr) :
-        _type(EQUIRECTANGULAR),
-        _widthRatio(wr),
-        _heightRatio(hr) {}
-
-
-    static const CubeLayout CUBEMAP_LAYOUTS[];
-    static const int NUM_CUBEMAP_LAYOUTS;
-
-    static int findLayout(int width, int height) {
-        // Find the layout of the cubemap in the 2D image
-        int foundLayout = -1;
-        for (int i = 0; i < NUM_CUBEMAP_LAYOUTS; i++) {
-            if ((height * CUBEMAP_LAYOUTS[i]._widthRatio) == (width * CUBEMAP_LAYOUTS[i]._heightRatio)) {
-                foundLayout = i;
-                break;
-            }
-        }
-        return foundLayout;
-    }
-
-    static QImage extractEquirectangularFace(const QImage& source, gpu::Texture::CubeFace face, int faceWidth) {
-        QImage image(faceWidth, faceWidth, source.format());
-
-        glm::vec2 dstInvSize(1.0f / faceWidth);
-
-        struct CubeToXYZ {
-            gpu::Texture::CubeFace _face;
-            CubeToXYZ(gpu::Texture::CubeFace face) : _face(face) {}
-
-            glm::vec3 xyzFrom(const glm::vec2& uv) {
-                auto faceDir = glm::normalize(glm::vec3(-1.0f + 2.0f * uv.x, -1.0f + 2.0f * uv.y, 1.0f));
-
-                switch (_face) {
-                    case gpu::Texture::CubeFace::CUBE_FACE_BACK_POS_Z:
-                        return glm::vec3(-faceDir.x, faceDir.y, faceDir.z);
-                    case gpu::Texture::CubeFace::CUBE_FACE_FRONT_NEG_Z:
-                        return glm::vec3(faceDir.x, faceDir.y, -faceDir.z);
-                    case gpu::Texture::CubeFace::CUBE_FACE_LEFT_NEG_X:
-                        return glm::vec3(faceDir.z, faceDir.y, faceDir.x);
-                    case gpu::Texture::CubeFace::CUBE_FACE_RIGHT_POS_X:
-                        return glm::vec3(-faceDir.z, faceDir.y, -faceDir.x);
-                    case gpu::Texture::CubeFace::CUBE_FACE_BOTTOM_NEG_Y:
-                        return glm::vec3(-faceDir.x, -faceDir.z, faceDir.y);
-                    case gpu::Texture::CubeFace::CUBE_FACE_TOP_POS_Y:
-                    default:
-                        return glm::vec3(-faceDir.x, faceDir.z, -faceDir.y);
-                }
-            }
-        };
-        CubeToXYZ cubeToXYZ(face);
-
-        struct RectToXYZ {
-            RectToXYZ() {}
-
-            glm::vec2 uvFrom(const glm::vec3& xyz) {
-                auto flatDir = glm::normalize(glm::vec2(xyz.x, xyz.z));
-                auto uvRad = glm::vec2(atan2(flatDir.x, flatDir.y), asin(xyz.y));
-
-                const float LON_TO_RECT_U = 1.0f / (glm::pi<float>());
-                const float LAT_TO_RECT_V = 2.0f / glm::pi<float>();
-                return glm::vec2(0.5f * uvRad.x * LON_TO_RECT_U + 0.5f, 0.5f * uvRad.y * LAT_TO_RECT_V + 0.5f);
-            }
-        };
-        RectToXYZ rectToXYZ;
-
-        int srcFaceHeight = source.height();
-        int srcFaceWidth = source.width();
-
-        glm::vec2 dstCoord;
-        glm::ivec2 srcPixel;
-        for (int y = 0; y < faceWidth; ++y) {
-            QRgb* destScanLineBegin = reinterpret_cast<QRgb*>( image.scanLine(y) );
-            QRgb* destPixelIterator = destScanLineBegin;
-
-            dstCoord.y = 1.0f - (y + 0.5f) * dstInvSize.y; // Fill cube face images from top to bottom
-            for (int x = 0; x < faceWidth; ++x) {
-                dstCoord.x = (x + 0.5f) * dstInvSize.x;
-
-                auto xyzDir = cubeToXYZ.xyzFrom(dstCoord);
-                auto srcCoord = rectToXYZ.uvFrom(xyzDir);
-
-                srcPixel.x = floor(srcCoord.x * srcFaceWidth);
-                // Flip the vertical axis to QImage going top to bottom
-                srcPixel.y = floor((1.0f - srcCoord.y) * srcFaceHeight);
-
-                if (((uint32)srcPixel.x < (uint32)source.width()) && ((uint32)srcPixel.y < (uint32)source.height())) {
-                    // We can't directly use the pixel() method because that launches a pixel color conversion to output
-                    // a correct RGBA8 color. But in our case we may have stored HDR values encoded in a RGB30 format which
-                    // are not convertible by Qt. The same goes with the setPixel method, by the way.
-                    const QRgb* sourcePixelIterator = reinterpret_cast<const QRgb*>(source.scanLine(srcPixel.y));
-                    sourcePixelIterator += srcPixel.x;
-                    *destPixelIterator = *sourcePixelIterator;
-
-                    // Keep for debug, this is showing the dir as a color
-                    //  glm::u8vec4 rgba((xyzDir.x + 1.0)*0.5 * 256, (xyzDir.y + 1.0)*0.5 * 256, (xyzDir.z + 1.0)*0.5 * 256, 256);
-                    //  unsigned int val = 0xff000000 | (rgba.r) | (rgba.g << 8) | (rgba.b << 16);
-                    //  *destPixelIterator = val;
-                }
-                ++destPixelIterator;
-            }
-        }
-        return image;
-    }
-};
-
-const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = {
-
-    // Here is the expected layout for the faces in an image with the 2/1 aspect ratio:
-    // THis is detected as an Equirectangular projection
-    //                   WIDTH
-    //       <--------------------------->
-    //    ^  +------+------+------+------+
-    //    H  |      |      |      |      |
-    //    E  |      |      |      |      |
-    //    I  |      |      |      |      |
-    //    G  +------+------+------+------+
-    //    H  |      |      |      |      |
-    //    T  |      |      |      |      |
-    //    |  |      |      |      |      |
-    //    v  +------+------+------+------+
-    //
-    //    FaceWidth = width = height / 6
-    { 2, 1 },
-
-    // Here is the expected layout for the faces in an image with the 1/6 aspect ratio:
-    //
-    //         WIDTH
-    //       <------>
-    //    ^  +------+
-    //    |  |      |
-    //    |  |  +X  |
-    //    |  |      |
-    //    H  +------+
-    //    E  |      |
-    //    I  |  -X  |
-    //    G  |      |
-    //    H  +------+
-    //    T  |      |
-    //    |  |  +Y  |
-    //    |  |      |
-    //    |  +------+
-    //    |  |      |
-    //    |  |  -Y  |
-    //    |  |      |
-    //    H  +------+
-    //    E  |      |
-    //    I  |  +Z  |
-    //    G  |      |
-    //    H  +------+
-    //    T  |      |
-    //    |  |  -Z  |
-    //    |  |      |
-    //    V  +------+
-    //
-    //    FaceWidth = width = height / 6
-    { 1, 6,
-    { 0, 0, true, false },
-    { 0, 1, true, false },
-    { 0, 2, false, true },
-    { 0, 3, false, true },
-    { 0, 4, true, false },
-    { 0, 5, true, false }
-    },
-
-    // Here is the expected layout for the faces in an image with the 3/4 aspect ratio:
-    //
-    //       <-----------WIDTH----------->
-    //    ^  +------+------+------+------+
-    //    |  |      |      |      |      |
-    //    |  |      |  +Y  |      |      |
-    //    |  |      |      |      |      |
-    //    H  +------+------+------+------+
-    //    E  |      |      |      |      |
-    //    I  |  -X  |  -Z  |  +X  |  +Z  |
-    //    G  |      |      |      |      |
-    //    H  +------+------+------+------+
-    //    T  |      |      |      |      |
-    //    |  |      |  -Y  |      |      |
-    //    |  |      |      |      |      |
-    //    V  +------+------+------+------+
-    //
-    //    FaceWidth = width / 4 = height / 3
-    { 4, 3,
-    { 2, 1, true, false },
-    { 0, 1, true, false },
-    { 1, 0, false, true },
-    { 1, 2, false, true },
-    { 3, 1, true, false },
-    { 1, 1, true, false }
-    },
-
-    // Here is the expected layout for the faces in an image with the 4/3 aspect ratio:
-    //
-    //       <-------WIDTH-------->
-    //    ^  +------+------+------+
-    //    |  |      |      |      |
-    //    |  |      |  +Y  |      |
-    //    |  |      |      |      |
-    //    H  +------+------+------+
-    //    E  |      |      |      |
-    //    I  |  -X  |  -Z  |  +X  |
-    //    G  |      |      |      |
-    //    H  +------+------+------+
-    //    T  |      |      |      |
-    //    |  |      |  -Y  |      |
-    //    |  |      |      |      |
-    //    |  +------+------+------+
-    //    |  |      |      |      |
-    //    |  |      |  +Z! |      | <+Z is upside down!
-    //    |  |      |      |      |
-    //    V  +------+------+------+
-    //
-    //    FaceWidth = width / 3 = height / 4
-    { 3, 4,
-    { 2, 1, true, false },
-    { 0, 1, true, false },
-    { 1, 0, false, true },
-    { 1, 2, false, true },
-    { 1, 3, false, true },
-    { 1, 1, true, false }
-    }
-};
-const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout);
-
-//#define DEBUG_COLOR_PACKING
-QImage convertToLDRFormat(QImage&& srcImage, QImage::Format 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 ldrImage(localCopy.width(), localCopy.height(), format);
-    auto unpackFunc = getHDRUnpackingFunction();
-
-    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* ldrLineIt = reinterpret_cast<uint32*>(ldrImage.scanLine(y));
-        glm::vec3 color;
-
-        while (srcLineIt < srcLineEnd) {
-            color = unpackFunc(*srcLineIt);
-            // Apply reverse gamma and clamp
-            color.r = std::pow(color.r, 1.0f / 2.2f);
-            color.g = std::pow(color.g, 1.0f / 2.2f);
-            color.b = std::pow(color.b, 1.0f / 2.2f);
-            color.r = std::min(1.0f, color.r) * 255.0f;
-            color.g = std::min(1.0f, color.g) * 255.0f;
-            color.b = std::min(1.0f, color.b) * 255.0f;
-            *ldrLineIt = qRgb((int)color.r, (int)color.g, (int)color.b);
-
-            ++srcLineIt;
-            ++ldrLineIt;
-        }
-    }
-    return ldrImage;
-}
-
-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_HDRFORMAT);
-    std::function<uint32(const glm::vec3&)> packFunc;
-#ifdef DEBUG_COLOR_PACKING
-    std::function<glm::vec3(uint32)> unpackFunc;
-#endif
-
-    switch (format.getSemantic()) {
-        case gpu::R11G11B10:
-            packFunc = packR11G11B10F;
-#ifdef DEBUG_COLOR_PACKING
-            unpackFunc = glm::unpackF2x11_1x10;
-#endif
-            break;
-        case gpu::RGB9E5:
-            packFunc = glm::packF3x9_E1x5;
-#ifdef DEBUG_COLOR_PACKING
-            unpackFunc = glm::unpackF3x9_E1x5;
-#endif
-            break;
-        default:
-            qCWarning(imagelogging) << "Unsupported HDR format";
-            Q_UNREACHABLE();
-            return localCopy;
-    }
-
-    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;
-
-        while (srcLineIt < srcLineEnd) {
-            color.r = qRed(*srcLineIt);
-            color.g = qGreen(*srcLineIt);
-            color.b = qBlue(*srcLineIt);
-            // Normalize and apply gamma
-            color /= 255.0f;
-            color.r = std::pow(color.r, 2.2f);
-            color.g = std::pow(color.g, 2.2f);
-            color.b = std::pow(color.b, 2.2f);
-            *hdrLineIt = packFunc(color);
-#ifdef DEBUG_COLOR_PACKING
-            glm::vec3 ucolor = unpackFunc(*hdrLineIt);
-            assert(glm::distance(color, ucolor) <= 5e-2);
-#endif
-            ++srcLineIt;
-            ++hdrLineIt;
-        }
-    }
-    return hdrImage;
-}
-
-gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName,
-                                                                   bool compress, BackendTarget target, int options,
-                                                                   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;
-
-    QImage image = processSourceImage(std::move(localCopy), true, target);
-    auto targetCubemapFormat = cubeMapFormatForTarget(target);
-    if (targetCubemapFormat == QIMAGE_HDRFORMAT && image.format() != targetCubemapFormat) {
-        // If the target format is HDR but the image isn't, we need to convert the
-        // image to HDR.
-        image = convertToHDRFormat(std::move(image), GPUTEXTURE_HDRFORMAT);
-    } else if (image.format() == QIMAGE_HDRFORMAT && image.format() != targetCubemapFormat) {
-        // If the target format isn't HDR (such as on GLES) but the image is, we need to
-        // convert the image to LDR
-        image = convertToLDRFormat(std::move(image), targetCubemapFormat);
-    }
-
-    gpu::Element formatMip;
-    gpu::Element formatGPU;
-    if (compress) {
-        if (target == BackendTarget::GLES32) {
-            formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB;
-        } else {
-            formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB;
-        }
-    } else {
-        formatGPU = GPUTEXTURE_HDRFORMAT;
-    }
-
-    formatMip = formatGPU;
-
-    // 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));
-        }
-    }
-
-    // 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.
-
-    // 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 (options & CUBE_GENERATE_IRRADIANCE) {
-            PROFILE_RANGE(resource_parse, "generateIrradiance");
-            gpu::Element irradianceFormat;
-            // TODO: we could locally compress the irradiance texture on Android, but we don't need to
-            if (target == BackendTarget::GLES32) {
-                irradianceFormat = gpu::Element::COLOR_SRGBA_32;
-            } else {
-                irradianceFormat = GPUTEXTURE_HDRFORMAT;
-            }
-
-            auto irradianceTexture = gpu::Texture::createCube(irradianceFormat, 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(irradianceFormat);
-            for (uint8 face = 0; face < faces.size(); ++face) {
-                irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits());
-            }
-
-            irradianceTexture->generateIrradiance(target);
-
-            auto irradiance = irradianceTexture->getIrradiance();
-            theTexture->overrideIrradiance(irradiance);
-        }
-
-        if (options & CUBE_GGX_CONVOLVE) {
-            convolveForGGX(faces, GPUTEXTURE_HDRFORMAT, theTexture.get(), abortProcessing);
-        } else {
-            // Create mip maps and compress to final format in one go
-            for (uint8 face = 0; face < faces.size(); ++face) {
-                // Force building the mip maps right now on CPU if we are convolving for GGX later on
-                generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face);
-            }
-        }
-
-    }
-
-    return theTexture;
-}
-
-} // namespace image
diff --git a/libraries/image/src/image/TextureProcessing.h b/libraries/image/src/image/TextureProcessing.h
deleted file mode 100644
index e925718347..0000000000
--- a/libraries/image/src/image/TextureProcessing.h
+++ /dev/null
@@ -1,125 +0,0 @@
-//
-//  Image.h
-//  image/src/image
-//
-//  Created by Clement Brisset on 4/5/2017.
-//  Copyright 2017 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-#ifndef hifi_image_Image_h
-#define hifi_image_Image_h
-
-#include <QVariant>
-#include <QImage>
-#include <nvtt/nvtt.h>
-
-#include <gpu/Texture.h>
-
-#include "ColorChannel.h"
-
-class QByteArray;
-
-namespace image {
-
-extern const QImage::Format QIMAGE_HDRFORMAT;
-
-std::function<gpu::uint32(const glm::vec3&)> getHDRPackingFunction();
-std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction();
-void convertToFloat(const unsigned char* source, int width, int height, size_t srcLineByteStride, gpu::Element sourceFormat, 
-                    glm::vec4* output, size_t outputLinePixelStride);
-void convertFromFloat(unsigned char* output, int width, int height, size_t outputLineByteStride, gpu::Element outputFormat,
-                      const glm::vec4* source, size_t srcLinePixelStride);
-                      
-namespace TextureUsage {
-
-enum Type {
-    DEFAULT_TEXTURE,
-    STRICT_TEXTURE,
-    ALBEDO_TEXTURE,
-    NORMAL_TEXTURE,
-    BUMP_TEXTURE,
-    SPECULAR_TEXTURE,
-    METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey
-    ROUGHNESS_TEXTURE,
-    GLOSS_TEXTURE,
-    EMISSIVE_TEXTURE,
-    SKY_TEXTURE,
-    AMBIENT_TEXTURE,
-    OCCLUSION_TEXTURE,
-    SCATTERING_TEXTURE = OCCLUSION_TEXTURE,
-    LIGHTMAP_TEXTURE,
-    UNUSED_TEXTURE
-};
-
-using TextureLoader = std::function<gpu::TexturePointer(QImage&&, const std::string&, bool, gpu::BackendTarget, const std::atomic<bool>&)>;
-TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap());
-
-gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName,
-                                             bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName,
-                                                   bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName,
-                                                 bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName,
-                                                   bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName,
-                                                       bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName,
-                                                     bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName,
-                                                    bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName,
-                                                         bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName,
-                                                   bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
-                                               bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createCubeTextureAndIrradianceFromImage(QImage&& image, const std::string& srcImageName,
-                                                            bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createAmbientCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
-                                                      bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createAmbientCubeTextureAndIrradianceFromImage(QImage&& image, const std::string& srcImageName,
-                                                                   bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName,
-                                                   bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing); 
-gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress,
-                                                   gpu::BackendTarget target, bool isStrict, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress,
-                                                       gpu::BackendTarget target, bool isBumpMap, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress,
-                                                       gpu::BackendTarget target, bool isInvertedPixels, const std::atomic<bool>& abortProcessing);
-
-enum CubeTextureOptions {
-    CUBE_DEFAULT = 0x0,
-    CUBE_GENERATE_IRRADIANCE = 0x1,
-    CUBE_GGX_CONVOLVE = 0x2
-};
-gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress,
-                                                     gpu::BackendTarget target, int option, const std::atomic<bool>& abortProcessing);
-
-} // namespace TextureUsage
-
-const QStringList getSupportedFormats();
-
-gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& url, ColorChannel sourceChannel,
-                                 int maxNumPixels, TextureUsage::Type textureType,
-                                 bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing = false);
-
-#if defined(NVTT_API)
-class SequentialTaskDispatcher : public nvtt::TaskDispatcher {
-public:
-    SequentialTaskDispatcher(const std::atomic<bool>& abortProcessing);
-
-    const std::atomic<bool>& _abortProcessing;
-
-    void dispatch(nvtt::Task* task, void* context, int count) override;
-};
-
-nvtt::OutputHandler* getNVTTCompressionOutputHandler(gpu::Texture* outputTexture, int face, nvtt::CompressionOptions& compressOptions);
-#endif
-} // namespace image
-
-#endif // hifi_image_Image_h

From 59eeb9361e6b35c110a8b7506ba085951326ad96 Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Mon, 1 Apr 2019 11:08:23 +0200
Subject: [PATCH 10/23] Corrections after merge

---
 libraries/image/src/image/CubeMap.cpp         |  10 +-
 libraries/image/src/image/CubeMap.h           |   4 +-
 libraries/image/src/image/Image.h             |   1 +
 .../image/src/image/TextureProcessing.cpp     | 210 ++++++++++++------
 libraries/image/src/image/TextureProcessing.h |  38 +++-
 5 files changed, 181 insertions(+), 82 deletions(-)

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index 68fc6fe848..f818f1f6e0 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -15,7 +15,7 @@
 #include <tbb/blocked_range2d.h>
 
 #include "RandomAndNoise.h"
-#include "Image.h"
+#include "TextureProcessing.h"
 #include "ImageLogging.h"
 
 #include <nvtt/nvtt.h>
@@ -290,8 +290,8 @@ struct CubeMap::MipMapOutputHandler : public nvtt::OutputHandler {
     glm::vec4* _current{ nullptr };
 };
 
-CubeMap::CubeMap(const std::vector<QImage>& faces, gpu::Element srcTextureFormat, int mipCount, const std::atomic<bool>& abortProcessing) {
-    reset(faces.front().width(), faces.front().height(), mipCount);
+CubeMap::CubeMap(const std::vector<Image>& faces, gpu::Element srcTextureFormat, int mipCount, const std::atomic<bool>& abortProcessing) {
+    reset(faces.front().getWidth(), faces.front().getHeight(), mipCount);
 
     int face;
 
@@ -303,10 +303,10 @@ CubeMap::CubeMap(const std::vector<QImage>& faces, gpu::Element srcTextureFormat
 
     // Compute mips
     for (face = 0; face < 6; face++) {
-        auto sourcePixels = faces[face].bits();
+        auto sourcePixels = faces[face].getBits();
         auto floatPixels = editFace(0, face);
 
-        convertToFloat(sourcePixels, _width, _height, faces[face].bytesPerLine(), srcTextureFormat, floatPixels, _width);
+        convertToFloat(sourcePixels, _width, _height, faces[face].getBytesPerLineCount(), srcTextureFormat, floatPixels, _width);
 
         nvtt::Surface surface;
         surface.setImage(nvtt::InputFormat_RGBA_32F, _width, _height, 1, floatPixels);
diff --git a/libraries/image/src/image/CubeMap.h b/libraries/image/src/image/CubeMap.h
index 17bc5642eb..808f6eea42 100644
--- a/libraries/image/src/image/CubeMap.h
+++ b/libraries/image/src/image/CubeMap.h
@@ -18,7 +18,7 @@
 #include <array>
 #include <atomic>
 
-#include <QImage>
+#include "Image.h"
 
 namespace image {
 
@@ -26,7 +26,7 @@ namespace image {
     public:
  
         CubeMap(int width, int height, int mipCount);
-        CubeMap(const std::vector<QImage>& faces, gpu::Element faceFormat, int mipCount, const std::atomic<bool>& abortProcessing = false);
+        CubeMap(const std::vector<Image>& faces, gpu::Element faceFormat, int mipCount, const std::atomic<bool>& abortProcessing = false);
 
         void reset(int width, int height, int mipCount);
         void copyTo(gpu::Texture* texture, const std::atomic<bool>& abortProcessing = false) const;
diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h
index bfecf4f2a1..7ed4f80370 100644
--- a/libraries/image/src/image/Image.h
+++ b/libraries/image/src/image/Image.h
@@ -70,6 +70,7 @@ namespace image {
         glm::uint32 getHeight() const { return (glm::uint32)_data.height(); }
         glm::uvec2 getSize() const { return toGlm(_data.size()); }
         size_t getByteCount() const { return _data.byteCount(); }
+        size_t getBytesPerLineCount() const { return _data.bytesPerLine(); }
 
         QRgb getPixel(int x, int y) const { return _data.pixel(x, y); }
         void setPixel(int x, int y, QRgb value) {
diff --git a/libraries/image/src/image/TextureProcessing.cpp b/libraries/image/src/image/TextureProcessing.cpp
index 037229ace5..1563ba7079 100644
--- a/libraries/image/src/image/TextureProcessing.cpp
+++ b/libraries/image/src/image/TextureProcessing.cpp
@@ -29,6 +29,7 @@
 #include "OpenEXRReader.h"
 #endif
 #include "ImageLogging.h"
+#include "CubeMap.h"
 
 using namespace gpu;
 
@@ -111,11 +112,13 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con
             return image::TextureUsage::createEmissiveTextureFromImage;
         case LIGHTMAP_TEXTURE:
             return image::TextureUsage::createLightmapTextureFromImage;
-        case CUBE_TEXTURE:
+        case SKY_TEXTURE:
+            return image::TextureUsage::createCubeTextureFromImage;
+        case AMBIENT_TEXTURE:
             if (options.value("generateIrradiance", true).toBool()) {
-                return image::TextureUsage::createCubeTextureFromImage;
+                return image::TextureUsage::createAmbientCubeTextureAndIrradianceFromImage;
             } else {
-                return image::TextureUsage::createCubeTextureFromImageWithoutIrradiance;
+                return image::TextureUsage::createAmbientCubeTextureFromImage;
             }
         case BUMP_TEXTURE:
             return image::TextureUsage::createNormalTextureFromBumpImage;
@@ -186,14 +189,24 @@ gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(Image&& srcImag
     return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing);
 }
 
-gpu::TexturePointer TextureUsage::createCubeTextureFromImage(Image&& srcImage, const std::string& srcImageName,
+gpu::TexturePointer TextureUsage::createCubeTextureAndIrradianceFromImage(Image&& srcImage, const std::string& srcImageName,
                                                              bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing);
+    return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, CUBE_GENERATE_IRRADIANCE, abortProcessing);
 }
 
-gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(Image&& srcImage, const std::string& srcImageName,
-                                                                              bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
-    return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing);
+gpu::TexturePointer TextureUsage::createCubeTextureFromImage(Image&& srcImage, const std::string& srcImageName,
+                                                             bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
+    return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, CUBE_DEFAULT, abortProcessing);
+}
+
+gpu::TexturePointer TextureUsage::createAmbientCubeTextureFromImage(Image&& image, const std::string& srcImageName,
+                                                           bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing) {
+    return processCubeTextureColorFromImage(std::move(image), srcImageName, compress, target, CUBE_GGX_CONVOLVE, abortProcessing);
+}
+
+gpu::TexturePointer TextureUsage::createAmbientCubeTextureAndIrradianceFromImage(Image&& image, const std::string& srcImageName,
+                                                                        bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing) {
+    return processCubeTextureColorFromImage(std::move(image), srcImageName, compress, target, CUBE_GENERATE_IRRADIANCE | CUBE_GGX_CONVOLVE, abortProcessing);
 }
 
 static float denormalize(float value, const float minValue) {
@@ -215,11 +228,17 @@ static uint32 packR11G11B10F(const glm::vec3& color) {
     return glm::packF2x11_1x10(ucolor);
 }
 
+static uint32 packUnorm4x8(const glm::vec3& color) {
+    return glm::packUnorm4x8(glm::vec4(color, 1.0f));
+}
+
 static std::function<uint32(const glm::vec3&)> getHDRPackingFunction(const gpu::Element& format) {
     if (format == gpu::Element::COLOR_RGB9E5) {
         return glm::packF3x9_E1x5;
     } else if (format == gpu::Element::COLOR_R11G11B10) {
         return packR11G11B10F;
+    } else if (format == gpu::Element::COLOR_RGBA_32 || format == gpu::Element::COLOR_SRGBA_32 || format == gpu::Element::COLOR_BGRA_32 || format == gpu::Element::COLOR_SBGRA_32) {
+        return packUnorm4x8;
     } else {
         qCWarning(imagelogging) << "Unknown handler format";
         Q_UNREACHABLE();
@@ -231,13 +250,15 @@ std::function<uint32(const glm::vec3&)> getHDRPackingFunction() {
     return getHDRPackingFunction(GPU_CUBEMAP_HDR_FORMAT);
 }
 
-std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction() {
-    if (GPU_CUBEMAP_HDR_FORMAT == gpu::Element::COLOR_RGB9E5) {
+std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction(const gpu::Element& format) {
+    if (format == gpu::Element::COLOR_RGB9E5) {
         return glm::unpackF3x9_E1x5;
-    } else if (GPU_CUBEMAP_HDR_FORMAT == gpu::Element::COLOR_R11G11B10) {
+    } else if (format == gpu::Element::COLOR_R11G11B10) {
         return glm::unpackF2x11_1x10;
+    } else if (format == gpu::Element::COLOR_RGBA_32 || format == gpu::Element::COLOR_SRGBA_32 || format == gpu::Element::COLOR_BGRA_32 || format == gpu::Element::COLOR_SBGRA_32) {
+        return glm::unpackUnorm4x8;
     } else {
-        qCWarning(imagelogging) << "Unknown HDR encoding format in Image";
+        qCWarning(imagelogging) << "Unknown handler format";
         Q_UNREACHABLE();
         return nullptr;
     }
@@ -490,22 +511,92 @@ struct MyErrorHandler : public nvtt::ErrorHandler {
     }
 };
 
-class SequentialTaskDispatcher : public nvtt::TaskDispatcher {
-public:
-    SequentialTaskDispatcher(const std::atomic<bool>& abortProcessing) : _abortProcessing(abortProcessing) {};
+SequentialTaskDispatcher::SequentialTaskDispatcher(const std::atomic<bool>& abortProcessing) : _abortProcessing(abortProcessing) {
+}
 
-    const std::atomic<bool>& _abortProcessing;
-
-    virtual void dispatch(nvtt::Task* task, void* context, int count) override {
-        for (int i = 0; i < count; i++) {
-            if (!_abortProcessing.load()) {
-                task(context, i);
-            } else {
-                break;
-            }
+void SequentialTaskDispatcher::dispatch(nvtt::Task* task, void* context, int count) {
+    for (int i = 0; i < count; i++) {
+        if (!_abortProcessing.load()) {
+            task(context, i);
+        } else {
+            break;
         }
     }
-};
+}
+
+void image::convertToFloat(const unsigned char* source, int width, int height, size_t srcLineByteStride, gpu::Element sourceFormat,
+                           glm::vec4* output, size_t outputLinePixelStride) {
+    glm::vec4* outputIt;
+    auto unpackFunc = getHDRUnpackingFunction(sourceFormat);
+
+    outputLinePixelStride -= width;
+    outputIt = output;
+    for (auto lineNb = 0; lineNb < height; lineNb++) {
+        const uint32* srcPixelIt = reinterpret_cast<const uint32*>(source + lineNb * srcLineByteStride);
+        const uint32* srcPixelEnd = srcPixelIt + width;
+
+        while (srcPixelIt < srcPixelEnd) {
+            *outputIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f);
+            ++srcPixelIt;
+            ++outputIt;
+        }
+        outputIt += outputLinePixelStride;
+    }
+}
+
+void image::convertFromFloat(unsigned char* output, int width, int height, size_t outputLineByteStride, gpu::Element outputFormat,
+                             const glm::vec4* source, size_t srcLinePixelStride) {
+    const glm::vec4* sourceIt;
+    auto packFunc = getHDRPackingFunction(outputFormat);
+
+    srcLinePixelStride -= width;
+    sourceIt = source;
+    for (auto lineNb = 0; lineNb < height; lineNb++) {
+        uint32* outPixelIt = reinterpret_cast<uint32*>(output + lineNb * outputLineByteStride);
+        uint32* outPixelEnd = outPixelIt + width;
+
+        while (outPixelIt < outPixelEnd) {
+            *outPixelIt = packFunc(*sourceIt);
+            ++outPixelIt;
+            ++sourceIt;
+        }
+        sourceIt += srcLinePixelStride;
+    }
+}
+
+nvtt::OutputHandler* getNVTTCompressionOutputHandler(gpu::Texture* outputTexture, int face, nvtt::CompressionOptions& compressionOptions) {
+    auto outputFormat = outputTexture->getStoredMipFormat();
+
+    nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F;
+    nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
+    nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
+
+    compressionOptions.setQuality(nvtt::Quality_Production);
+
+    // TODO: gles: generate ETC mips instead?
+    if (outputFormat == gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB) {
+        compressionOptions.setFormat(nvtt::Format_BC6);
+    } else if (outputFormat == gpu::Element::COLOR_RGB9E5) {
+        compressionOptions.setFormat(nvtt::Format_RGB);
+        compressionOptions.setPixelType(nvtt::PixelType_Float);
+        compressionOptions.setPixelFormat(32, 32, 32, 0);
+    } else if (outputFormat == gpu::Element::COLOR_R11G11B10) {
+        compressionOptions.setFormat(nvtt::Format_RGB);
+        compressionOptions.setPixelType(nvtt::PixelType_Float);
+        compressionOptions.setPixelFormat(32, 32, 32, 0);
+    } else {
+        qCWarning(imagelogging) << "Unknown mip format";
+        Q_UNREACHABLE();
+        return nullptr;
+    }
+
+    if (outputFormat == gpu::Element::COLOR_RGB9E5 || outputFormat == gpu::Element::COLOR_R11G11B10) {
+        // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats
+        return new PackedFloatOutputHandler(outputTexture, face, outputFormat);
+    } else {
+        return new OutputHandler(outputTexture, face);
+    }
+}
 
 void generateHDRMips(gpu::Texture* texture, Image&& image, BackendTarget target, const std::atomic<bool>& abortProcessing, int face) {
     // Take a local copy to force move construction
@@ -518,64 +609,28 @@ void generateHDRMips(gpu::Texture* texture, Image&& image, BackendTarget target,
     std::vector<glm::vec4> data;
     std::vector<glm::vec4>::iterator dataIt;
     auto mipFormat = texture->getStoredMipFormat();
-    std::function<glm::vec3(uint32)> unpackFunc = getHDRUnpackingFunction();
 
     nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F;
     nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
     nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
 
-    nvtt::CompressionOptions compressionOptions;
-    compressionOptions.setQuality(nvtt::Quality_Production);
-
-    // TODO: gles: generate ETC mips instead?
-    if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB) {
-        compressionOptions.setFormat(nvtt::Format_BC6);
-    } else if (mipFormat == gpu::Element::COLOR_RGB9E5) {
-        compressionOptions.setFormat(nvtt::Format_RGB);
-        compressionOptions.setPixelType(nvtt::PixelType_Float);
-        compressionOptions.setPixelFormat(32, 32, 32, 0);
-    } else if (mipFormat == gpu::Element::COLOR_R11G11B10) {
-        compressionOptions.setFormat(nvtt::Format_RGB);
-        compressionOptions.setPixelType(nvtt::PixelType_Float);
-        compressionOptions.setPixelFormat(32, 32, 32, 0);
-    } 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 = reinterpret_cast<const uint32*>(localCopy.getScanLine(lineNb));
-        const uint32* srcPixelEnd = srcPixelIt + width;
-
-        while (srcPixelIt < srcPixelEnd) {
-            *dataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f);
-            ++srcPixelIt;
-            ++dataIt;
-        }
-    }
-    assert(dataIt == data.end());
+    convertToFloat(localCopy.getBits(), width, height, localCopy.getBytesPerLineCount(), GPU_CUBEMAP_HDR_FORMAT, data.data(), width);
 
     // We're done with the localCopy, free up the memory to avoid bloating the heap
     localCopy = Image(); // Image 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;
+
+    nvtt::CompressionOptions compressionOptions;
+    std::unique_ptr<nvtt::OutputHandler> outputHandler{ getNVTTCompressionOutputHandler(texture, face, compressionOptions) };
+
     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;
@@ -1416,8 +1471,17 @@ Image convertToHDRFormat(Image&& srcImage, gpu::Element format) {
     return hdrImage;
 }
 
+void convolveForGGX(const std::vector<Image>& faces, gpu::Element faceFormat, gpu::Texture* texture, const std::atomic<bool>& abortProcessing = false) {
+    PROFILE_RANGE(resource_parse, "convolveForGGX");
+    CubeMap source(faces, faceFormat, texture->getNumMips(), abortProcessing);
+    CubeMap output(texture->getWidth(), texture->getHeight(), texture->getNumMips());
+
+    source.convolveForGGX(output, abortProcessing);
+    output.copyTo(texture, abortProcessing);
+}
+
 gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(Image&& srcImage, const std::string& srcImageName,
-                                                                   bool compress, BackendTarget target, bool generateIrradiance,
+                                                                   bool compress, BackendTarget target, int options,
                                                                    const std::atomic<bool>& abortProcessing) {
     PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage");
 
@@ -1491,7 +1555,7 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(Image&& srcIm
         theTexture->setStoredMipFormat(formatMip);
 
         // Generate irradiance while we are at it
-        if (generateIrradiance) {
+        if (options & CUBE_GENERATE_IRRADIANCE) {
             PROFILE_RANGE(resource_parse, "generateIrradiance");
             gpu::Element irradianceFormat;
             // TODO: we could locally compress the irradiance texture on Android, but we don't need to
@@ -1514,8 +1578,14 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(Image&& srcIm
             theTexture->overrideIrradiance(irradiance);
         }
 
-        for (uint8 face = 0; face < faces.size(); ++face) {
-            generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face);
+        if (options & CUBE_GGX_CONVOLVE) {
+            convolveForGGX(faces, GPU_CUBEMAP_HDR_FORMAT, theTexture.get(), abortProcessing);
+        } else {
+            // Create mip maps and compress to final format in one go
+            for (uint8 face = 0; face < faces.size(); ++face) {
+                // Force building the mip maps right now on CPU if we are convolving for GGX later on
+                generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face);
+            }
         }
     }
 
diff --git a/libraries/image/src/image/TextureProcessing.h b/libraries/image/src/image/TextureProcessing.h
index 2fc23b136d..7d1c483155 100644
--- a/libraries/image/src/image/TextureProcessing.h
+++ b/libraries/image/src/image/TextureProcessing.h
@@ -17,11 +17,16 @@
 #include <gpu/Texture.h>
 
 #include "Image.h"
+#include <nvtt/nvtt.h>
 
 namespace image {
 
     std::function<gpu::uint32(const glm::vec3&)> getHDRPackingFunction();
     std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction();
+    void convertToFloat(const unsigned char* source, int width, int height, size_t srcLineByteStride, gpu::Element sourceFormat, 
+                        glm::vec4* output, size_t outputLinePixelStride);
+    void convertFromFloat(unsigned char* output, int width, int height, size_t outputLineByteStride, gpu::Element outputFormat,
+                          const glm::vec4* source, size_t srcLinePixelStride);
 
 namespace TextureUsage {
 
@@ -36,7 +41,8 @@ enum Type {
     ROUGHNESS_TEXTURE,
     GLOSS_TEXTURE,
     EMISSIVE_TEXTURE,
-    CUBE_TEXTURE,
+    SKY_TEXTURE,
+    AMBIENT_TEXTURE,
     OCCLUSION_TEXTURE,
     SCATTERING_TEXTURE = OCCLUSION_TEXTURE,
     LIGHTMAP_TEXTURE,
@@ -66,8 +72,12 @@ gpu::TexturePointer createMetallicTextureFromImage(Image&& image, const std::str
                                                    bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
 gpu::TexturePointer createCubeTextureFromImage(Image&& image, const std::string& srcImageName,
                                                bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(Image&& image, const std::string& srcImageName,
-                                                                bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
+gpu::TexturePointer createCubeTextureAndIrradianceFromImage(Image&& image, const std::string& srcImageName,
+                                                            bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
+gpu::TexturePointer createAmbientCubeTextureFromImage(Image&& image, const std::string& srcImageName,
+                                                      bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
+gpu::TexturePointer createAmbientCubeTextureAndIrradianceFromImage(Image&& image, const std::string& srcImageName,
+                                                                   bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
 gpu::TexturePointer createLightmapTextureFromImage(Image&& image, const std::string& srcImageName,
                                                    bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing); 
 gpu::TexturePointer process2DTextureColorFromImage(Image&& srcImage, const std::string& srcImageName, bool compress,
@@ -76,9 +86,14 @@ gpu::TexturePointer process2DTextureNormalMapFromImage(Image&& srcImage, const s
                                                        gpu::BackendTarget target, bool isBumpMap, const std::atomic<bool>& abortProcessing);
 gpu::TexturePointer process2DTextureGrayscaleFromImage(Image&& srcImage, const std::string& srcImageName, bool compress,
                                                        gpu::BackendTarget target, bool isInvertedPixels, const std::atomic<bool>& abortProcessing);
-gpu::TexturePointer processCubeTextureColorFromImage(Image&& srcImage, const std::string& srcImageName, bool compress,
-                                                     gpu::BackendTarget target, bool generateIrradiance, const std::atomic<bool>& abortProcessing);
 
+enum CubeTextureOptions {
+    CUBE_DEFAULT = 0x0,
+    CUBE_GENERATE_IRRADIANCE = 0x1,
+    CUBE_GGX_CONVOLVE = 0x2
+};
+gpu::TexturePointer processCubeTextureColorFromImage(Image&& srcImage, const std::string& srcImageName, bool compress,
+                                                     gpu::BackendTarget target, int option, const std::atomic<bool>& abortProcessing);
 } // namespace TextureUsage
 
 const QStringList getSupportedFormats();
@@ -87,6 +102,19 @@ gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::
                                  int maxNumPixels, TextureUsage::Type textureType,
                                  bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing = false);
 
+#if defined(NVTT_API)
+class SequentialTaskDispatcher : public nvtt::TaskDispatcher {
+public:
+    SequentialTaskDispatcher(const std::atomic<bool>& abortProcessing);
+
+    const std::atomic<bool>& _abortProcessing;
+
+    void dispatch(nvtt::Task* task, void* context, int count) override;
+};
+
+nvtt::OutputHandler* getNVTTCompressionOutputHandler(gpu::Texture* outputTexture, int face, nvtt::CompressionOptions& compressOptions);
+#endif
+
 } // namespace image
 
 #endif // hifi_image_TextureProcessing_h

From 706dc0e30343335f6672f6cefb40e51d5bbf6e9b Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Mon, 1 Apr 2019 11:31:38 +0200
Subject: [PATCH 11/23] Fixed some issues with merge

---
 libraries/image/src/image/TextureProcessing.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/libraries/image/src/image/TextureProcessing.cpp b/libraries/image/src/image/TextureProcessing.cpp
index 1563ba7079..00e6fd806d 100644
--- a/libraries/image/src/image/TextureProcessing.cpp
+++ b/libraries/image/src/image/TextureProcessing.cpp
@@ -264,6 +264,10 @@ std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction(const gpu::Element
     }
 }
 
+std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction() {
+    return getHDRUnpackingFunction(GPU_CUBEMAP_HDR_FORMAT);
+}
+
 Image processRawImageData(QIODevice& content, const std::string& filename) {
     // Help the Image loader by extracting the image file format from the url filename ext.
     // Some tga are not created properly without it.

From f895e96500246844a287811073d794d995a9fc3f Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Mon, 1 Apr 2019 14:22:26 +0200
Subject: [PATCH 12/23] Fixed wrong sample count

---
 libraries/image/src/image/CubeMap.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index f818f1f6e0..62cca1f248 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -588,7 +588,7 @@ void CubeMap::convolveForGGX(CubeMap& output, const std::atomic<bool>& abortProc
     params.points.reserve(MAX_SAMPLE_COUNT);
 
     for (gpu::uint16 mipLevel = 0; mipLevel < mipCount; ++mipLevel) {
-        // This is the inverse code found in fragment.glsl in evaluateAmbientLighting
+        // This is the inverse code found in LightAmbient.slh in getMipLevelFromRoughness
         float levelAlpha = float(mipLevel) / (mipCount - ROUGHNESS_1_MIP_RESOLUTION);
         float mipRoughness = levelAlpha * (1.0f + 2.0f * levelAlpha) / 3.0f;
 
@@ -599,7 +599,7 @@ void CubeMap::convolveForGGX(CubeMap& output, const std::atomic<bool>& abortProc
         size_t sampleCount = 1U + size_t(4000 * mipRoughness * mipRoughness);
 
         sampleCount = std::min(sampleCount, 2 * mipTotalPixelCount);
-        sampleCount = std::min(MAX_SAMPLE_COUNT, 4 * mipTotalPixelCount);
+        sampleCount = std::min(MAX_SAMPLE_COUNT, sampleCount);
 
         params.points.resize(sampleCount);
         generateGGXSamples(params, mipRoughness, _width);

From ce0254e141a26c5fcace615692f4243d6ccab20e Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Mon, 1 Apr 2019 17:40:09 +0200
Subject: [PATCH 13/23] Fixed some crashes

---
 libraries/image/src/image/CubeMap.cpp | 105 +++++++++++++++++---------
 1 file changed, 70 insertions(+), 35 deletions(-)

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index 62cca1f248..e746aa25fe 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -82,18 +82,30 @@ public:
     }
 
     glm::vec4 fetch(int face, glm::vec2 uv) const {
-        glm::vec2 coordFrac = uv * glm::vec2(_dims) + 0.5f;
+        glm::vec2 coordFrac = uv * glm::vec2(_dims) - 0.5f;
         glm::vec2 coords = glm::floor(coordFrac);
 
         coordFrac -= coords;
 
-        const auto* pixels = _faces[face].data();
+        coords += 1.0f;
+
+        const auto& pixels = _faces[face];
         gpu::Vec2i loCoords(coords);
-        const int offset = loCoords.x + loCoords.y * _lineStride;
-        glm::vec4 colorLL = pixels[offset];
-        glm::vec4 colorHL = pixels[offset +1 ];
-        glm::vec4 colorLH = pixels[offset + _lineStride];
-        glm::vec4 colorHH = pixels[offset + 1 + _lineStride];
+
+        loCoords = glm::clamp(loCoords, gpu::Vec2i(0, 0), _dims);
+
+        const size_t offsetLL = loCoords.x + loCoords.y * _lineStride;
+        const size_t offsetHL = offsetLL + 1;
+        const size_t offsetLH = offsetLL + _lineStride;
+        const size_t offsetHH = offsetLH + 1;
+        assert(offsetLL >= 0 && offsetLL < (_dims.x + 2)*(_dims.y + 2));
+        assert(offsetHL >= 0 && offsetHL < (_dims.x + 2)*(_dims.y + 2));
+        assert(offsetLH >= 0 && offsetLH < (_dims.x + 2)*(_dims.y + 2));
+        assert(offsetHH >= 0 && offsetHH < (_dims.x + 2)*(_dims.y + 2));
+        glm::vec4 colorLL = pixels[offsetLL];
+        glm::vec4 colorHL = pixels[offsetHL];
+        glm::vec4 colorLH = pixels[offsetLH];
+        glm::vec4 colorHH = pixels[offsetHH];
 
         colorLL += (colorHL - colorLL) * coordFrac.x;
         colorLH += (colorHH - colorLH) * coordFrac.x;
@@ -120,7 +132,7 @@ public:
         // Copy edge rows and columns from neighbouring faces to fix seam filtering issues
         seamColumnAndRow(gpu::Texture::CUBE_FACE_TOP_POS_Y, _dims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, -1, -1);
         seamColumnAndRow(gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, _dims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, _dims.y, 1);
-        seamColumnAndColumn(gpu::Texture::CUBE_FACE_FRONT_NEG_Z, 0, gpu::Texture::CUBE_FACE_RIGHT_POS_X, _dims.x, 1);
+        seamColumnAndColumn(gpu::Texture::CUBE_FACE_FRONT_NEG_Z, -1, gpu::Texture::CUBE_FACE_RIGHT_POS_X, _dims.x, 1);
         seamColumnAndColumn(gpu::Texture::CUBE_FACE_BACK_POS_Z, _dims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, -1, 1);
 
         seamRowAndRow(gpu::Texture::CUBE_FACE_BACK_POS_Z, -1, gpu::Texture::CUBE_FACE_TOP_POS_Y, _dims.y, 1);
@@ -150,7 +162,7 @@ private:
 
     Faces& _faces;
 
-    inline static void copy(const glm::vec4* srcFirst, const glm::vec4* srcLast, int srcStride, glm::vec4* dstBegin, int dstStride) {
+    inline static void copy(CubeMap::Face::const_iterator srcFirst, CubeMap::Face::const_iterator srcLast, int srcStride, CubeMap::Face::iterator dstBegin, int dstStride) {
         while (srcFirst <= srcLast) {
             *dstBegin = *srcFirst;
             srcFirst += srcStride;
@@ -198,13 +210,18 @@ private:
 
     void copyColumnToColumn(int srcFace, int srcCol, int dstFace, int dstCol, const int dstInc) {
         const auto lastOffset = _lineStride * (_dims.y - 1);
-        auto srcFirst = _faces[srcFace].data() + srcCol + _lineStride;
+        auto srcFirst = _faces[srcFace].begin() + srcCol + _lineStride;
         auto srcLast = srcFirst + lastOffset;
 
-        auto dstFirst = _faces[dstFace].data() + dstCol + _lineStride;
+        auto dstFirst = _faces[dstFace].begin() + dstCol + _lineStride;
         auto dstLast = dstFirst + lastOffset;
         const auto dstStride = _lineStride * dstInc;
 
+        assert(srcFirst < _faces[srcFace].end());
+        assert(srcLast < _faces[srcFace].end());
+        assert(dstFirst < _faces[dstFace].end());
+        assert(dstLast < _faces[dstFace].end());
+
         if (dstInc < 0) {
             std::swap(dstFirst, dstLast);
         }
@@ -214,12 +231,17 @@ private:
 
     void copyRowToRow(int srcFace, int srcRow, int dstFace, int dstRow, const int dstInc) {
         const auto lastOffset =(_dims.x - 1);
-        auto srcFirst = _faces[srcFace].data() + srcRow * _lineStride + 1;
+        auto srcFirst = _faces[srcFace].begin() + srcRow * _lineStride + 1;
         auto srcLast = srcFirst + lastOffset;
 
-        auto dstFirst = _faces[dstFace].data() + dstRow * _lineStride + 1;
+        auto dstFirst = _faces[dstFace].begin() + dstRow * _lineStride + 1;
         auto dstLast = dstFirst + lastOffset;
 
+        assert(srcFirst < _faces[srcFace].end());
+        assert(srcLast < _faces[srcFace].end());
+        assert(dstFirst < _faces[dstFace].end());
+        assert(dstLast < _faces[dstFace].end());
+
         if (dstInc < 0) {
             std::swap(dstFirst, dstLast);
         }
@@ -229,13 +251,18 @@ private:
 
     void copyColumnToRow(int srcFace, int srcCol, int dstFace, int dstRow, int dstInc) {
         const auto srcLastOffset = _lineStride * (_dims.y - 1);
-        auto srcFirst = _faces[srcFace].data() + srcCol + _lineStride;
+        auto srcFirst = _faces[srcFace].begin() + srcCol + _lineStride;
         auto srcLast = srcFirst + srcLastOffset;
 
         const auto dstLastOffset = (_dims.x - 1);
-        auto dstFirst = _faces[dstFace].data() + dstRow * _lineStride + 1;
+        auto dstFirst = _faces[dstFace].begin() + dstRow * _lineStride + 1;
         auto dstLast = dstFirst + dstLastOffset;
 
+        assert(srcFirst < _faces[srcFace].end());
+        assert(srcLast < _faces[srcFace].end());
+        assert(dstFirst < _faces[dstFace].end());
+        assert(dstLast < _faces[dstFace].end());
+
         if (dstInc < 0) {
             std::swap(dstFirst, dstLast);
         }
@@ -245,14 +272,19 @@ private:
 
     void copyRowToColumn(int srcFace, int srcRow, int dstFace, int dstCol, int dstInc) {
         const auto srcLastOffset = (_dims.x - 1);
-        auto srcFirst = _faces[srcFace].data() + srcRow * _lineStride + 1;
+        auto srcFirst = _faces[srcFace].begin() + srcRow * _lineStride + 1;
         auto srcLast = srcFirst + srcLastOffset;
 
         const auto dstLastOffset = _lineStride * (_dims.y - 1);
-        auto dstFirst = _faces[dstFace].data() + dstCol + _lineStride;
+        auto dstFirst = _faces[dstFace].begin() + dstCol + _lineStride;
         auto dstLast = dstFirst + dstLastOffset;
         const auto dstStride = _lineStride * dstInc;
 
+        assert(srcFirst < _faces[srcFace].end());
+        assert(srcLast < _faces[srcFace].end());
+        assert(dstFirst < _faces[dstFace].end());
+        assert(dstLast < _faces[dstFace].end());
+
         if (dstInc < 0) {
             std::swap(dstFirst, dstLast);
         }
@@ -343,7 +375,7 @@ void CubeMap::copyFace(int width, int height, const glm::vec4* source, int srcLi
 }
 
 void CubeMap::reset(int width, int height, int mipCount) {
-    assert(mipCount >0 && _width > 0 && _height > 0);
+    assert(mipCount >0 && width > 0 && height > 0);
     _width = width;
     _height = height;
     _mips.resize(mipCount);
@@ -476,6 +508,8 @@ void CubeMap::getFaceUV(const glm::vec3& dir, int* index, glm::vec2* uv) {
 }
 
 glm::vec4 CubeMap::fetchLod(const glm::vec3& dir, float lod) const {
+    lod = glm::clamp<float>(lod, 0.0f, _mips.size() - 1);
+
     gpu::uint16 loLevel = (gpu::uint16)std::floor(lod);
     gpu::uint16 hiLevel = (gpu::uint16)std::ceil(lod);
     float lodFrac = lod - (float)loLevel;
@@ -615,32 +649,33 @@ void CubeMap::convolveForGGX(CubeMap& output, const std::atomic<bool>& abortProc
 
 void CubeMap::convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output, gpu::uint16 mipLevel, int face, const std::atomic<bool>& abortProcessing) const {
     const glm::vec3* faceNormals = FACE_NORMALS + face * 4;
-    const glm::vec3 deltaXNormalLo = faceNormals[1] - faceNormals[0];
-    const glm::vec3 deltaXNormalHi = faceNormals[3] - faceNormals[2];
+    const glm::vec3 deltaYNormalLo = faceNormals[2] - faceNormals[0];
+    const glm::vec3 deltaYNormalHi = faceNormals[3] - faceNormals[1];
+    auto mipDimensions = output.getMipDimensions(mipLevel);
     auto outputFacePixels = output.editFace(mipLevel, face);
     auto outputLineStride = output.getFaceLineStride(mipLevel);
 
-    tbb::parallel_for(tbb::blocked_range2d<int, int>(0, _width, 16, 0, _height, 16), [&](const tbb::blocked_range2d<int, int>& range) {
+    tbb::parallel_for(tbb::blocked_range2d<int, int>(0, mipDimensions.x, 16, 0, mipDimensions.y, 16), [&](const tbb::blocked_range2d<int, int>& range) {
         auto rowRange = range.rows();
         auto colRange = range.cols();
 
-        for (auto x = rowRange.begin(); x < rowRange.end(); x++) {
-            const float xAlpha = (x + 0.5f) / _width;
-            const glm::vec3 normalYLo = faceNormals[0] + deltaXNormalLo * xAlpha;
-            const glm::vec3 normalYHi = faceNormals[2] + deltaXNormalHi * xAlpha;
-            const glm::vec3 deltaYNormal = normalYHi - normalYLo;
-
-            for (auto y = colRange.begin(); y < colRange.end(); y++) {
-                const float yAlpha = (y + 0.5f) / _width;
-                // Interpolate normal for this pixel
-                const glm::vec3 normal = glm::normalize(normalYLo + deltaYNormal * yAlpha);
-
-                outputFacePixels[x + y * outputLineStride] = computeConvolution(normal, samples);
-            }
-
+        for (auto y = rowRange.begin(); y < rowRange.end(); y++) {
             if (abortProcessing.load()) {
                 break;
             }
+
+            const float yAlpha = (y + 0.5f) / _height;
+            const glm::vec3 normalXLo = faceNormals[0] + deltaYNormalLo * yAlpha;
+            const glm::vec3 normalXHi = faceNormals[1] + deltaYNormalHi * yAlpha;
+            const glm::vec3 deltaXNormal = normalXHi - normalXLo;
+
+            for (auto x = colRange.begin(); x < colRange.end(); x++) {
+                const float xAlpha = (x + 0.5f) / _width;
+                // Interpolate normal for this pixel
+                const glm::vec3 normal = glm::normalize(normalXLo + deltaXNormal * yAlpha);
+
+                outputFacePixels[x + y * outputLineStride] = computeConvolution(normal, samples);
+            }
         }
     });
 }

From 1aedfff6f7642bbcd94447daa8172d98616f302b Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Tue, 2 Apr 2019 15:40:42 +0200
Subject: [PATCH 14/23] Working convolution

---
 libraries/image/src/image/CubeMap.cpp         | 128 +++++++++++-------
 libraries/image/src/image/CubeMap.h           |  20 ++-
 .../image/src/image/TextureProcessing.cpp     |   3 +-
 libraries/image/src/image/TextureProcessing.h |   2 +-
 libraries/render-utils/src/LightAmbient.slh   |   5 +-
 5 files changed, 97 insertions(+), 61 deletions(-)

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index e746aa25fe..c8fd9cee80 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -63,7 +63,7 @@ struct CubeFaceMip {
 
     CubeFaceMip(gpu::uint16 level, const CubeMap* cubemap) {
         _dims = cubemap->getMipDimensions(level);
-        _lineStride = _dims.x + 2;
+        _lineStride = cubemap->getMipLineStride(level);
     }
 
     CubeFaceMip(const CubeFaceMip& other) : _dims(other._dims), _lineStride(other._lineStride) {
@@ -71,7 +71,7 @@ struct CubeFaceMip {
     }
 
     gpu::Vec2i _dims;
-    int _lineStride;
+    size_t _lineStride;
 };
 
 class CubeMap::ConstMip : public CubeFaceMip {
@@ -87,21 +87,23 @@ public:
 
         coordFrac -= coords;
 
-        coords += 1.0f;
+        coords += (float)EDGE_WIDTH;
 
         const auto& pixels = _faces[face];
         gpu::Vec2i loCoords(coords);
+        gpu::Vec2i hiCoords;
 
-        loCoords = glm::clamp(loCoords, gpu::Vec2i(0, 0), _dims);
+        hiCoords = glm::clamp(loCoords + 1, gpu::Vec2i(0, 0), _dims - 1 + (int)EDGE_WIDTH);
+        loCoords = glm::clamp(loCoords, gpu::Vec2i(0, 0), _dims - 1 + (int)EDGE_WIDTH);
 
         const size_t offsetLL = loCoords.x + loCoords.y * _lineStride;
-        const size_t offsetHL = offsetLL + 1;
-        const size_t offsetLH = offsetLL + _lineStride;
-        const size_t offsetHH = offsetLH + 1;
-        assert(offsetLL >= 0 && offsetLL < (_dims.x + 2)*(_dims.y + 2));
-        assert(offsetHL >= 0 && offsetHL < (_dims.x + 2)*(_dims.y + 2));
-        assert(offsetLH >= 0 && offsetLH < (_dims.x + 2)*(_dims.y + 2));
-        assert(offsetHH >= 0 && offsetHH < (_dims.x + 2)*(_dims.y + 2));
+        const size_t offsetHL = hiCoords.x + loCoords.y * _lineStride;
+        const size_t offsetLH = loCoords.x + hiCoords.y * _lineStride;
+        const size_t offsetHH = hiCoords.x + hiCoords.y * _lineStride;
+        assert(offsetLL >= 0 && offsetLL < _lineStride*(_dims.y + 2 * EDGE_WIDTH));
+        assert(offsetHL >= 0 && offsetHL < _lineStride*(_dims.y + 2 * EDGE_WIDTH));
+        assert(offsetLH >= 0 && offsetLH < _lineStride*(_dims.y + 2 * EDGE_WIDTH));
+        assert(offsetHH >= 0 && offsetHH < _lineStride*(_dims.y + 2 * EDGE_WIDTH));
         glm::vec4 colorLL = pixels[offsetLL];
         glm::vec4 colorHL = pixels[offsetHL];
         glm::vec4 colorLH = pixels[offsetLH];
@@ -129,6 +131,10 @@ public:
     }
 
     void applySeams() {
+        if (EDGE_WIDTH == 0) {
+            return;
+        }
+
         // Copy edge rows and columns from neighbouring faces to fix seam filtering issues
         seamColumnAndRow(gpu::Texture::CUBE_FACE_TOP_POS_Y, _dims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, -1, -1);
         seamColumnAndRow(gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y, _dims.x, gpu::Texture::CUBE_FACE_RIGHT_POS_X, _dims.y, 1);
@@ -162,7 +168,7 @@ private:
 
     Faces& _faces;
 
-    inline static void copy(CubeMap::Face::const_iterator srcFirst, CubeMap::Face::const_iterator srcLast, int srcStride, CubeMap::Face::iterator dstBegin, int dstStride) {
+    inline static void copy(CubeMap::Face::const_iterator srcFirst, CubeMap::Face::const_iterator srcLast, size_t srcStride, CubeMap::Face::iterator dstBegin, size_t dstStride) {
         while (srcFirst <= srcLast) {
             *dstBegin = *srcFirst;
             srcFirst += srcStride;
@@ -293,6 +299,26 @@ private:
     }
 };
 
+static void copySurface(const nvtt::Surface& source, glm::vec4* dest, size_t dstLineStride) {
+    const float* srcRedIt = source.channel(0);
+    const float* srcGreenIt = source.channel(1);
+    const float* srcBlueIt = source.channel(2);
+    const float* srcAlphaIt = source.channel(3);
+
+    for (int y = 0; y < source.height(); y++) {
+        glm::vec4* dstColIt = dest;
+        for (int x = 0; x < source.width(); x++) {
+            *dstColIt = glm::vec4(*srcRedIt, *srcGreenIt, *srcBlueIt, *srcAlphaIt);
+            dstColIt++;
+            srcRedIt++;
+            srcGreenIt++;
+            srcBlueIt++;
+            srcAlphaIt++;
+        }
+        dest += dstLineStride;
+    }
+}
+
 CubeMap::CubeMap(int width, int height, int mipCount) {
     reset(width, height, mipCount);
 }
@@ -327,32 +353,26 @@ CubeMap::CubeMap(const std::vector<Image>& faces, gpu::Element srcTextureFormat,
 
     int face;
 
-    struct MipMapErrorHandler : public nvtt::ErrorHandler {
-        virtual void error(nvtt::Error e) override {
-            qCWarning(imagelogging) << "Texture mip map creation error:" << nvtt::errorString(e);
-        }
-    };
+    nvtt::Surface surface;
+    surface.setAlphaMode(nvtt::AlphaMode_None);
+    surface.setWrapMode(nvtt::WrapMode_Mirror);
+
+    std::vector<glm::vec4> floatPixels;
+    floatPixels.resize(_width * _height);
 
     // Compute mips
     for (face = 0; face < 6; face++) {
-        auto sourcePixels = faces[face].getBits();
-        auto floatPixels = editFace(0, face);
-
-        convertToFloat(sourcePixels, _width, _height, faces[face].getBytesPerLineCount(), srcTextureFormat, floatPixels, _width);
-
-        nvtt::Surface surface;
-        surface.setImage(nvtt::InputFormat_RGBA_32F, _width, _height, 1, floatPixels);
-        surface.setAlphaMode(nvtt::AlphaMode_None);
-        surface.setWrapMode(nvtt::WrapMode_Clamp);
+        convertToFloat(faces[face].getBits(), _width, _height, faces[face].getBytesPerLineCount(), srcTextureFormat, floatPixels.data(), _width);
+        surface.setImage(nvtt::InputFormat_RGBA_32F, _width, _height, 1, &floatPixels.front().x);
 
         auto mipLevel = 0;
-        copyFace(_width, _height, reinterpret_cast<const glm::vec4*>(surface.data()), surface.width(), editFace(0, face), getFaceLineStride(0));
+        copySurface(surface, editFace(0, face), getMipLineStride(0));
 
         while (surface.canMakeNextMipmap() && !abortProcessing.load()) {
             surface.buildNextMipmap(nvtt::MipmapFilter_Box);
             mipLevel++;
 
-            copyFace(surface.width(), surface.height(), reinterpret_cast<const glm::vec4*>(surface.data()), surface.width(), editFace(mipLevel, face), getFaceLineStride(mipLevel));
+            copySurface(surface, editFace(mipLevel, face), getMipLineStride(mipLevel));
         }
     }
 
@@ -366,7 +386,7 @@ CubeMap::CubeMap(const std::vector<Image>& faces, gpu::Element srcTextureFormat,
     }
 }
 
-void CubeMap::copyFace(int width, int height, const glm::vec4* source, int srcLineStride, glm::vec4* dest, int dstLineStride) {
+void CubeMap::copyFace(int width, int height, const glm::vec4* source, size_t srcLineStride, glm::vec4* dest, size_t dstLineStride) {
     for (int y = 0; y < height; y++) {
         std::copy(source, source + width, dest);
         source += srcLineStride;
@@ -383,7 +403,7 @@ void CubeMap::reset(int width, int height, int mipCount) {
         auto mipDimensions = getMipDimensions(mipLevel);
         // Add extra pixels on edges to perform edge seam fixup (we will duplicate pixels from
         // neighbouring faces)
-        auto mipPixelCount = (mipDimensions.x+2) * (mipDimensions.y+2);
+        auto mipPixelCount = (mipDimensions.x + 2 * EDGE_WIDTH) * (mipDimensions.y + 2 * EDGE_WIDTH);
 
         for (auto& face : _mips[mipLevel]) {
             face.resize(mipPixelCount);
@@ -391,6 +411,12 @@ void CubeMap::reset(int width, int height, int mipCount) {
     }
 }
 
+void CubeMap::copyTo(CubeMap& other) const {
+    other._width = _width;
+    other._height = _height;
+    other._mips = _mips;
+}
+
 void CubeMap::copyTo(gpu::Texture* texture, const std::atomic<bool>& abortProcessing) const {
     assert(_width == texture->getWidth() && _height == texture->getHeight() && texture->getNumMips() == _mips.size());
 
@@ -407,24 +433,27 @@ void CubeMap::copyTo(gpu::Texture* texture, const std::atomic<bool>& abortProces
 
     nvtt::Surface surface;
     surface.setAlphaMode(nvtt::AlphaMode_None);
-    surface.setWrapMode(nvtt::WrapMode_Clamp);
+    surface.setWrapMode(nvtt::WrapMode_Mirror);
+
+    std::vector<glm::vec4> floatPixels;
+    floatPixels.resize(_width * _height);
+
+    nvtt::CompressionOptions compressionOptions;
+
+    SequentialTaskDispatcher dispatcher(abortProcessing);
+    nvtt::Context context;
+    context.setTaskDispatcher(&dispatcher);
 
-    glm::vec4* packedPixels = new glm::vec4[_width * _height];
     for (int face = 0; face < 6; face++) {
-        nvtt::CompressionOptions compressionOptions;
-        std::unique_ptr<nvtt::OutputHandler> outputHandler{ getNVTTCompressionOutputHandler(texture, face, compressionOptions) };
-
-        outputOptions.setOutputHandler(outputHandler.get());
-
-        SequentialTaskDispatcher dispatcher(abortProcessing);
-        nvtt::Context context;
-        context.setTaskDispatcher(&dispatcher);
-
         for (gpu::uint16 mipLevel = 0; mipLevel < _mips.size() && !abortProcessing.load(); mipLevel++) {
             auto mipDims = getMipDimensions(mipLevel);
 
-            copyFace(mipDims.x, mipDims.y, getFace(mipLevel, face), getFaceLineStride(mipLevel), packedPixels, mipDims.x);
-            surface.setImage(nvtt::InputFormat_RGBA_32F, mipDims.x, mipDims.y, 1, packedPixels);
+            std::unique_ptr<nvtt::OutputHandler> outputHandler{ getNVTTCompressionOutputHandler(texture, face, compressionOptions) };
+
+            outputOptions.setOutputHandler(outputHandler.get());
+
+            copyFace(mipDims.x, mipDims.y, getFace(mipLevel, face), getMipLineStride(mipLevel), &floatPixels.front(), mipDims.x);
+            surface.setImage(nvtt::InputFormat_RGBA_32F, mipDims.x, mipDims.y, 1, &floatPixels.front().x);
             context.compress(surface, face, mipLevel, compressionOptions, outputOptions);
         }
 
@@ -432,7 +461,6 @@ void CubeMap::copyTo(gpu::Texture* texture, const std::atomic<bool>& abortProces
             break;
         }
     }
-    delete[] packedPixels;
 }
 
 void CubeMap::getFaceUV(const glm::vec3& dir, int* index, glm::vec2* uv) {
@@ -651,11 +679,11 @@ void CubeMap::convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output,
     const glm::vec3* faceNormals = FACE_NORMALS + face * 4;
     const glm::vec3 deltaYNormalLo = faceNormals[2] - faceNormals[0];
     const glm::vec3 deltaYNormalHi = faceNormals[3] - faceNormals[1];
-    auto mipDimensions = output.getMipDimensions(mipLevel);
+    const auto mipDimensions = output.getMipDimensions(mipLevel);
+    const auto outputLineStride = output.getMipLineStride(mipLevel);
     auto outputFacePixels = output.editFace(mipLevel, face);
-    auto outputLineStride = output.getFaceLineStride(mipLevel);
 
-    tbb::parallel_for(tbb::blocked_range2d<int, int>(0, mipDimensions.x, 16, 0, mipDimensions.y, 16), [&](const tbb::blocked_range2d<int, int>& range) {
+    tbb::parallel_for(tbb::blocked_range2d<int, int>(0, mipDimensions.x, 32, 0, mipDimensions.y, 32), [&](const tbb::blocked_range2d<int, int>& range) {
         auto rowRange = range.rows();
         auto colRange = range.cols();
 
@@ -664,15 +692,15 @@ void CubeMap::convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output,
                 break;
             }
 
-            const float yAlpha = (y + 0.5f) / _height;
+            const float yAlpha = (y + 0.5f) / mipDimensions.y;
             const glm::vec3 normalXLo = faceNormals[0] + deltaYNormalLo * yAlpha;
             const glm::vec3 normalXHi = faceNormals[1] + deltaYNormalHi * yAlpha;
             const glm::vec3 deltaXNormal = normalXHi - normalXLo;
 
             for (auto x = colRange.begin(); x < colRange.end(); x++) {
-                const float xAlpha = (x + 0.5f) / _width;
+                const float xAlpha = (x + 0.5f) / mipDimensions.x;
                 // Interpolate normal for this pixel
-                const glm::vec3 normal = glm::normalize(normalXLo + deltaXNormal * yAlpha);
+                const glm::vec3 normal = glm::normalize(normalXLo + deltaXNormal * xAlpha);
 
                 outputFacePixels[x + y * outputLineStride] = computeConvolution(normal, samples);
             }
diff --git a/libraries/image/src/image/CubeMap.h b/libraries/image/src/image/CubeMap.h
index 808f6eea42..6f867ce57a 100644
--- a/libraries/image/src/image/CubeMap.h
+++ b/libraries/image/src/image/CubeMap.h
@@ -23,6 +23,11 @@
 namespace image {
 
     class CubeMap {
+
+        enum {
+            EDGE_WIDTH = 1
+        };
+
     public:
  
         CubeMap(int width, int height, int mipCount);
@@ -30,6 +35,7 @@ namespace image {
 
         void reset(int width, int height, int mipCount);
         void copyTo(gpu::Texture* texture, const std::atomic<bool>& abortProcessing = false) const;
+        void copyTo(CubeMap& other) const;
 
         gpu::uint16 getMipCount() const { return (gpu::uint16)_mips.size(); }
         int getMipWidth(gpu::uint16 mipLevel) const {
@@ -42,16 +48,16 @@ namespace image {
             return gpu::Vec2i(getMipWidth(mipLevel), getMipHeight(mipLevel));
         }
 
+        size_t getMipLineStride(gpu::uint16 mipLevel) const {
+            return getMipWidth(mipLevel) + 2 * EDGE_WIDTH;
+        }
+
         glm::vec4* editFace(gpu::uint16 mipLevel, int face) {
-            return _mips[mipLevel][face].data() + getFaceLineStride(mipLevel) + 1;
+            return _mips[mipLevel][face].data() + (getMipLineStride(mipLevel) + 1)*EDGE_WIDTH;
         }
 
         const glm::vec4* getFace(gpu::uint16 mipLevel, int face) const {
-            return _mips[mipLevel][face].data() + getFaceLineStride(mipLevel) + 1;
-        }
-
-        size_t getFaceLineStride(gpu::uint16 mipLevel) const {
-            return getMipWidth(mipLevel)+2;
+            return _mips[mipLevel][face].data() + (getMipLineStride(mipLevel) + 1)*EDGE_WIDTH;
         }
 
         void convolveForGGX(CubeMap& output, const std::atomic<bool>& abortProcessing) const;
@@ -73,7 +79,7 @@ namespace image {
 
         static void getFaceUV(const glm::vec3& dir, int* index, glm::vec2* uv);
         static void generateGGXSamples(GGXSamples& data, float roughness, const int resolution);
-        static void copyFace(int width, int height, const glm::vec4* source, int srcLineStride, glm::vec4* dest, int dstLineStride);
+        static void copyFace(int width, int height, const glm::vec4* source, size_t srcLineStride, glm::vec4* dest, size_t dstLineStride);
         void convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output, gpu::uint16 mipLevel, int face, const std::atomic<bool>& abortProcessing) const;
         glm::vec4 computeConvolution(const glm::vec3& normal, const GGXSamples& samples) const;
 
diff --git a/libraries/image/src/image/TextureProcessing.cpp b/libraries/image/src/image/TextureProcessing.cpp
index 00e6fd806d..ac0c17d115 100644
--- a/libraries/image/src/image/TextureProcessing.cpp
+++ b/libraries/image/src/image/TextureProcessing.cpp
@@ -1581,8 +1581,9 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(Image&& srcIm
             auto irradiance = irradianceTexture->getIrradiance();
             theTexture->overrideIrradiance(irradiance);
         }
-
+        
         if (options & CUBE_GGX_CONVOLVE) {
+            // Performs and convolution AND mip map generation
             convolveForGGX(faces, GPU_CUBEMAP_HDR_FORMAT, theTexture.get(), abortProcessing);
         } else {
             // Create mip maps and compress to final format in one go
diff --git a/libraries/image/src/image/TextureProcessing.h b/libraries/image/src/image/TextureProcessing.h
index 7d1c483155..378e68228a 100644
--- a/libraries/image/src/image/TextureProcessing.h
+++ b/libraries/image/src/image/TextureProcessing.h
@@ -105,7 +105,7 @@ gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::
 #if defined(NVTT_API)
 class SequentialTaskDispatcher : public nvtt::TaskDispatcher {
 public:
-    SequentialTaskDispatcher(const std::atomic<bool>& abortProcessing);
+    SequentialTaskDispatcher(const std::atomic<bool>& abortProcessing = false);
 
     const std::atomic<bool>& _abortProcessing;
 
diff --git a/libraries/render-utils/src/LightAmbient.slh b/libraries/render-utils/src/LightAmbient.slh
index 0c7130b110..8afcb6ccd3 100644
--- a/libraries/render-utils/src/LightAmbient.slh
+++ b/libraries/render-utils/src/LightAmbient.slh
@@ -17,8 +17,9 @@ vec4 evalSkyboxLight(vec3 direction, float lod) {
 
 #if !defined(GL_ES)
     float filterLod = textureQueryLod(skyboxMap, direction).x;
-    // Keep texture filtering LOD as limit to prevent aliasing on specular reflection
-    lod = max(lod, filterLod);
+    // Keep texture filtering LOD as limit to prevent aliasing on specular reflection, but add
+    // a bias to limit overblurring with convolved maps
+    lod = max(lod, filterLod-2);
 #endif
 
     return textureLod(skyboxMap, direction, lod);

From e3355cd6a4b01a9c910c15925750b52c8ef9ab6f Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Wed, 3 Apr 2019 10:26:39 +0200
Subject: [PATCH 15/23] Trying to work uniformly with Image

---
 libraries/image/src/image/CubeMap.cpp         |  82 +-----
 libraries/image/src/image/CubeMap.h           |   4 +-
 libraries/image/src/image/Image.cpp           | 237 +++++++++++++++---
 libraries/image/src/image/Image.h             |  85 +++++--
 .../image/src/image/TextureProcessing.cpp     | 193 +++++++-------
 libraries/image/src/image/TextureProcessing.h |  18 +-
 libraries/render-utils/src/LightAmbient.slh   |   2 +-
 7 files changed, 383 insertions(+), 238 deletions(-)

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index c8fd9cee80..1e25ffb9c0 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -18,8 +18,6 @@
 #include "TextureProcessing.h"
 #include "ImageLogging.h"
 
-#include <nvtt/nvtt.h>
-
 #ifndef M_PI
 #define M_PI    3.14159265359
 #endif
@@ -323,31 +321,6 @@ CubeMap::CubeMap(int width, int height, int mipCount) {
     reset(width, height, mipCount);
 }
 
-struct CubeMap::MipMapOutputHandler : public nvtt::OutputHandler {
-    MipMapOutputHandler(CubeMap* cube) : _cubemap(cube) {}
-
-    void beginImage(int size, int width, int height, int depth, int face, int miplevel) override {
-        _data = _cubemap->editFace(miplevel, face);
-        _current = _data;
-    }
-
-    bool writeData(const void* data, int size) override {
-        assert((size % sizeof(glm::vec4)) == 0);
-        memcpy(_current, data, size);
-        _current += size / sizeof(glm::vec4);
-        return true;
-    }
-
-    void endImage() override {
-        _data = nullptr;
-        _current = nullptr;
-    }
-
-    CubeMap* _cubemap{ nullptr };
-    glm::vec4* _data{ nullptr };
-    glm::vec4* _current{ nullptr };
-};
-
 CubeMap::CubeMap(const std::vector<Image>& faces, gpu::Element srcTextureFormat, int mipCount, const std::atomic<bool>& abortProcessing) {
     reset(faces.front().getWidth(), faces.front().getHeight(), mipCount);
 
@@ -362,7 +335,7 @@ CubeMap::CubeMap(const std::vector<Image>& faces, gpu::Element srcTextureFormat,
 
     // Compute mips
     for (face = 0; face < 6; face++) {
-        convertToFloat(faces[face].getBits(), _width, _height, faces[face].getBytesPerLineCount(), srcTextureFormat, floatPixels.data(), _width);
+        convertToFloatFromPacked(faces[face].getBits(), _width, _height, faces[face].getBytesPerLineCount(), srcTextureFormat, floatPixels.data(), _width);
         surface.setImage(nvtt::InputFormat_RGBA_32F, _width, _height, 1, &floatPixels.front().x);
 
         auto mipLevel = 0;
@@ -394,6 +367,13 @@ void CubeMap::copyFace(int width, int height, const glm::vec4* source, size_t sr
     }
 }
 
+Image CubeMap::getFaceImage(gpu::uint16 mipLevel, int face) const {
+    auto mipDims = getMipDimensions(mipLevel);
+    Image faceImage(mipDims.x, mipDims.y, Image::Format_RGBAF);
+    copyFace(mipDims.x, mipDims.y, getFace(mipLevel, face), getMipLineStride(mipLevel), (glm::vec4*)faceImage.editBits(), faceImage.getBytesPerLineCount() / sizeof(glm::vec4));
+    return faceImage;
+}
+
 void CubeMap::reset(int width, int height, int mipCount) {
     assert(mipCount >0 && width > 0 && height > 0);
     _width = width;
@@ -417,52 +397,6 @@ void CubeMap::copyTo(CubeMap& other) const {
     other._mips = _mips;
 }
 
-void CubeMap::copyTo(gpu::Texture* texture, const std::atomic<bool>& abortProcessing) const {
-    assert(_width == texture->getWidth() && _height == texture->getHeight() && texture->getNumMips() == _mips.size());
-
-    struct CompressionpErrorHandler : public nvtt::ErrorHandler {
-        virtual void error(nvtt::Error e) override {
-            qCWarning(imagelogging) << "Texture compression error:" << nvtt::errorString(e);
-        }
-    };
-
-    CompressionpErrorHandler errorHandler;
-    nvtt::OutputOptions outputOptions;
-    outputOptions.setOutputHeader(false);
-    outputOptions.setErrorHandler(&errorHandler);
-
-    nvtt::Surface surface;
-    surface.setAlphaMode(nvtt::AlphaMode_None);
-    surface.setWrapMode(nvtt::WrapMode_Mirror);
-
-    std::vector<glm::vec4> floatPixels;
-    floatPixels.resize(_width * _height);
-
-    nvtt::CompressionOptions compressionOptions;
-
-    SequentialTaskDispatcher dispatcher(abortProcessing);
-    nvtt::Context context;
-    context.setTaskDispatcher(&dispatcher);
-
-    for (int face = 0; face < 6; face++) {
-        for (gpu::uint16 mipLevel = 0; mipLevel < _mips.size() && !abortProcessing.load(); mipLevel++) {
-            auto mipDims = getMipDimensions(mipLevel);
-
-            std::unique_ptr<nvtt::OutputHandler> outputHandler{ getNVTTCompressionOutputHandler(texture, face, compressionOptions) };
-
-            outputOptions.setOutputHandler(outputHandler.get());
-
-            copyFace(mipDims.x, mipDims.y, getFace(mipLevel, face), getMipLineStride(mipLevel), &floatPixels.front(), mipDims.x);
-            surface.setImage(nvtt::InputFormat_RGBA_32F, mipDims.x, mipDims.y, 1, &floatPixels.front().x);
-            context.compress(surface, face, mipLevel, compressionOptions, outputOptions);
-        }
-
-        if (abortProcessing.load()) {
-            break;
-        }
-    }
-}
-
 void CubeMap::getFaceUV(const glm::vec3& dir, int* index, glm::vec2* uv) {
     // Taken from https://en.wikipedia.org/wiki/Cube_mapping
     float absX = std::abs(dir.x);
diff --git a/libraries/image/src/image/CubeMap.h b/libraries/image/src/image/CubeMap.h
index 6f867ce57a..100164d7df 100644
--- a/libraries/image/src/image/CubeMap.h
+++ b/libraries/image/src/image/CubeMap.h
@@ -34,7 +34,6 @@ namespace image {
         CubeMap(const std::vector<Image>& faces, gpu::Element faceFormat, int mipCount, const std::atomic<bool>& abortProcessing = false);
 
         void reset(int width, int height, int mipCount);
-        void copyTo(gpu::Texture* texture, const std::atomic<bool>& abortProcessing = false) const;
         void copyTo(CubeMap& other) const;
 
         gpu::uint16 getMipCount() const { return (gpu::uint16)_mips.size(); }
@@ -60,13 +59,14 @@ namespace image {
             return _mips[mipLevel][face].data() + (getMipLineStride(mipLevel) + 1)*EDGE_WIDTH;
         }
 
+        Image getFaceImage(gpu::uint16 mipLevel, int face) const;
+
         void convolveForGGX(CubeMap& output, const std::atomic<bool>& abortProcessing) const;
         glm::vec4 fetchLod(const glm::vec3& dir, float lod) const;
 
     private:
 
         struct GGXSamples;
-        struct MipMapOutputHandler;
         class Mip;
         class ConstMip;
 
diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp
index 25e9ac3f59..0752783355 100644
--- a/libraries/image/src/image/Image.cpp
+++ b/libraries/image/src/image/Image.cpp
@@ -6,28 +6,91 @@
 
 using namespace image;
 
+Image::Image(int width, int height, Format format) : 
+    _dims(width, height), 
+    _format(format) {
+    if (_format == Format_RGBAF) {
+        _floatData.resize(width*height);
+    } else {
+        _packedData = QImage(width, height, (QImage::Format)format);
+    }
+}
+
+size_t Image::getByteCount() const {
+    if (_format == Format_RGBAF) {
+        return sizeof(FloatPixels::value_type) * _floatData.size();
+    } else {
+        return _packedData.byteCount();
+    }
+}
+
+size_t Image::getBytesPerLineCount() const { 
+    if (_format == Format_RGBAF) {
+        return sizeof(FloatPixels::value_type) * _dims.x;
+    } else {
+        return _packedData.bytesPerLine();
+    }
+}
+
+glm::uint8* Image::editScanLine(int y) {
+    if (_format == Format_RGBAF) {
+        return reinterpret_cast<glm::uint8*>(_floatData.data() + y * _dims.x);
+    } else {
+        return _packedData.scanLine(y);
+    }
+}
+
+const glm::uint8* Image::getScanLine(int y) const { 
+    if (_format == Format_RGBAF) {
+        return reinterpret_cast<const glm::uint8*>(_floatData.data() + y * _dims.x);
+    } else {
+        return _packedData.scanLine(y);
+    }
+}
+
+glm::uint8* Image::editBits() {
+    if (_format == Format_RGBAF) {
+        return reinterpret_cast<glm::uint8*>(_floatData.data());
+    } else {
+        return _packedData.bits();
+    }
+}
+
+const glm::uint8* Image::getBits() const {
+    if (_format == Format_RGBAF) {
+        return reinterpret_cast<const glm::uint8*>(_floatData.data());
+    } else {
+        return _packedData.bits();
+    }
+}
+
 Image Image::getScaled(glm::uvec2 dstSize, AspectRatioMode ratioMode, TransformationMode transformMode) const {
-    if (_data.format() == Image::Format_PACKED_FLOAT) {
-        // Start by converting to full float
-        glm::vec4* floatPixels = new glm::vec4[getWidth()*getHeight()];
-        auto unpackFunc = getHDRUnpackingFunction();
-        auto floatDataIt = floatPixels;
-        for (auto lineNb = 0; lineNb < getHeight(); lineNb++) {
-            const glm::uint32* srcPixelIt = reinterpret_cast<const glm::uint32*>(getScanLine(lineNb));
-            const glm::uint32* srcPixelEnd = srcPixelIt + getWidth();
-
-            while (srcPixelIt < srcPixelEnd) {
-                *floatDataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f);
-                ++srcPixelIt;
-                ++floatDataIt;
-            }
-        }
-
-        // Perform filtered resize with NVTT
-        static_assert(sizeof(glm::vec4) == 4 * sizeof(float), "Assuming glm::vec4 holds 4 floats");
+    if (_format == Format_PACKED_FLOAT || _format == Format_RGBAF) {
         nvtt::Surface surface;
-        surface.setImage(nvtt::InputFormat_RGBA_32F, getWidth(), getHeight(), 1, floatPixels);
-        delete[] floatPixels;
+
+        if (_format == Format_RGBAF) {
+            surface.setImage(nvtt::InputFormat_RGBA_32F, getWidth(), getHeight(), 1, _floatData.data());
+        } else {
+            // Start by converting to full float
+            glm::vec4* floatPixels = new glm::vec4[getWidth()*getHeight()];
+            auto unpackFunc = getHDRUnpackingFunction();
+            auto floatDataIt = floatPixels;
+            for (auto lineNb = 0; lineNb < getHeight(); lineNb++) {
+                const glm::uint32* srcPixelIt = reinterpret_cast<const glm::uint32*>(getScanLine(lineNb));
+                const glm::uint32* srcPixelEnd = srcPixelIt + getWidth();
+
+                while (srcPixelIt < srcPixelEnd) {
+                    *floatDataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f);
+                    ++srcPixelIt;
+                    ++floatDataIt;
+                }
+            }
+
+            // Perform filtered resize with NVTT
+            static_assert(sizeof(glm::vec4) == 4 * sizeof(float), "Assuming glm::vec4 holds 4 floats");
+            surface.setImage(nvtt::InputFormat_RGBA_32F, getWidth(), getHeight(), 1, floatPixels);
+            delete[] floatPixels;
+        }
 
         nvtt::ResizeFilter filter = nvtt::ResizeFilter_Kaiser;
         if (transformMode == Qt::TransformationMode::FastTransformation) {
@@ -35,44 +98,148 @@ Image Image::getScaled(glm::uvec2 dstSize, AspectRatioMode ratioMode, Transforma
         }
         surface.resize(dstSize.x, dstSize.y, 1, nvtt::ResizeFilter_Box);
 
-        // And convert back to original format
-        QImage resizedImage((int)dstSize.x, (int)dstSize.y, (QImage::Format)Image::Format_PACKED_FLOAT);
-
-        auto packFunc = getHDRPackingFunction();
         auto srcRedIt = reinterpret_cast<const float*>(surface.channel(0));
         auto srcGreenIt = reinterpret_cast<const float*>(surface.channel(1));
         auto srcBlueIt = reinterpret_cast<const float*>(surface.channel(2));
-        for (auto lineNb = 0; lineNb < dstSize.y; lineNb++) {
-            glm::uint32* dstPixelIt = reinterpret_cast<glm::uint32*>(resizedImage.scanLine(lineNb));
-            glm::uint32* dstPixelEnd = dstPixelIt + dstSize.x;
+        auto srcAlphaIt = reinterpret_cast<const float*>(surface.channel(3));
+
+        if (_format == Format_RGBAF) {
+            Image output(_dims.x, _dims.y, _format);
+            auto dstPixelIt = output._floatData.begin();
+            auto dstPixelEnd = output._floatData.end();
 
             while (dstPixelIt < dstPixelEnd) {
-                *dstPixelIt = packFunc(glm::vec3(*srcRedIt, *srcGreenIt, *srcBlueIt));
+                *dstPixelIt = glm::vec4(*srcRedIt, *srcGreenIt, *srcBlueIt, *srcAlphaIt);
                 ++srcRedIt;
                 ++srcGreenIt;
                 ++srcBlueIt;
+                ++srcAlphaIt;
+
                 ++dstPixelIt;
             }
+
+            return output;
+        } else {
+            // And convert back to original format
+            QImage resizedImage((int)dstSize.x, (int)dstSize.y, (QImage::Format)Image::Format_PACKED_FLOAT);
+
+            auto packFunc = getHDRPackingFunction();
+            for (auto lineNb = 0; lineNb < dstSize.y; lineNb++) {
+                glm::uint32* dstPixelIt = reinterpret_cast<glm::uint32*>(resizedImage.scanLine(lineNb));
+                glm::uint32* dstPixelEnd = dstPixelIt + dstSize.x;
+
+                while (dstPixelIt < dstPixelEnd) {
+                    *dstPixelIt = packFunc(glm::vec3(*srcRedIt, *srcGreenIt, *srcBlueIt));
+                    ++srcRedIt;
+                    ++srcGreenIt;
+                    ++srcBlueIt;
+                    ++dstPixelIt;
+                }
+            }
+            return resizedImage;
         }
-        return resizedImage;
     } else {
-        return _data.scaled(fromGlm(dstSize), ratioMode, transformMode);
+        return _packedData.scaled(fromGlm(dstSize), ratioMode, transformMode);
     }
 }
 
 Image Image::getConvertedToFormat(Format newFormat) const {
-    assert(getFormat() != Format_PACKED_FLOAT);
-    return _data.convertToFormat((QImage::Format)newFormat);
+    const float MAX_COLOR_VALUE = 255.0f;
+
+    if (newFormat == _format) {
+        return *this;
+    } else if ((_format != Format_R11G11B10F && _format != Format_RGBAF) && (newFormat != Format_R11G11B10F && newFormat != Format_RGBAF)) {
+        return _packedData.convertToFormat((QImage::Format)newFormat);
+    } else if (_format == Format_PACKED_FLOAT) {
+        Image newImage(_dims.x, _dims.y, newFormat);
+
+        switch (newFormat) {
+            case Format_RGBAF:
+                convertToFloatFromPacked(getBits(), _dims.x, _dims.y, getBytesPerLineCount(), gpu::Element::COLOR_R11G11B10, newImage._floatData.data(), _dims.x);
+                break;
+
+            default:
+            {
+                auto unpackFunc = getHDRUnpackingFunction();
+                const glm::uint32* srcIt = reinterpret_cast<const glm::uint32*>(getBits());
+
+                for (int y = 0; y < _dims.y; y++) {
+                    for (int x = 0; x < _dims.x; x++) {
+                        auto color = glm::clamp(unpackFunc(*srcIt) * MAX_COLOR_VALUE, 0.0f, 255.0f);
+                        newImage.setPackedPixel(x, y, qRgb(color.r, color.g, color.b));
+                        srcIt++;
+                    }
+                }
+                break;
+            }
+        }
+        return newImage;
+    } else if (_format == Format_RGBAF) {
+        Image newImage(_dims.x, _dims.y, newFormat);
+
+        switch (newFormat) {
+            case Format_R11G11B10F:
+                convertToPackedFromFloat(newImage.editBits(), _dims.x, _dims.y, getBytesPerLineCount(), gpu::Element::COLOR_R11G11B10, _floatData.data(), _dims.x);
+                break;
+
+            default:
+            {
+                FloatPixels::const_iterator srcIt = _floatData.begin();
+
+                for (int y = 0; y < _dims.y; y++) {
+                    for (int x = 0; x < _dims.x; x++) {
+                        auto color = glm::clamp((*srcIt) * MAX_COLOR_VALUE, 0.0f, 255.0f);
+                        newImage.setPackedPixel(x, y, qRgba(color.r, color.g, color.b, color.a));
+                        srcIt++;
+                    }
+                }
+                break;
+            }
+        }
+        return newImage;
+    } else {
+        Image newImage(_dims.x, _dims.y, newFormat);
+        assert(newImage.hasFloatFormat());
+
+        if (newFormat == Format_RGBAF) {
+            FloatPixels::iterator dstIt = newImage._floatData.begin();
+
+            for (int y = 0; y < _dims.y; y++) {
+                auto line = (const QRgb*)getScanLine(y);
+                for (int x = 0; x < _dims.x; x++) {
+                    QRgb pixel = line[x];
+                    *dstIt = glm::vec4(qRed(pixel), qGreen(pixel), qBlue(pixel), qAlpha(pixel)) / MAX_COLOR_VALUE;
+                    dstIt++;
+                }
+            }
+        } else {
+            auto packFunc = getHDRPackingFunction();
+            glm::uint32* dstIt = reinterpret_cast<glm::uint32*>( newImage.editBits() );
+
+            for (int y = 0; y < _dims.y; y++) {
+                auto line = (const QRgb*)getScanLine(y);
+                for (int x = 0; x < _dims.x; x++) {
+                    QRgb pixel = line[x];
+                    *dstIt = packFunc(glm::vec3(qRed(pixel), qGreen(pixel), qBlue(pixel)) / MAX_COLOR_VALUE);
+                    dstIt++;
+                }
+            }
+        }
+        return newImage;
+    }
 }
 
 void Image::invertPixels() {
-    _data.invertPixels(QImage::InvertRgba);
+    assert(_format != Format_PACKED_FLOAT && _format != Format_RGBAF);
+    _packedData.invertPixels(QImage::InvertRgba);
 }
 
 Image Image::getSubImage(QRect rect) const {
-    return _data.copy(rect);
+    assert(_format != Format_RGBAF);
+    return _packedData.copy(rect);
 }
 
 Image Image::getMirrored(bool horizontal, bool vertical) const {
-    return _data.mirrored(horizontal, vertical);
+    assert(_format != Format_RGBAF);
+    return _packedData.mirrored(horizontal, vertical);
 }
diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h
index 7ed4f80370..129061900f 100644
--- a/libraries/image/src/image/Image.h
+++ b/libraries/image/src/image/Image.h
@@ -48,38 +48,69 @@ namespace image {
             Format_RGBA8888_Premultiplied = QImage::Format_RGBA8888_Premultiplied,
             Format_Grayscale8 = QImage::Format_Grayscale8,
             Format_R11G11B10F = QImage::Format_RGB30,
-            Format_PACKED_FLOAT = Format_R11G11B10F
+            Format_PACKED_FLOAT = Format_R11G11B10F,
+            // RGBA 32 bit single precision float per component
+            Format_RGBAF = 100
         };
 
         using AspectRatioMode = Qt::AspectRatioMode;
         using TransformationMode = Qt::TransformationMode;
 
-        Image() {}
-        Image(int width, int height, Format format) : _data(width, height, (QImage::Format)format) {}
-        Image(const QImage& data) : _data(data) {}
-        void operator=(const QImage& image) {
-            _data = image;
+        Image() : _dims(0,0) {}
+        Image(int width, int height, Format format);
+        Image(const QImage& data) : _packedData(data), _dims(data.width(), data.height()), _format((Format)data.format()) {}
+
+        void operator=(const QImage& other) {
+            _packedData = other;
+            _floatData.clear();
+            _dims.x = other.width();
+            _dims.y = other.height();
+            _format = (Format)other.format();
         }
 
-        bool isNull() const { return _data.isNull(); }
-
-        Format getFormat() const { return (Format)_data.format(); }
-        bool hasAlphaChannel() const { return _data.hasAlphaChannel(); }
-
-        glm::uint32 getWidth() const { return (glm::uint32)_data.width(); }
-        glm::uint32 getHeight() const { return (glm::uint32)_data.height(); }
-        glm::uvec2 getSize() const { return toGlm(_data.size()); }
-        size_t getByteCount() const { return _data.byteCount(); }
-        size_t getBytesPerLineCount() const { return _data.bytesPerLine(); }
-
-        QRgb getPixel(int x, int y) const { return _data.pixel(x, y); }
-        void setPixel(int x, int y, QRgb value) {
-            _data.setPixel(x, y, value);
+        void operator=(const Image& other) {
+            if (&other != this) {
+                _packedData = other._packedData;
+                _floatData = other._floatData;
+                _dims = other._dims;
+                _format = other._format;
+            }
         }
 
-        glm::uint8* editScanLine(int y) { return _data.scanLine(y); }
-        const glm::uint8* getScanLine(int y) const { return _data.scanLine(y); }
-        const glm::uint8* getBits() const { return _data.constBits(); }
+        bool isNull() const { return _packedData.isNull() && _floatData.empty(); }
+
+        Format getFormat() const { return _format; }
+        bool hasAlphaChannel() const { return _packedData.hasAlphaChannel() || _format == Format_RGBAF; }
+        bool hasFloatFormat() const { return _format == Format_R11G11B10F || _format == Format_RGBAF; }
+
+        glm::uint32 getWidth() const { return (glm::uint32)_dims.x; }
+        glm::uint32 getHeight() const { return (glm::uint32)_dims.y; }
+        glm::uvec2 getSize() const { return glm::uvec2(_dims); }
+        size_t getByteCount() const;
+        size_t getBytesPerLineCount() const;
+
+        QRgb getPackedPixel(int x, int y) const {
+            assert(_format != Format_RGBAF);
+            return _packedData.pixel(x, y);
+        }
+        void setPackedPixel(int x, int y, QRgb value) {
+            assert(_format != Format_RGBAF);
+            _packedData.setPixel(x, y, value);
+        }
+
+        glm::vec4 getFloatPixel(int x, int y) const {
+            assert(_format == Format_RGBAF);
+            return _floatData[x + y*_dims.x];
+        }
+        void setFloatPixel(int x, int y, const glm::vec4& value) {
+            assert(_format == Format_RGBAF);
+            _floatData[x + y * _dims.x] = value;
+        }
+
+        glm::uint8* editScanLine(int y);
+        const glm::uint8* getScanLine(int y) const;
+        glm::uint8* editBits();
+        const glm::uint8* getBits() const;
 
         Image getScaled(glm::uvec2 newSize, AspectRatioMode ratioMode, TransformationMode transformationMode = Qt::SmoothTransformation) const;
         Image getConvertedToFormat(Format newFormat) const;
@@ -91,7 +122,13 @@ namespace image {
 
     private:
 
-        QImage _data;
+        using FloatPixels = std::vector<glm::vec4>;
+
+        // For QImage supported formats
+        QImage _packedData;
+        FloatPixels _floatData;
+        glm::ivec2 _dims;
+        Format _format;
     };
 
 } // namespace image
diff --git a/libraries/image/src/image/TextureProcessing.cpp b/libraries/image/src/image/TextureProcessing.cpp
index ac0c17d115..589335d816 100644
--- a/libraries/image/src/image/TextureProcessing.cpp
+++ b/libraries/image/src/image/TextureProcessing.cpp
@@ -33,7 +33,6 @@
 
 using namespace gpu;
 
-#define CPU_MIPMAPS 1
 #include <nvtt/nvtt.h>
 
 #undef _CRT_SECURE_NO_WARNINGS
@@ -515,21 +514,28 @@ struct MyErrorHandler : public nvtt::ErrorHandler {
     }
 };
 
-SequentialTaskDispatcher::SequentialTaskDispatcher(const std::atomic<bool>& abortProcessing) : _abortProcessing(abortProcessing) {
-}
+#if defined(NVTT_API)
+class SequentialTaskDispatcher : public nvtt::TaskDispatcher {
+public:
+    SequentialTaskDispatcher(const std::atomic<bool>& abortProcessing = false) : _abortProcessing(abortProcessing) {
+    }
 
-void SequentialTaskDispatcher::dispatch(nvtt::Task* task, void* context, int count) {
-    for (int i = 0; i < count; i++) {
-        if (!_abortProcessing.load()) {
-            task(context, i);
-        } else {
-            break;
+    const std::atomic<bool>& _abortProcessing;
+
+    void dispatch(nvtt::Task* task, void* context, int count) override {
+        for (int i = 0; i < count; i++) {
+            if (!_abortProcessing.load()) {
+                task(context, i);
+            } else {
+                break;
+            }
         }
     }
-}
+};
+#endif
 
-void image::convertToFloat(const unsigned char* source, int width, int height, size_t srcLineByteStride, gpu::Element sourceFormat,
-                           glm::vec4* output, size_t outputLinePixelStride) {
+void image::convertToFloatFromPacked(const unsigned char* source, int width, int height, size_t srcLineByteStride, gpu::Element sourceFormat,
+                                     glm::vec4* output, size_t outputLinePixelStride) {
     glm::vec4* outputIt;
     auto unpackFunc = getHDRUnpackingFunction(sourceFormat);
 
@@ -548,8 +554,8 @@ void image::convertToFloat(const unsigned char* source, int width, int height, s
     }
 }
 
-void image::convertFromFloat(unsigned char* output, int width, int height, size_t outputLineByteStride, gpu::Element outputFormat,
-                             const glm::vec4* source, size_t srcLinePixelStride) {
+void image::convertToPackedFromFloat(unsigned char* output, int width, int height, size_t outputLineByteStride, gpu::Element outputFormat,
+                                     const glm::vec4* source, size_t srcLinePixelStride) {
     const glm::vec4* sourceIt;
     auto packFunc = getHDRPackingFunction(outputFormat);
 
@@ -574,11 +580,13 @@ nvtt::OutputHandler* getNVTTCompressionOutputHandler(gpu::Texture* outputTexture
     nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F;
     nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
     nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
+    bool useNVTT = false;
 
     compressionOptions.setQuality(nvtt::Quality_Production);
 
     // TODO: gles: generate ETC mips instead?
     if (outputFormat == gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB) {
+        useNVTT = true;
         compressionOptions.setFormat(nvtt::Format_BC6);
     } else if (outputFormat == gpu::Element::COLOR_RGB9E5) {
         compressionOptions.setFormat(nvtt::Format_RGB);
@@ -588,13 +596,18 @@ nvtt::OutputHandler* getNVTTCompressionOutputHandler(gpu::Texture* outputTexture
         compressionOptions.setFormat(nvtt::Format_RGB);
         compressionOptions.setPixelType(nvtt::PixelType_Float);
         compressionOptions.setPixelFormat(32, 32, 32, 0);
+    } else if (outputFormat == gpu::Element::COLOR_SRGBA_32) {
+        useNVTT = true;
+        compressionOptions.setFormat(nvtt::Format_RGB);
+        compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
+        compressionOptions.setPixelFormat(8, 8, 8, 0);
     } else {
         qCWarning(imagelogging) << "Unknown mip format";
         Q_UNREACHABLE();
         return nullptr;
     }
 
-    if (outputFormat == gpu::Element::COLOR_RGB9E5 || outputFormat == gpu::Element::COLOR_R11G11B10) {
+    if (!useNVTT) {
         // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats
         return new PackedFloatOutputHandler(outputTexture, face, outputFormat);
     } else {
@@ -602,28 +615,18 @@ nvtt::OutputHandler* getNVTTCompressionOutputHandler(gpu::Texture* outputTexture
     }
 }
 
-void generateHDRMips(gpu::Texture* texture, Image&& image, BackendTarget target, 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
-    Image localCopy = std::move(image);
+void convertToHDRTexture(gpu::Texture* texture, Image&& image, BackendTarget target, int baseMipLevel, bool buildMips, const std::atomic<bool>& abortProcessing, int face) {
+    assert(image.hasFloatFormat());
 
-    assert(localCopy.getFormat() == Image::Format_PACKED_FLOAT);
+    Image localCopy = image.getConvertedToFormat(Image::Format_RGBAF);
 
     const int width = localCopy.getWidth(), height = localCopy.getHeight();
-    std::vector<glm::vec4> data;
-    std::vector<glm::vec4>::iterator dataIt;
     auto mipFormat = texture->getStoredMipFormat();
 
     nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F;
     nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
     nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
 
-    data.resize(width * height);
-    convertToFloat(localCopy.getBits(), width, height, localCopy.getBytesPerLineCount(), GPU_CUBEMAP_HDR_FORMAT, data.data(), width);
-
-    // We're done with the localCopy, free up the memory to avoid bloating the heap
-    localCopy = Image(); // Image doesn't have a clear function, so override it with an empty one.
-
     nvtt::OutputOptions outputOptions;
     outputOptions.setOutputHeader(false);
 
@@ -633,12 +636,12 @@ void generateHDRMips(gpu::Texture* texture, Image&& image, BackendTarget target,
     MyErrorHandler errorHandler;
     outputOptions.setErrorHandler(&errorHandler);
     nvtt::Context context;
-    int mipLevel = 0;
+    int mipLevel = baseMipLevel;
 
     outputOptions.setOutputHandler(outputHandler.get());
 
     nvtt::Surface surface;
-    surface.setImage(inputFormat, width, height, 1, &(*data.begin()));
+    surface.setImage(inputFormat, width, height, 1, localCopy.getBits());
     surface.setAlphaMode(alphaMode);
     surface.setWrapMode(wrapMode);
 
@@ -647,13 +650,15 @@ void generateHDRMips(gpu::Texture* texture, Image&& image, BackendTarget target,
     context.setTaskDispatcher(&dispatcher);
 
     context.compress(surface, face, mipLevel++, compressionOptions, outputOptions);
-    while (surface.canMakeNextMipmap() && !abortProcessing.load()) {
-        surface.buildNextMipmap(nvtt::MipmapFilter_Box);
-        context.compress(surface, face, mipLevel++, compressionOptions, outputOptions);
+    if (buildMips) {
+        while (surface.canMakeNextMipmap() && !abortProcessing.load()) {
+            surface.buildNextMipmap(nvtt::MipmapFilter_Box);
+            context.compress(surface, face, mipLevel++, compressionOptions, outputOptions);
+        }
     }
 }
 
-void generateLDRMips(gpu::Texture* texture, Image&& image, BackendTarget target, const std::atomic<bool>& abortProcessing, int face) {
+void convertToLDRTexture(gpu::Texture* texture, Image&& image, BackendTarget target, int baseMipLevel, bool buildMips, 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
     Image localCopy = std::move(image);
@@ -665,6 +670,7 @@ void generateLDRMips(gpu::Texture* texture, Image&& image, BackendTarget target,
 
     const int width = localCopy.getWidth(), height = localCopy.getHeight();
     auto mipFormat = texture->getStoredMipFormat();
+    int mipLevel = baseMipLevel;
 
     if (target != BackendTarget::GLES32) {
         const void* data = static_cast<const void*>(localCopy.getBits());
@@ -677,23 +683,22 @@ void generateLDRMips(gpu::Texture* texture, Image&& image, BackendTarget target,
         float inputGamma = 2.2f;
         float outputGamma = 2.2f;
 
-        nvtt::InputOptions inputOptions;
-        inputOptions.setTextureLayout(textureType, width, height);
+        nvtt::Surface surface;
+        surface.setImage(inputFormat, width, height, 1, data);
+        surface.setAlphaMode(alphaMode);
+        surface.setWrapMode(wrapMode);
 
-        inputOptions.setMipmapData(data, width, height);
-        // setMipmapData copies the memory, so free up the memory afterward to avoid bloating the heap
+        // Surface copies the memory, so free up the memory afterward to avoid bloating the heap
         data = nullptr;
         localCopy = Image(); // Image doesn't have a clear function, so override it with an empty one.
 
+        nvtt::InputOptions inputOptions;
+        inputOptions.setTextureLayout(textureType, width, height);
+
         inputOptions.setFormat(inputFormat);
         inputOptions.setGamma(inputGamma, outputGamma);
-        inputOptions.setAlphaMode(alphaMode);
-        inputOptions.setWrapMode(wrapMode);
         inputOptions.setRoundMode(roundMode);
 
-        inputOptions.setMipmapGeneration(true);
-        inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box);
-
         nvtt::CompressionOptions compressionOptions;
         compressionOptions.setQuality(nvtt::Quality_Production);
 
@@ -777,11 +782,22 @@ void generateLDRMips(gpu::Texture* texture, Image&& image, BackendTarget target,
         outputOptions.setErrorHandler(&errorHandler);
 
         SequentialTaskDispatcher dispatcher(abortProcessing);
-        nvtt::Compressor compressor;
-        compressor.setTaskDispatcher(&dispatcher);
-        compressor.process(inputOptions, compressionOptions, outputOptions);
+        nvtt::Compressor context;
+
+        context.compress(surface, face, mipLevel++, compressionOptions, outputOptions);
+        if (buildMips) {
+            while (surface.canMakeNextMipmap() && !abortProcessing.load()) {
+                surface.buildNextMipmap(nvtt::MipmapFilter_Box);
+                context.compress(surface, face, mipLevel++, compressionOptions, outputOptions);
+            }
+        }
     } else {
-        int numMips = 1 + (int)log2(std::max(width, height));
+        int numMips = 1;
+    
+        if (buildMips) {
+            numMips += (int)log2(std::max(width, height)) - baseMipLevel;
+        }
+        assert(numMips > 0);
         Etc::RawImage *mipMaps = new Etc::RawImage[numMips];
         Etc::Image::Format etcFormat = Etc::Image::Format::DEFAULT;
 
@@ -815,23 +831,13 @@ void generateLDRMips(gpu::Texture* texture, Image&& image, BackendTarget target,
         const float effort = 1.0f;
         const int numEncodeThreads = 4;
         int encodingTime;
-        const float MAX_COLOR = 255.0f;
 
-        std::vector<vec4> floatData;
-        floatData.resize(width * height);
-        for (int y = 0; y < height; y++) {
-            QRgb *line = (QRgb *)localCopy.editScanLine(y);
-            for (int x = 0; x < width; x++) {
-                QRgb &pixel = line[x];
-                floatData[x + y * width] = vec4(qRed(pixel), qGreen(pixel), qBlue(pixel), qAlpha(pixel)) / MAX_COLOR;
-            }
+        if (localCopy.getFormat() != Image::Format_RGBAF) {
+            localCopy = localCopy.getConvertedToFormat(Image::Format_RGBAF);
         }
 
-        // free up the memory afterward to avoid bloating the heap
-        localCopy = Image(); // Image doesn't have a clear function, so override it with an empty one.
-
         Etc::EncodeMipmaps(
-            (float *)floatData.data(), width, height,
+            (float *)localCopy.editBits(), width, height,
             etcFormat, errorMetric, effort,
             numEncodeThreads, numEncodeThreads,
             numMips, Etc::FILTER_WRAP_NONE,
@@ -841,9 +847,9 @@ void generateLDRMips(gpu::Texture* texture, Image&& image, BackendTarget target,
         for (int i = 0; i < numMips; i++) {
             if (mipMaps[i].paucEncodingBits.get()) {
                 if (face >= 0) {
-                    texture->assignStoredMipFace(i, face, mipMaps[i].uiEncodingBitsBytes, static_cast<const gpu::Byte*>(mipMaps[i].paucEncodingBits.get()));
+                    texture->assignStoredMipFace(i+baseMipLevel, face, mipMaps[i].uiEncodingBitsBytes, static_cast<const gpu::Byte*>(mipMaps[i].paucEncodingBits.get()));
                 } else {
-                    texture->assignStoredMip(i, mipMaps[i].uiEncodingBitsBytes, static_cast<const gpu::Byte*>(mipMaps[i].paucEncodingBits.get()));
+                    texture->assignStoredMip(i + baseMipLevel, mipMaps[i].uiEncodingBitsBytes, static_cast<const gpu::Byte*>(mipMaps[i].paucEncodingBits.get()));
                 }
             }
         }
@@ -854,22 +860,27 @@ void generateLDRMips(gpu::Texture* texture, Image&& image, BackendTarget target,
 
 #endif
 
-void generateMips(gpu::Texture* texture, Image&& image, BackendTarget target, const std::atomic<bool>& abortProcessing = false, int face = -1) {
-#if CPU_MIPMAPS
-    PROFILE_RANGE(resource_parse, "generateMips");
+void convertImageToTexture(gpu::Texture* texture, Image& image, BackendTarget target, int face, int baseMipLevel, bool buildMips, const std::atomic<bool>& abortProcessing) {
+    PROFILE_RANGE(resource_parse, "convertToTextureWithMips");
 
     if (target == BackendTarget::GLES32) {
-        generateLDRMips(texture, std::move(image), target, abortProcessing, face);
+        convertToLDRTexture(texture, std::move(image), target, baseMipLevel, buildMips, abortProcessing, face);
     } else {
-        if (image.getFormat() == Image::Format_PACKED_FLOAT) {
-            generateHDRMips(texture, std::move(image), target, abortProcessing, face);
+        if (image.hasFloatFormat()) {
+            convertToHDRTexture(texture, std::move(image), target, baseMipLevel, buildMips, abortProcessing, face);
         } else {
-            generateLDRMips(texture, std::move(image), target, abortProcessing, face);
+            convertToLDRTexture(texture, std::move(image), target, baseMipLevel, buildMips, abortProcessing, face);
         }
     }
-#else
-    texture->setAutoGenerateMips(true);
-#endif
+}
+
+void convertToTextureWithMips(gpu::Texture* texture, Image&& image, BackendTarget target, const std::atomic<bool>& abortProcessing, int face) {
+    convertImageToTexture(texture, image, target, face, 0, true, abortProcessing);
+}
+
+void convertToTexture(gpu::Texture* texture, Image&& image, BackendTarget target, const std::atomic<bool>& abortProcessing, int face, int mipLevel) {
+    PROFILE_RANGE(resource_parse, "convertToTexture");
+    convertImageToTexture(texture, image, target, face, mipLevel, false, abortProcessing);
 }
 
 void processTextureAlpha(const Image& srcImage, bool& validAlpha, bool& alphaAsMask) {
@@ -959,7 +970,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(Image&& srcImag
         theTexture->setUsage(usage.build());
         theTexture->setStoredMipFormat(formatMip);
         theTexture->assignStoredMip(0, image.getByteCount(), image.getBits());
-        generateMips(theTexture.get(), std::move(image), target, abortProcessing);
+        convertToTextureWithMips(theTexture.get(), std::move(image), target, abortProcessing);
     }
 
     return theTexture;
@@ -1003,14 +1014,14 @@ Image processBumpMap(Image&& image) {
             const int jPrevClamped = clampPixelCoordinate(j - 1, height - 1);
 
             // surrounding pixels
-            const QRgb topLeft = localCopy.getPixel(iPrevClamped, jPrevClamped);
-            const QRgb top = localCopy.getPixel(iPrevClamped, j);
-            const QRgb topRight = localCopy.getPixel(iPrevClamped, jNextClamped);
-            const QRgb right = localCopy.getPixel(i, jNextClamped);
-            const QRgb bottomRight = localCopy.getPixel(iNextClamped, jNextClamped);
-            const QRgb bottom = localCopy.getPixel(iNextClamped, j);
-            const QRgb bottomLeft = localCopy.getPixel(iNextClamped, jPrevClamped);
-            const QRgb left = localCopy.getPixel(i, jPrevClamped);
+            const QRgb topLeft = localCopy.getPackedPixel(iPrevClamped, jPrevClamped);
+            const QRgb top = localCopy.getPackedPixel(iPrevClamped, j);
+            const QRgb topRight = localCopy.getPackedPixel(iPrevClamped, jNextClamped);
+            const QRgb right = localCopy.getPackedPixel(i, jNextClamped);
+            const QRgb bottomRight = localCopy.getPackedPixel(iNextClamped, jNextClamped);
+            const QRgb bottom = localCopy.getPackedPixel(iNextClamped, j);
+            const QRgb bottomLeft = localCopy.getPackedPixel(iNextClamped, jPrevClamped);
+            const QRgb left = localCopy.getPackedPixel(i, jPrevClamped);
 
             // take their gray intensities
             // since it's a grayscale image, the value of each component RGB is the same
@@ -1033,12 +1044,13 @@ Image processBumpMap(Image&& image) {
 
             // convert to rgb from the value obtained computing the filter
             QRgb qRgbValue = qRgba(mapComponent(v.z), mapComponent(v.y), mapComponent(v.x), 1.0);
-            result.setPixel(i, j, qRgbValue);
+            result.setPackedPixel(i, j, qRgbValue);
         }
     }
 
     return result;
 }
+
 gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(Image&& srcImage, const std::string& srcImageName,
                                                                      bool compress, BackendTarget target, bool isBumpMap,
                                                                      const std::atomic<bool>& abortProcessing) {
@@ -1073,7 +1085,7 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(Image&& src
         theTexture->setSource(srcImageName);
         theTexture->setStoredMipFormat(formatMip);
         theTexture->assignStoredMip(0, image.getByteCount(), image.getBits());
-        generateMips(theTexture.get(), std::move(image), target, abortProcessing);
+        convertToTextureWithMips(theTexture.get(), std::move(image), target, abortProcessing);
     }
 
     return theTexture;
@@ -1113,7 +1125,7 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(Image&& src
         theTexture->setSource(srcImageName);
         theTexture->setStoredMipFormat(formatMip);
         theTexture->assignStoredMip(0, image.getByteCount(), image.getBits());
-        generateMips(theTexture.get(), std::move(image), target, abortProcessing);
+        convertToTextureWithMips(theTexture.get(), std::move(image), target, abortProcessing);
     }
 
     return theTexture;  
@@ -1475,13 +1487,18 @@ Image convertToHDRFormat(Image&& srcImage, gpu::Element format) {
     return hdrImage;
 }
 
-void convolveForGGX(const std::vector<Image>& faces, gpu::Element faceFormat, gpu::Texture* texture, const std::atomic<bool>& abortProcessing = false) {
+void convolveForGGX(const std::vector<Image>& faces, gpu::Element faceFormat, gpu::Texture* texture, BackendTarget target, const std::atomic<bool>& abortProcessing = false) {
     PROFILE_RANGE(resource_parse, "convolveForGGX");
     CubeMap source(faces, faceFormat, texture->getNumMips(), abortProcessing);
     CubeMap output(texture->getWidth(), texture->getHeight(), texture->getNumMips());
 
     source.convolveForGGX(output, abortProcessing);
-    output.copyTo(texture, abortProcessing);
+
+    for (int face = 0; face < 6; face++) {
+        for (gpu::uint16 mipLevel = 0; mipLevel < output.getMipCount(); mipLevel++) {
+            convertToTexture(texture, output.getFaceImage(mipLevel, face), target, abortProcessing, face, mipLevel);
+        }
+    }
 }
 
 gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(Image&& srcImage, const std::string& srcImageName,
@@ -1584,12 +1601,12 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(Image&& srcIm
         
         if (options & CUBE_GGX_CONVOLVE) {
             // Performs and convolution AND mip map generation
-            convolveForGGX(faces, GPU_CUBEMAP_HDR_FORMAT, theTexture.get(), abortProcessing);
+            convolveForGGX(faces, GPU_CUBEMAP_HDR_FORMAT, theTexture.get(), target, abortProcessing);
         } else {
             // Create mip maps and compress to final format in one go
             for (uint8 face = 0; face < faces.size(); ++face) {
                 // Force building the mip maps right now on CPU if we are convolving for GGX later on
-                generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face);
+                convertToTextureWithMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face);
             }
         }
     }
diff --git a/libraries/image/src/image/TextureProcessing.h b/libraries/image/src/image/TextureProcessing.h
index 378e68228a..5daa99a9d8 100644
--- a/libraries/image/src/image/TextureProcessing.h
+++ b/libraries/image/src/image/TextureProcessing.h
@@ -23,9 +23,9 @@ namespace image {
 
     std::function<gpu::uint32(const glm::vec3&)> getHDRPackingFunction();
     std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction();
-    void convertToFloat(const unsigned char* source, int width, int height, size_t srcLineByteStride, gpu::Element sourceFormat, 
+    void convertToFloatFromPacked(const unsigned char* source, int width, int height, size_t srcLineByteStride, gpu::Element sourceFormat, 
                         glm::vec4* output, size_t outputLinePixelStride);
-    void convertFromFloat(unsigned char* output, int width, int height, size_t outputLineByteStride, gpu::Element outputFormat,
+    void convertToPackedFromFloat(unsigned char* output, int width, int height, size_t outputLineByteStride, gpu::Element outputFormat,
                           const glm::vec4* source, size_t srcLinePixelStride);
 
 namespace TextureUsage {
@@ -102,18 +102,8 @@ gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::
                                  int maxNumPixels, TextureUsage::Type textureType,
                                  bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing = false);
 
-#if defined(NVTT_API)
-class SequentialTaskDispatcher : public nvtt::TaskDispatcher {
-public:
-    SequentialTaskDispatcher(const std::atomic<bool>& abortProcessing = false);
-
-    const std::atomic<bool>& _abortProcessing;
-
-    void dispatch(nvtt::Task* task, void* context, int count) override;
-};
-
-nvtt::OutputHandler* getNVTTCompressionOutputHandler(gpu::Texture* outputTexture, int face, nvtt::CompressionOptions& compressOptions);
-#endif
+void convertToTextureWithMips(gpu::Texture* texture, Image&& image, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing = false, int face = -1);
+void convertToTexture(gpu::Texture* texture, Image&& image, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing = false, int face = -1, int mipLevel = 0);
 
 } // namespace image
 
diff --git a/libraries/render-utils/src/LightAmbient.slh b/libraries/render-utils/src/LightAmbient.slh
index 8afcb6ccd3..1904889ec0 100644
--- a/libraries/render-utils/src/LightAmbient.slh
+++ b/libraries/render-utils/src/LightAmbient.slh
@@ -41,7 +41,7 @@ float getMipLevelFromRoughness(float roughness, float lodCount) {
     // This should match the value in the CubeMap::convolveForGGX method (CubeMap.cpp)
     float ROUGHNESS_1_MIP_RESOLUTION = 1.5;
     float deltaLod = lodCount - ROUGHNESS_1_MIP_RESOLUTION;
-    return (sqrt(6.0*roughness+0.25)-0.5)*deltaLod*0.5;
+    return deltaLod * (sqrt(1.0+24.0*roughness)-1.0) / 4.0;
 }
 
 vec3 evalAmbientSpecularIrradiance(LightAmbient ambient, SurfaceData surface, vec3 lightDir) {

From 50fecf5e0139bd7b93dc2d20c827800d15d7ef4b Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Wed, 3 Apr 2019 11:29:59 +0200
Subject: [PATCH 16/23] Ambient map has different hash than sky map to prevent
 using one instead of the other, even if they are using the same source
 texture.

---
 libraries/baking/src/TextureBaker.cpp | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp
index 60cca77bda..bed0e9a40b 100644
--- a/libraries/baking/src/TextureBaker.cpp
+++ b/libraries/baking/src/TextureBaker.cpp
@@ -131,7 +131,14 @@ void TextureBaker::handleTextureNetworkReply() {
 void TextureBaker::processTexture() {
     // 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);
+    QCryptographicHash hasher(QCryptographicHash::Md5);
+    hasher.addData(_originalTexture);
+    // An ambient texture is built with the same pixel data as sky texture but its Mip Maps are different
+    // so we mustn't use one instead of the other.
+    if (_textureType == image::TextureUsage::AMBIENT_TEXTURE) {
+        hasher.addData((const char*)&_textureType, sizeof(_textureType));
+    }
+    auto hashData = hasher.result();
     std::string hash = hashData.toHex().toStdString();
 
     TextureMeta meta;

From 57de55c5ce42214e0fa8bfc0916fbaf82b2d9703 Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Thu, 4 Apr 2019 10:15:38 +0200
Subject: [PATCH 17/23] Fixed crash in lasterpointer

---
 interface/src/raypick/LaserPointer.cpp | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp
index bd746c9090..12daae0351 100644
--- a/interface/src/raypick/LaserPointer.cpp
+++ b/interface/src/raypick/LaserPointer.cpp
@@ -233,16 +233,19 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P
 
     // If we just started triggering and we haven't moved too much, don't update intersection and pos2D
     TriggerState& state = hover ? _latestState : _states[button];
-    float sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
-    float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale;
-    bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared;
-    if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) {
-        pos2D = state.triggerPos2D;
-        intersection = state.intersection;
-        surfaceNormal = state.surfaceNormal;
-    }
-    if (!withinDeadspot) {
-        state.deadspotExpired = true;
+    auto avatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
+    if (avatar) {
+        float sensorToWorldScale = avatar->getSensorToWorldScale();
+        float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale;
+        bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared;
+        if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) {
+            pos2D = state.triggerPos2D;
+            intersection = state.intersection;
+            surfaceNormal = state.surfaceNormal;
+        }
+        if (!withinDeadspot) {
+            state.deadspotExpired = true;
+        }
     }
 
     return PointerEvent(pos2D, intersection, surfaceNormal, direction);

From b71a8f7902520fdc8d12e60ecd4bd792b51654aa Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Thu, 4 Apr 2019 16:10:33 +0200
Subject: [PATCH 18/23] Switched to split sum model for ambient (as Unreal)

---
 libraries/image/src/image/CubeMap.cpp         | 38 +++-------
 .../render-utils/src/AntialiasingEffect.cpp   |  2 +-
 .../src/DeferredLightingEffect.cpp            |  2 +
 libraries/render-utils/src/LightAmbient.slh   | 11 ++-
 libraries/render-utils/src/LightingModel.cpp  | 75 +++++++++++++++++++
 libraries/render-utils/src/LightingModel.h    |  2 +
 .../render-utils/src/RenderCommonTask.cpp     |  1 +
 .../render-utils/src/RenderDeferredTask.cpp   |  2 +
 .../render-utils/src/RenderForwardTask.cpp    |  1 +
 .../src/render-utils/ShaderConstants.h        |  2 +
 libraries/shared/src/BRDF.cpp                 | 45 +++++++++++
 libraries/shared/src/BRDF.h                   | 36 +++++++++
 libraries/shared/src/RandomAndNoise.h         | 37 +++++----
 13 files changed, 207 insertions(+), 47 deletions(-)
 create mode 100644 libraries/shared/src/BRDF.cpp
 create mode 100644 libraries/shared/src/BRDF.h

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index f511890c58..5f54129cdc 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -15,6 +15,7 @@
 #include <tbb/blocked_range2d.h>
 
 #include "RandomAndNoise.h"
+#include "BRDF.h"
 #include "ImageLogging.h"
 
 #ifndef M_PI
@@ -501,34 +502,15 @@ glm::vec4 CubeMap::fetchLod(const glm::vec3& dir, float lod) const {
     return loColor + (hiColor - loColor) * lodFrac;
 }
 
-static glm::vec3 sampleGGX(const glm::vec2& Xi, const float roughness) {
-    const float a = roughness * roughness;
-
-    float phi = (float)(2.0 * M_PI * Xi.x);
-    float cosTheta = (float)(std::sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)));
-    float sinTheta = (float)(std::sqrt(1.0 - cosTheta * cosTheta));
-
-    // from spherical coordinates to cartesian coordinates
-    glm::vec3 H;
-    H.x = std::cos(phi) * sinTheta;
-    H.y = std::sin(phi) * sinTheta;
-    H.z = cosTheta;
-
-    return H;
-}
-
-static float evaluateGGX(float NdotH, float roughness) {
-    float alpha = roughness * roughness;
-    float alphaSquared = alpha * alpha;
-    float denom = (float)(NdotH * NdotH * (alphaSquared - 1.0) + 1.0);
-    return alphaSquared / (denom * denom);
-}
-
 struct CubeMap::GGXSamples {
     float invTotalWeight;
     std::vector<glm::vec4> points;
 };
 
+// All the GGX convolution code is inspired from:
+// https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/
+// Computation is done in tangent space so normal is always (0,0,1) which simplifies a lot of things
+
 void CubeMap::generateGGXSamples(GGXSamples& data, float roughness, const int resolution) {
     glm::vec2 xi;
     glm::vec3 L;
@@ -546,8 +528,8 @@ void CubeMap::generateGGXSamples(GGXSamples& data, float roughness, const int re
     // Do some computation in tangent space
     while (sampleIndex < sampleCount) {
         if (hammersleySampleIndex < hammersleySequenceLength) {
-            xi = evaluateHammersley((int)hammersleySampleIndex, (int)hammersleySequenceLength);
-            H = sampleGGX(xi, roughness);
+            xi = hammersley::evaluate((int)hammersleySampleIndex, (int)hammersleySequenceLength);
+            H = ggx::sample(xi, roughness);
             L = H * (2.0f * H.z) - glm::vec3(0.0f, 0.0f, 1.0f);
             NdotL = L.z;
             hammersleySampleIndex++;
@@ -559,14 +541,14 @@ void CubeMap::generateGGXSamples(GGXSamples& data, float roughness, const int re
             // Create a purely random sample
             xi.x = rand() / float(RAND_MAX);
             xi.y = rand() / float(RAND_MAX);
-            H = sampleGGX(xi, roughness);
+            H = ggx::sample(xi, roughness);
             L = H * (2.0f * H.z) - glm::vec3(0.0f, 0.0f, 1.0f);
             NdotL = L.z;
         }
 
         float NdotH = std::max(0.0f, H.z);
         float HdotV = NdotH;
-        float D = evaluateGGX(NdotH, roughness);
+        float D = ggx::evaluate(NdotH, roughness);
         float pdf = (D * NdotH / (4.0f * HdotV)) + 0.0001f;
         float saSample = 1.0f / (float(sampleCount) * pdf + 0.0001f);
         float mipLevel = std::max(0.5f * log2(saSample / saTexel) + mipBias, 0.0f);
@@ -628,7 +610,7 @@ void CubeMap::convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output,
     const auto outputLineStride = output.getMipLineStride(mipLevel);
     auto outputFacePixels = output.editFace(mipLevel, face);
 
-    tbb::parallel_for(tbb::blocked_range2d<int, int>(0, mipDimensions.x, 32, 0, mipDimensions.y, 32), [&](const tbb::blocked_range2d<int, int>& range) {
+    tbb::parallel_for(tbb::blocked_range2d<int, int>(0, mipDimensions.y, 32, 0, mipDimensions.x, 32), [&](const tbb::blocked_range2d<int, int>& range) {
         auto rowRange = range.rows();
         auto colRange = range.cols();
 
diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp
index f30e67a979..a445ea2343 100644
--- a/libraries/render-utils/src/AntialiasingEffect.cpp
+++ b/libraries/render-utils/src/AntialiasingEffect.cpp
@@ -363,7 +363,7 @@ JitterSample::SampleSequence::SampleSequence(){
     // Halton sequence (2,3)
 
     for (int i = 0; i < SEQUENCE_LENGTH; i++) {
-        offsets[i] = glm::vec2(evaluateHalton<2>(i), evaluateHalton<3>(i));
+        offsets[i] = glm::vec2(halton::evaluate<2>(i), halton::evaluate<3>(i));
         offsets[i] -= vec2(0.5f);
     }
     offsets[SEQUENCE_LENGTH] = glm::vec2(0.0f);
diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp
index 2b19101653..b8c720e9ca 100644
--- a/libraries/render-utils/src/DeferredLightingEffect.cpp
+++ b/libraries/render-utils/src/DeferredLightingEffect.cpp
@@ -365,6 +365,7 @@ void PrepareDeferred::run(const RenderContextPointer& renderContext, const Input
 
         // For the rest of the rendering, bind the lighting model
         batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer());
+        batch.setResourceTexture(ru::Texture::AmbientFresnel, lightingModel->getAmbientFresnelLUT());
     });
 }
 
@@ -416,6 +417,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext,
 
         // THe lighting model
         batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer());
+        batch.setResourceTexture(ru::Texture::AmbientFresnel, lightingModel->getAmbientFresnelLUT());
 
         // Subsurface scattering specific
         if (surfaceGeometryFramebuffer) {
diff --git a/libraries/render-utils/src/LightAmbient.slh b/libraries/render-utils/src/LightAmbient.slh
index 1904889ec0..8e7abd8861 100644
--- a/libraries/render-utils/src/LightAmbient.slh
+++ b/libraries/render-utils/src/LightAmbient.slh
@@ -27,10 +27,17 @@ vec4 evalSkyboxLight(vec3 direction, float lod) {
 <@endfunc@>
 
 <@func declareEvalAmbientSpecularIrradiance(supportAmbientSphere, supportAmbientMap, supportIfAmbientMapElseAmbientSphere)@>
+LAYOUT(binding=RENDER_UTILS_TEXTURE_AMBIENT_FRESNEL) uniform sampler2D ambientFresnelLUT;
 
-vec3 fresnelSchlickAmbient(vec3 fresnelColor, float ndotd, float gloss) {
+vec3 fresnelSchlickAmbient(vec3 fresnelColor, float ndotd, float roughness) {
+#if 0
+    float gloss = 1.0-roughness;
     float f = pow(1.0 - ndotd, 5.0);
     return fresnelColor + (max(vec3(gloss), fresnelColor) - fresnelColor) * f;
+#else
+    vec2 ambientFresnel = texture(ambientFresnelLUT, vec2(roughness, ndotd)).xy;
+    return fresnelColor * ambientFresnel.x + vec3(ambientFresnel.y);
+#endif
 }
 
 <@if supportAmbientMap@>
@@ -95,7 +102,7 @@ void evalLightingAmbient(out vec3 diffuse, out vec3 specular, LightAmbient ambie
     vec3 ambientSpaceLowNormal = (ambient.transform * vec4(lowNormalCurvature.xyz, 0.0)).xyz;
 <@endif@>
 
-    vec3 ambientFresnel = fresnelSchlickAmbient(fresnelF0, surface.ndotv, 1.0-surface.roughness);
+    vec3 ambientFresnel = fresnelSchlickAmbient(fresnelF0, surface.ndotv, surface.roughness);
 
     diffuse = (1.0 - metallic) * (vec3(1.0) - ambientFresnel) * 
               sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(ambient), ambientSpaceSurfaceNormal).xyz;
diff --git a/libraries/render-utils/src/LightingModel.cpp b/libraries/render-utils/src/LightingModel.cpp
index 2a85fcd960..cd49acd3da 100644
--- a/libraries/render-utils/src/LightingModel.cpp
+++ b/libraries/render-utils/src/LightingModel.cpp
@@ -9,10 +9,85 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 #include "LightingModel.h"
+#include "RandomAndNoise.h"
+#include "BRDF.h"
+
+#include <tbb/parallel_for.h>
+#include <tbb/blocked_range2d.h>
+
+gpu::TexturePointer LightingModel::_ambientFresnelLUT;
 
 LightingModel::LightingModel() {
     Parameters parameters;
     _parametersBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Parameters), (const gpu::Byte*) &parameters, sizeof(Parameters)));
+
+    if (!_ambientFresnelLUT) {
+        // Code taken from the IntegrateBRDF method as described in this talk :
+        // https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
+        const auto N_roughness = 32;
+        const auto N_NdotV = 256;
+
+        using LUTVector = std::vector<glm::u16vec2>;
+        using LUTValueType = LUTVector::value_type::value_type;
+
+        LUTVector lut(N_roughness * N_NdotV);
+
+        _ambientFresnelLUT = gpu::Texture::create2D(gpu::Element{ gpu::VEC2, gpu::NUINT16, gpu::XY }, N_roughness, N_NdotV, 1U,
+                                                    gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR, gpu::Sampler::WRAP_CLAMP));
+
+        tbb::parallel_for(tbb::blocked_range2d<int, int>(0, N_NdotV, 8, 0, N_roughness, 8), [&](const tbb::blocked_range2d<int, int>& range) {
+            auto roughnessRange = range.cols();
+            auto ndotvRange = range.rows();
+
+            for (auto j = ndotvRange.begin(); j < ndotvRange.end(); j++) {
+                const float NdotV = j / float(N_NdotV - 1);
+
+                glm::vec3 V;
+                V.x = std::sqrt(1.0f - NdotV * NdotV);  // sin
+                V.y = 0;
+                V.z = NdotV;                            // cos
+
+                for (auto k = roughnessRange.begin(); k < roughnessRange.end(); k++) {
+                    const float roughness = k / float(N_roughness - 1);
+                    const float alpha = roughness * roughness;
+                    const float alphaSquared = alpha * alpha;
+
+                    float A = 0.0f;
+                    float B = 0.0f;
+
+                    const uint NumSamples = 1024;
+                    for (uint i = 0; i < NumSamples; i++) {
+                        glm::vec2 Xi = hammersley::evaluate(i, NumSamples);
+                        glm::vec3 H = ggx::sample(Xi, roughness);
+                        float VdotH = glm::dot(V, H);
+                        glm::vec3 L = 2.0f * VdotH * H - V;
+                        float NdotL = L.z;
+
+                        if (NdotL > 0.0f) {
+                            VdotH = glm::clamp(VdotH, 0.0f, 1.0f);
+
+                            float NdotH = glm::clamp(H.z, 0.0f, 1.0f);
+                            float G = smith::evaluateFastWithoutNdotV(alphaSquared, NdotV, NdotL);
+                            float G_Vis = (G * VdotH) / NdotH;
+                            float Fc = std::pow(1.0f - VdotH, 5.0f);
+
+                            A += (1.0f - Fc) * G_Vis;
+                            B += Fc * G_Vis;
+                        }
+                    }
+
+                    A /= NumSamples;
+                    B /= NumSamples;
+
+                    auto& lutValue = lut[k + j * N_roughness];
+                    lutValue.x = (LUTValueType)(glm::min(1.0f, A) * std::numeric_limits<LUTValueType>::max());
+                    lutValue.y = (LUTValueType)(glm::min(1.0f, B) * std::numeric_limits<LUTValueType>::max());
+                }
+            }
+        });
+
+        _ambientFresnelLUT->assignStoredMip(0, N_roughness * N_NdotV * sizeof(LUTVector::value_type), (const gpu::Byte*)lut.data());
+    }
 }
 
 void LightingModel::setUnlit(bool enable) {
diff --git a/libraries/render-utils/src/LightingModel.h b/libraries/render-utils/src/LightingModel.h
index f6bd6dcd46..a488abcb09 100644
--- a/libraries/render-utils/src/LightingModel.h
+++ b/libraries/render-utils/src/LightingModel.h
@@ -83,6 +83,7 @@ public:
     bool isShadowEnabled() const;
 
     UniformBufferView getParametersBuffer() const { return _parametersBuffer; }
+    gpu::TexturePointer getAmbientFresnelLUT() const { return _ambientFresnelLUT; }
 
 protected:
 
@@ -126,6 +127,7 @@ protected:
         Parameters() {}
     };
     UniformBufferView _parametersBuffer;
+    static gpu::TexturePointer _ambientFresnelLUT;
 };
 
 using LightingModelPointer = std::shared_ptr<LightingModel>;
diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp
index b1a62625b2..18532b7a66 100644
--- a/libraries/render-utils/src/RenderCommonTask.cpp
+++ b/libraries/render-utils/src/RenderCommonTask.cpp
@@ -94,6 +94,7 @@ void DrawLayered3D::run(const RenderContextPointer& renderContext, const Inputs&
 
             // Setup lighting model for all items;
             batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer());
+            batch.setResourceTexture(ru::Texture::AmbientFresnel, lightingModel->getAmbientFresnelLUT());
 
             if (_opaquePass) {
                 renderStateSortShapes(renderContext, _shapePlumber, inItems, _maxDrawn);
diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp
index ea2b05a6fa..d52f1da043 100644
--- a/libraries/render-utils/src/RenderDeferredTask.cpp
+++ b/libraries/render-utils/src/RenderDeferredTask.cpp
@@ -471,6 +471,7 @@ void RenderTransparentDeferred::run(const RenderContextPointer& renderContext, c
 
         // Setup lighting model for all items;
         batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer());
+        batch.setResourceTexture(ru::Texture::AmbientFresnel, lightingModel->getAmbientFresnelLUT());
 
         // Set the light
         deferredLightingEffect->setupKeyLightBatch(args, batch, *lightFrame);
@@ -536,6 +537,7 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const
 
         // Setup lighting model for all items;
         batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer());
+        batch.setResourceTexture(ru::Texture::AmbientFresnel, lightingModel->getAmbientFresnelLUT());
 
         // From the lighting model define a global shapeKey ORED with individiual keys
         ShapeKey::Builder keyBuilder;
diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp
index 0bc117bdb9..5e30308a05 100755
--- a/libraries/render-utils/src/RenderForwardTask.cpp
+++ b/libraries/render-utils/src/RenderForwardTask.cpp
@@ -251,6 +251,7 @@ void DrawForward::run(const RenderContextPointer& renderContext, const Inputs& i
 
         // Setup lighting model for all items;
         batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer());
+        batch.setResourceTexture(ru::Texture::AmbientFresnel, lightingModel->getAmbientFresnelLUT());
 
         // From the lighting model define a global shapeKey ORED with individiual keys
         ShapeKey::Builder keyBuilder;
diff --git a/libraries/render-utils/src/render-utils/ShaderConstants.h b/libraries/render-utils/src/render-utils/ShaderConstants.h
index 8c289e62d1..ffa580503b 100644
--- a/libraries/render-utils/src/render-utils/ShaderConstants.h
+++ b/libraries/render-utils/src/render-utils/ShaderConstants.h
@@ -54,6 +54,7 @@
 #define RENDER_UTILS_TEXTURE_DEFERRED_DIFFUSED_CURVATURE 7
 #define RENDER_UTILS_TEXTURE_DEFERRED_LIGHTING 10
 #define RENDER_UTILS_TEXTURE_SKYBOX 11
+#define RENDER_UTILS_TEXTURE_AMBIENT_FRESNEL 14
 
 #define RENDER_UTILS_BUFFER_SHADOW_PARAMS 2
 #define RENDER_UTILS_TEXTURE_SHADOW 12
@@ -198,6 +199,7 @@ enum Texture {
     BloomColor = RENDER_UTILS_TEXTURE_BLOOM_COLOR,
     ToneMappingColor = RENDER_UTILS_TEXTURE_TM_COLOR,
     TextFont = RENDER_UTILS_TEXTURE_TEXT_FONT,
+    AmbientFresnel = RENDER_UTILS_TEXTURE_AMBIENT_FRESNEL,
     DebugTexture0 = RENDER_UTILS_DEBUG_TEXTURE0,
 };
 } // namespace texture
diff --git a/libraries/shared/src/BRDF.cpp b/libraries/shared/src/BRDF.cpp
new file mode 100644
index 0000000000..726a233fc7
--- /dev/null
+++ b/libraries/shared/src/BRDF.cpp
@@ -0,0 +1,45 @@
+#include "BRDF.h"
+
+#include <cmath>
+#ifndef M_PI
+#define M_PI    3.14159265359
+#endif
+
+namespace ggx {
+
+float evaluate(float NdotH, float roughness) {
+    float alpha = roughness * roughness;
+    float alphaSquared = alpha * alpha;
+    float denom = (float)(NdotH * NdotH * (alphaSquared - 1.0) + 1.0);
+    return alphaSquared / (denom * denom);
+}
+
+glm::vec3 sample(const glm::vec2& Xi, const float roughness) {
+    const float a = roughness * roughness;
+
+    float phi = (float)(2.0 * M_PI * Xi.x);
+    float cosTheta = (float)(std::sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)));
+    float sinTheta = (float)(std::sqrt(1.0 - cosTheta * cosTheta));
+
+    // from spherical coordinates to cartesian coordinates
+    glm::vec3 H;
+    H.x = std::cos(phi) * sinTheta;
+    H.y = std::sin(phi) * sinTheta;
+    H.z = cosTheta;
+
+    return H;
+}
+
+}
+
+
+namespace smith {
+
+    float evaluateFastWithoutNdotV(float alphaSquared, float NdotV, float NdotL) {
+        float oneMinusAlphaSquared = 1.0f - alphaSquared;
+        float G = NdotL * std::sqrt(alphaSquared + NdotV * NdotV * oneMinusAlphaSquared);
+        G = G + NdotV * std::sqrt(alphaSquared + NdotL * NdotL * oneMinusAlphaSquared);
+        return 2.0f * NdotL / G;
+    }
+
+}
diff --git a/libraries/shared/src/BRDF.h b/libraries/shared/src/BRDF.h
new file mode 100644
index 0000000000..4e6cdd1f38
--- /dev/null
+++ b/libraries/shared/src/BRDF.h
@@ -0,0 +1,36 @@
+#pragma once
+//
+//  BRDF.h
+//
+//  Created by Olivier Prat on 04/04/19.
+//  Copyright 2019 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#ifndef SHARED_BRDF_H
+#define SHARED_BRDF_H
+
+#include <glm/vec2.hpp>
+#include <glm/vec3.hpp>
+
+// GGX micro-facet model
+namespace ggx {
+    float evaluate(float NdotH, float roughness);
+    glm::vec3 sample(const glm::vec2& Xi, const float roughness);
+}
+
+// Smith visibility function
+namespace smith {
+    float evaluateFastWithoutNdotV(float alphaSquared, float NdotV, float NdotL);
+
+    inline float evaluateFast(float alphaSquared, float NdotV, float NdotL) {
+        return evaluateFastWithoutNdotV(alphaSquared, NdotV, NdotL) * NdotV;
+    }
+
+    inline float evaluate(float roughness, float NdotV, float NdotL) {
+        return evaluateFast(roughness*roughness*roughness*roughness, NdotV, NdotL);
+    }
+}
+
+#endif // SHARED_BRDF_H
\ No newline at end of file
diff --git a/libraries/shared/src/RandomAndNoise.h b/libraries/shared/src/RandomAndNoise.h
index c69c186159..7bde14a141 100644
--- a/libraries/shared/src/RandomAndNoise.h
+++ b/libraries/shared/src/RandomAndNoise.h
@@ -12,22 +12,24 @@
 
 #include <glm/vec2.hpp>
 
-// Low discrepancy Halton sequence generator
-template <int B>
-float evaluateHalton(int index) {
-    float f = 1.0f;
-    float r = 0.0f;
-    float invB = 1.0f / (float)B;
-    index++; // Indices start at 1, not 0
+namespace halton {
+    // Low discrepancy Halton sequence generator
+    template <int B>
+    float evaluate(int index) {
+        float f = 1.0f;
+        float r = 0.0f;
+        float invB = 1.0f / (float)B;
+        index++; // Indices start at 1, not 0
 
-    while (index > 0) {
-        f = f * invB;
-        r = r + f * (float)(index % B);
-        index = index / B;
+        while (index > 0) {
+            f = f * invB;
+            r = r + f * (float)(index % B);
+            index = index / B;
 
+        }
+
+        return r;
     }
-
-    return r;
 }
 
 inline float getRadicalInverseVdC(uint32_t bits) {
@@ -39,9 +41,12 @@ inline float getRadicalInverseVdC(uint32_t bits) {
     return float(bits) * 2.3283064365386963e-10f; // / 0x100000000\n"
 }
 
-// Low discrepancy Hammersley 2D sequence generator
-inline glm::vec2 evaluateHammersley(int k, const int sequenceLength) {
-    return glm::vec2(float(k) / float(sequenceLength), getRadicalInverseVdC(k));
+namespace hammersley {
+    // Low discrepancy Hammersley 2D sequence generator
+    inline glm::vec2 evaluate(int k, const int sequenceLength) {
+        return glm::vec2(float(k) / float(sequenceLength), getRadicalInverseVdC(k));
+    }
 }
 
+
 #endif
\ No newline at end of file

From 361371b2a9909bc21a6f72af97cce4f7de0d8e5a Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Fri, 5 Apr 2019 08:58:06 +0200
Subject: [PATCH 19/23] Added flag to enable / disable ambient fresnel LUT

---
 libraries/render-utils/src/LightAmbient.slh               | 8 ++++----
 libraries/render-utils/src/LightingModel.cpp              | 4 ++++
 libraries/render-utils/src/render-utils/ShaderConstants.h | 4 ++++
 3 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/libraries/render-utils/src/LightAmbient.slh b/libraries/render-utils/src/LightAmbient.slh
index 8e7abd8861..cb76a8e545 100644
--- a/libraries/render-utils/src/LightAmbient.slh
+++ b/libraries/render-utils/src/LightAmbient.slh
@@ -30,13 +30,13 @@ vec4 evalSkyboxLight(vec3 direction, float lod) {
 LAYOUT(binding=RENDER_UTILS_TEXTURE_AMBIENT_FRESNEL) uniform sampler2D ambientFresnelLUT;
 
 vec3 fresnelSchlickAmbient(vec3 fresnelColor, float ndotd, float roughness) {
-#if 0
+#if RENDER_UTILS_ENABLE_AMBIENT_FRESNEL_LUT
+    vec2 ambientFresnel = texture(ambientFresnelLUT, vec2(roughness, ndotd)).xy;
+    return fresnelColor * ambientFresnel.x + vec3(ambientFresnel.y);
+#else
     float gloss = 1.0-roughness;
     float f = pow(1.0 - ndotd, 5.0);
     return fresnelColor + (max(vec3(gloss), fresnelColor) - fresnelColor) * f;
-#else
-    vec2 ambientFresnel = texture(ambientFresnelLUT, vec2(roughness, ndotd)).xy;
-    return fresnelColor * ambientFresnel.x + vec3(ambientFresnel.y);
 #endif
 }
 
diff --git a/libraries/render-utils/src/LightingModel.cpp b/libraries/render-utils/src/LightingModel.cpp
index cd49acd3da..5a9ab310c6 100644
--- a/libraries/render-utils/src/LightingModel.cpp
+++ b/libraries/render-utils/src/LightingModel.cpp
@@ -12,6 +12,8 @@
 #include "RandomAndNoise.h"
 #include "BRDF.h"
 
+#include "render-utils/ShaderConstants.h"
+
 #include <tbb/parallel_for.h>
 #include <tbb/blocked_range2d.h>
 
@@ -21,6 +23,7 @@ LightingModel::LightingModel() {
     Parameters parameters;
     _parametersBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Parameters), (const gpu::Byte*) &parameters, sizeof(Parameters)));
 
+#if RENDER_UTILS_ENABLE_AMBIENT_FRESNEL_LUT
     if (!_ambientFresnelLUT) {
         // Code taken from the IntegrateBRDF method as described in this talk :
         // https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
@@ -88,6 +91,7 @@ LightingModel::LightingModel() {
 
         _ambientFresnelLUT->assignStoredMip(0, N_roughness * N_NdotV * sizeof(LUTVector::value_type), (const gpu::Byte*)lut.data());
     }
+#endif
 }
 
 void LightingModel::setUnlit(bool enable) {
diff --git a/libraries/render-utils/src/render-utils/ShaderConstants.h b/libraries/render-utils/src/render-utils/ShaderConstants.h
index ffa580503b..76c8dd4981 100644
--- a/libraries/render-utils/src/render-utils/ShaderConstants.h
+++ b/libraries/render-utils/src/render-utils/ShaderConstants.h
@@ -14,6 +14,10 @@
 #ifndef RENDER_UTILS_SHADER_CONSTANTS_H
 #define RENDER_UTILS_SHADER_CONSTANTS_H
 
+// Feature enabling flags (possibly need to rebuild shaders if this changes)
+#define RENDER_UTILS_ENABLE_AMBIENT_FRESNEL_LUT 1
+
+// Binding slots
 #define RENDER_UTILS_ATTR_TEXCOORD01 0
 #define RENDER_UTILS_ATTR_COLOR 1
 

From 89ca7ac41557442825b3cc3e27f01f5cbf0ee1a0 Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Fri, 5 Apr 2019 10:22:57 +0200
Subject: [PATCH 20/23] Fixed compilation errors

---
 libraries/image/CMakeLists.txt               | 1 +
 libraries/image/src/image/CubeMap.cpp        | 5 ++---
 libraries/render-utils/src/LightingModel.cpp | 3 +--
 libraries/shared/src/TBBHelpers.h            | 1 +
 4 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/libraries/image/CMakeLists.txt b/libraries/image/CMakeLists.txt
index 0c733ae789..62f48f66e2 100644
--- a/libraries/image/CMakeLists.txt
+++ b/libraries/image/CMakeLists.txt
@@ -2,6 +2,7 @@ set(TARGET_NAME image)
 setup_hifi_library()
 link_hifi_libraries(shared gpu)
 target_nvtt()
+target_tbb()
 target_etc2comp()
 target_openexr()
 
diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index 5f54129cdc..fea3477d20 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -11,8 +11,7 @@
 #include "CubeMap.h"
 
 #include <cmath>
-#include <tbb/parallel_for.h>
-#include <tbb/blocked_range2d.h>
+#include <TBBHelpers.h>
 
 #include "RandomAndNoise.h"
 #include "BRDF.h"
@@ -551,7 +550,7 @@ void CubeMap::generateGGXSamples(GGXSamples& data, float roughness, const int re
         float D = ggx::evaluate(NdotH, roughness);
         float pdf = (D * NdotH / (4.0f * HdotV)) + 0.0001f;
         float saSample = 1.0f / (float(sampleCount) * pdf + 0.0001f);
-        float mipLevel = std::max(0.5f * log2(saSample / saTexel) + mipBias, 0.0f);
+        float mipLevel = std::max(0.5f * std::log2(saSample / saTexel) + mipBias, 0.0f);
 
         auto& sample = data.points[sampleIndex];
         sample.x = L.x;
diff --git a/libraries/render-utils/src/LightingModel.cpp b/libraries/render-utils/src/LightingModel.cpp
index 5a9ab310c6..5fcec1f033 100644
--- a/libraries/render-utils/src/LightingModel.cpp
+++ b/libraries/render-utils/src/LightingModel.cpp
@@ -14,8 +14,7 @@
 
 #include "render-utils/ShaderConstants.h"
 
-#include <tbb/parallel_for.h>
-#include <tbb/blocked_range2d.h>
+#include <TBBHelpers.h>
 
 gpu::TexturePointer LightingModel::_ambientFresnelLUT;
 
diff --git a/libraries/shared/src/TBBHelpers.h b/libraries/shared/src/TBBHelpers.h
index 6b5c4d416b..0c4deace6a 100644
--- a/libraries/shared/src/TBBHelpers.h
+++ b/libraries/shared/src/TBBHelpers.h
@@ -20,6 +20,7 @@
 #include <tbb/concurrent_unordered_set.h>
 #include <tbb/concurrent_vector.h>
 #include <tbb/parallel_for.h>
+#include <tbb/blocked_range2d.h>
 
 #ifdef _WIN32
 #pragma warning( pop )

From b66aa4a742fa59cae214914a39a30ffa0502f935 Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Fri, 5 Apr 2019 11:03:41 +0200
Subject: [PATCH 21/23] Fixed compilation errors on Mac & Ubuntu

---
 libraries/image/src/image/CubeMap.cpp         | 12 +++++----
 .../image/src/image/TextureProcessing.cpp     | 26 +++++++------------
 2 files changed, 16 insertions(+), 22 deletions(-)

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index fea3477d20..12c547933a 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -418,7 +418,9 @@ void CubeMap::getFaceUV(const glm::vec3& dir, int* index, glm::vec2* uv) {
     auto isYPositive = dir.y > 0;
     auto isZPositive = dir.z > 0;
 
-    float maxAxis, uc, vc;
+    float maxAxis = 1.0f;
+    float uc = 0.0f;
+    float vc = 0.0f;
 
     // POSITIVE X
     if (isXPositive && absX >= absY && absX >= absZ) {
@@ -518,8 +520,8 @@ void CubeMap::generateGGXSamples(GGXSamples& data, float roughness, const int re
     const float mipBias = 3.0f;
     const auto sampleCount = data.points.size();
     const auto hammersleySequenceLength = data.points.size();
-    int sampleIndex = 0;
-    int hammersleySampleIndex = 0;
+    size_t sampleIndex = 0;
+    size_t hammersleySampleIndex = 0;
     float NdotL;
 
     data.invTotalWeight = 0.0f;
@@ -636,14 +638,14 @@ void CubeMap::convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output,
 
 glm::vec4 CubeMap::computeConvolution(const glm::vec3& N, const GGXSamples& samples) const {
     // from tangent-space vector to world-space
-    glm::vec3 bitangent = abs(N.z) < 0.999 ? glm::vec3(0.0, 0.0, 1.0) : glm::vec3(1.0, 0.0, 0.0);
+    glm::vec3 bitangent = std::abs(N.z) < 0.999 ? glm::vec3(0.0, 0.0, 1.0) : glm::vec3(1.0, 0.0, 0.0);
     glm::vec3 tangent = glm::normalize(glm::cross(bitangent, N));
     bitangent = glm::cross(N, tangent);
 
     const size_t sampleCount = samples.points.size();
     glm::vec4 prefilteredColor = glm::vec4(0.0f);
 
-    for (int i = 0; i < sampleCount; ++i) {
+    for (size_t i = 0; i < sampleCount; ++i) {
         const auto& sample = samples.points[i];
         glm::vec3 L(sample.x, sample.y, sample.z);
         float NdotL = L.z;
diff --git a/libraries/image/src/image/TextureProcessing.cpp b/libraries/image/src/image/TextureProcessing.cpp
index d3b34b84fe..ee2f5b280a 100644
--- a/libraries/image/src/image/TextureProcessing.cpp
+++ b/libraries/image/src/image/TextureProcessing.cpp
@@ -534,8 +534,8 @@ public:
 };
 #endif
 
-void image::convertToFloatFromPacked(const unsigned char* source, int width, int height, size_t srcLineByteStride, gpu::Element sourceFormat,
-                                     glm::vec4* output, size_t outputLinePixelStride) {
+void convertToFloatFromPacked(const unsigned char* source, int width, int height, size_t srcLineByteStride, gpu::Element sourceFormat,
+                              glm::vec4* output, size_t outputLinePixelStride) {
     glm::vec4* outputIt;
     auto unpackFunc = getHDRUnpackingFunction(sourceFormat);
 
@@ -554,8 +554,8 @@ void image::convertToFloatFromPacked(const unsigned char* source, int width, int
     }
 }
 
-void image::convertToPackedFromFloat(unsigned char* output, int width, int height, size_t outputLineByteStride, gpu::Element outputFormat,
-                                     const glm::vec4* source, size_t srcLinePixelStride) {
+void convertToPackedFromFloat(unsigned char* output, int width, int height, size_t outputLineByteStride, gpu::Element outputFormat,
+                              const glm::vec4* source, size_t srcLinePixelStride) {
     const glm::vec4* sourceIt;
     auto packFunc = getHDRPackingFunction(outputFormat);
 
@@ -576,10 +576,6 @@ void image::convertToPackedFromFloat(unsigned char* output, int width, int heigh
 
 nvtt::OutputHandler* getNVTTCompressionOutputHandler(gpu::Texture* outputTexture, int face, nvtt::CompressionOptions& compressionOptions) {
     auto outputFormat = outputTexture->getStoredMipFormat();
-
-    nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F;
-    nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
-    nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
     bool useNVTT = false;
 
     compressionOptions.setQuality(nvtt::Quality_Production);
@@ -620,12 +616,8 @@ void convertImageToHDRTexture(gpu::Texture* texture, Image&& image, BackendTarge
 
     Image localCopy = image.getConvertedToFormat(Image::Format_RGBAF);
 
-    const int width = localCopy.getWidth(), height = localCopy.getHeight();
-    auto mipFormat = texture->getStoredMipFormat();
-
-    nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F;
-    nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
-    nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
+    const int width = localCopy.getWidth();
+    const int height = localCopy.getHeight();
 
     nvtt::OutputOptions outputOptions;
     outputOptions.setOutputHeader(false);
@@ -641,9 +633,9 @@ void convertImageToHDRTexture(gpu::Texture* texture, Image&& image, BackendTarge
     outputOptions.setOutputHandler(outputHandler.get());
 
     nvtt::Surface surface;
-    surface.setImage(inputFormat, width, height, 1, localCopy.getBits());
-    surface.setAlphaMode(alphaMode);
-    surface.setWrapMode(wrapMode);
+    surface.setImage(nvtt::InputFormat_RGBA_32F, width, height, 1, localCopy.getBits());
+    surface.setAlphaMode(nvtt::AlphaMode_None);
+    surface.setWrapMode(nvtt::WrapMode_Mirror);
 
     SequentialTaskDispatcher dispatcher(abortProcessing);
     nvtt::Compressor compressor;

From 08aa133472c4f952c2f8150d95a8811bf53513ab Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Fri, 5 Apr 2019 11:32:00 +0200
Subject: [PATCH 22/23] Fixed other compilation errors / warnings

---
 libraries/image/src/image/CubeMap.cpp | 2 +-
 libraries/shared/src/BRDF.cpp         | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index 12c547933a..f5637062af 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -638,7 +638,7 @@ void CubeMap::convolveMipFaceForGGX(const GGXSamples& samples, CubeMap& output,
 
 glm::vec4 CubeMap::computeConvolution(const glm::vec3& N, const GGXSamples& samples) const {
     // from tangent-space vector to world-space
-    glm::vec3 bitangent = std::abs(N.z) < 0.999 ? glm::vec3(0.0, 0.0, 1.0) : glm::vec3(1.0, 0.0, 0.0);
+    glm::vec3 bitangent = std::abs(N.z) < 0.999f ? glm::vec3(0.0f, 0.0f, 1.0f) : glm::vec3(1.0f, 0.0f, 0.0f);
     glm::vec3 tangent = glm::normalize(glm::cross(bitangent, N));
     bitangent = glm::cross(N, tangent);
 
diff --git a/libraries/shared/src/BRDF.cpp b/libraries/shared/src/BRDF.cpp
index 726a233fc7..fe438f12a1 100644
--- a/libraries/shared/src/BRDF.cpp
+++ b/libraries/shared/src/BRDF.cpp
@@ -10,16 +10,16 @@ namespace ggx {
 float evaluate(float NdotH, float roughness) {
     float alpha = roughness * roughness;
     float alphaSquared = alpha * alpha;
-    float denom = (float)(NdotH * NdotH * (alphaSquared - 1.0) + 1.0);
+    float denom = (float)(NdotH * NdotH * (alphaSquared - 1.0f) + 1.0f);
     return alphaSquared / (denom * denom);
 }
 
 glm::vec3 sample(const glm::vec2& Xi, const float roughness) {
     const float a = roughness * roughness;
 
-    float phi = (float)(2.0 * M_PI * Xi.x);
-    float cosTheta = (float)(std::sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)));
-    float sinTheta = (float)(std::sqrt(1.0 - cosTheta * cosTheta));
+    float phi = 2.0f * (float) M_PI * Xi.x;
+    float cosTheta = std::sqrt((1.0f - Xi.y) / (1.0f + (a*a - 1.0f) * Xi.y));
+    float sinTheta = std::sqrt(1.0f - cosTheta * cosTheta);
 
     // from spherical coordinates to cartesian coordinates
     glm::vec3 H;

From 1322fef56856c1e694e1bcd6192f93115e20b7f5 Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Fri, 5 Apr 2019 20:15:58 +0200
Subject: [PATCH 23/23] Taking into account samuel's remarks

---
 libraries/baking/src/TextureBaker.cpp           | 6 +-----
 libraries/image/src/image/CubeMap.cpp           | 8 ++++----
 libraries/image/src/image/TextureProcessing.cpp | 1 -
 tools/oven/src/ui/SkyboxBakeWidget.cpp          | 6 +++---
 4 files changed, 8 insertions(+), 13 deletions(-)

diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp
index bed0e9a40b..54d304b7d8 100644
--- a/libraries/baking/src/TextureBaker.cpp
+++ b/libraries/baking/src/TextureBaker.cpp
@@ -133,11 +133,7 @@ void TextureBaker::processTexture() {
     // so we add that to the processed texture before handling it off to be serialized
     QCryptographicHash hasher(QCryptographicHash::Md5);
     hasher.addData(_originalTexture);
-    // An ambient texture is built with the same pixel data as sky texture but its Mip Maps are different
-    // so we mustn't use one instead of the other.
-    if (_textureType == image::TextureUsage::AMBIENT_TEXTURE) {
-        hasher.addData((const char*)&_textureType, sizeof(_textureType));
-    }
+    hasher.addData((const char*)&_textureType, sizeof(_textureType));
     auto hashData = hasher.result();
     std::string hash = hashData.toHex().toStdString();
 
diff --git a/libraries/image/src/image/CubeMap.cpp b/libraries/image/src/image/CubeMap.cpp
index f5637062af..9196377daa 100644
--- a/libraries/image/src/image/CubeMap.cpp
+++ b/libraries/image/src/image/CubeMap.cpp
@@ -99,10 +99,10 @@ public:
         const size_t offsetHL = hiCoords.x + loCoords.y * _lineStride;
         const size_t offsetLH = loCoords.x + hiCoords.y * _lineStride;
         const size_t offsetHH = hiCoords.x + hiCoords.y * _lineStride;
-        assert(offsetLL >= 0 && offsetLL < _lineStride*(_dims.y + 2 * EDGE_WIDTH));
-        assert(offsetHL >= 0 && offsetHL < _lineStride*(_dims.y + 2 * EDGE_WIDTH));
-        assert(offsetLH >= 0 && offsetLH < _lineStride*(_dims.y + 2 * EDGE_WIDTH));
-        assert(offsetHH >= 0 && offsetHH < _lineStride*(_dims.y + 2 * EDGE_WIDTH));
+        assert(offsetLL >= 0 && offsetLL < _lineStride * (_dims.y + 2 * EDGE_WIDTH));
+        assert(offsetHL >= 0 && offsetHL < _lineStride * (_dims.y + 2 * EDGE_WIDTH));
+        assert(offsetLH >= 0 && offsetLH < _lineStride * (_dims.y + 2 * EDGE_WIDTH));
+        assert(offsetHH >= 0 && offsetHH < _lineStride * (_dims.y + 2 * EDGE_WIDTH));
         glm::vec4 colorLL = pixels[offsetLL];
         glm::vec4 colorHL = pixels[offsetHL];
         glm::vec4 colorLH = pixels[offsetLH];
diff --git a/libraries/image/src/image/TextureProcessing.cpp b/libraries/image/src/image/TextureProcessing.cpp
index ee2f5b280a..5b3d546f8e 100644
--- a/libraries/image/src/image/TextureProcessing.cpp
+++ b/libraries/image/src/image/TextureProcessing.cpp
@@ -580,7 +580,6 @@ nvtt::OutputHandler* getNVTTCompressionOutputHandler(gpu::Texture* outputTexture
 
     compressionOptions.setQuality(nvtt::Quality_Production);
 
-    // TODO: gles: generate ETC mips instead?
     if (outputFormat == gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB) {
         useNVTT = true;
         compressionOptions.setFormat(nvtt::Format_BC6);
diff --git a/tools/oven/src/ui/SkyboxBakeWidget.cpp b/tools/oven/src/ui/SkyboxBakeWidget.cpp
index 2bea38b571..6c6e0340ac 100644
--- a/tools/oven/src/ui/SkyboxBakeWidget.cpp
+++ b/tools/oven/src/ui/SkyboxBakeWidget.cpp
@@ -214,12 +214,12 @@ void SkyboxBakeWidget::addBaker(TextureBaker* baker, const QDir& outputDirectory
     // move the textureBaker to a worker thread
     textureBaker->moveToThread(Oven::instance().getNextWorkerThread());
 
-    // invoke the bake method on the textureBaker thread
-    QMetaObject::invokeMethod(textureBaker.get(), "bake");
-
     // make sure we hear about the results of this textureBaker when it is done
     connect(textureBaker.get(), &TextureBaker::finished, this, &SkyboxBakeWidget::handleFinishedBaker);
 
+    // invoke the bake method on the textureBaker thread
+    QMetaObject::invokeMethod(textureBaker.get(), "bake");
+
     // add a pending row to the results window to show that this bake is in process
     auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow();
     auto resultsRow = resultsWindow->addPendingResultRow(baker->getBaseFilename(), outputDirectory);