Fixed compression when convolving

This commit is contained in:
Olivier Prat 2019-03-28 18:50:12 +01:00
parent cef9e454d5
commit 5f6f178438
4 changed files with 216 additions and 115 deletions

View file

@ -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) {

View file

@ -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;

View file

@ -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;

View file

@ -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