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*) ¶meters, 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*) ¶meters, 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);