Finish Compressor class.

Move all the image processing to Compressor.cpp.
Finish rescale support, close issue 12.
2.0
castano 17 years ago
parent 6f28642282
commit 3b24951e93

@ -48,7 +48,6 @@ void CompressionOptions::reset()
m.format = Format_DXT1;
m.quality = Quality_Normal;
m.colorWeight.set(1.0f, 1.0f, 1.0f, 1.0f);
m.useCuda = true;
m.bitcount = 32;
m.bmask = 0x000000FF;
@ -91,20 +90,6 @@ void CompressionOptions::setColorWeights(float red, float green, float blue, flo
m.colorWeight.set(red, green, blue, alpha);
}
/*
/// Enable or disable CUDA compression.
void CompressionOptions::enableHardwareCompression(bool enable)
{
m.useCuda = enable;
}
/// Enable or disable CUDA compression.
void CompressionOptions::enableCudaCompression(bool enable)
{
m.useCuda = enable;
}
*/
/// Set color mask to describe the RGB/RGBA format.
void CompressionOptions::setPixelFormat(uint bitcount, uint rmask, uint gmask, uint bmask, uint amask)

@ -46,8 +46,6 @@ namespace nvtt
uint bmask;
uint amask;
mutable bool useCuda;
nv::String externalCompressor;
// Quantization.

@ -93,6 +93,10 @@ namespace
}
}
} // namespace
namespace nvtt
{
// Mipmap could be:
// - a pointer to an input image.
// - a fixed point image.
@ -103,35 +107,72 @@ namespace
~Mipmap() {}
// Reference input image.
void set(const InputOptions::Private & inputOptions, uint f, uint m)
void setFromInput(const InputOptions::Private & inputOptions, uint idx)
{
m_inputImage = inputOptions.image(f, m);
m_inputImage = inputOptions.image(idx);
m_fixedImage = NULL;
m_floatImage = NULL;
}
// Assign and take ownership of given image.
void set(FloatImage * image)
void setImage(FloatImage * image)
{
m_inputImage = NULL;
m_fixedImage = NULL;
m_floatImage = image;
}
// Assign and take ownership of given image.
void set(Image * image)
// Convert linear float image to fixed image ready for compression.
void toFixedImage(const InputOptions::Private & inputOptions)
{
m_inputImage = NULL;
m_fixedImage = image;
m_floatImage = NULL;
if (this->asFixedImage() == NULL)
{
nvDebugCheck(m_floatImage != NULL);
if (inputOptions.isNormalMap || inputOptions.outputGamma == 1.0f)
{
m_fixedImage = m_floatImage->createImage();
}
else
{
m_fixedImage = m_floatImage->createImageGammaCorrect(inputOptions.outputGamma);
}
}
}
// Convert input image to linear float image.
void toFloatImage(const InputOptions::Private & inputOptions)
{
if (m_floatImage == NULL)
{
nvDebugCheck(this->asFixedImage() != NULL);
m_floatImage = new FloatImage(this->asFixedImage());
if (inputOptions.isNormalMap)
{
// Expand normals to [-1, 1] range.
// floatImage->expandNormals(0);
}
else if (inputOptions.inputGamma != 1.0f)
{
// Convert to linear space.
m_floatImage->toLinear(0, 3, inputOptions.inputGamma);
}
}
}
const FloatImage * asFloatImage() const
{
return m_floatImage.ptr();
}
FloatImage * asFloatImage()
{
return m_floatImage.ptr();
}
const Image * asFixedImage() const
{
if (m_inputImage != NULL)
@ -140,15 +181,18 @@ namespace
}
return m_fixedImage.ptr();
}
/*void toFixed(const InputOptions::Private & inputOptions)
Image * asMutableFixedImage()
{
if (m_floatImage != NULL)
if (m_inputImage != NULL)
{
// Convert to fixed.
m_fixedImage = toFixedImage(m_floatImage.ptr(), inputOptions);
// Do not modify input image, create a copy.
m_fixedImage = new Image(*m_inputImage);
m_inputImage = NULL;
}
}*/
return m_fixedImage.ptr();
}
private:
const Image * m_inputImage;
@ -156,13 +200,13 @@ namespace
AutoPtr<FloatImage> m_floatImage;
};
} // namespace
}
Compressor::Compressor() : m(*new Compressor::Private())
{
m.cudaSupported = cuda::isHardwarePresent();
m.cudaEnabled = true;
m.cudaEnabled = m.cudaSupported;
// @@ Do CUDA initialization here.
@ -189,15 +233,13 @@ bool Compressor::isCudaAccelerationEnabled() const
return m.cudaEnabled;
}
#if 0
/// Compress the input texture with the given compression options.
bool Compressor::process(const InputOptions & inputOptions, const CompressionOptions & compressionOptions, const OutputOptions & outputOptions) const
{
return m.compress(inputOptions.m, outputOptions.m, compressionOptions.m);
return m.compress(inputOptions.m, compressionOptions.m, outputOptions.m);
}
#endif // 0
/// Estimate the size of compressing the input with the given options.
int Compressor::estimateSize(const InputOptions & inputOptions, const CompressionOptions & compressionOptions) const
@ -331,23 +373,8 @@ bool Compressor::Private::compressMipmaps(uint f, const InputOptions::Private &
uint w = inputOptions.targetWidth;
uint h = inputOptions.targetHeight;
uint d = inputOptions.targetDepth;
/*
int inputImageIdx = findMipmap(inputOptions, f, 0, w, h, d);
if (inputImageIdx == -1)
{
// First mipmap missing.
if (outputOptions.errorHandler != NULL) outputOptions.errorHandler->error(Error_InvalidInput);
return false;
}
ImagePair pair;
*/
Mipmap mipmap;
// Mipmap could be:
// - a pointer to an input image.
// - a fixed point image.
// - a floating point image.
const uint mipmapCount = inputOptions.realMipmapCount();
nvDebugCheck(mipmapCount > 0);
@ -359,21 +386,23 @@ bool Compressor::Private::compressMipmaps(uint f, const InputOptions::Private &
int size = computeImageSize(w, h, d, compressionOptions.bitcount, compressionOptions.format);
outputOptions.outputHandler->mipmap(size, w, h, d, f, m);
}
/*
inputImageIdx = findImage(inputOptions, f, w, h, d, inputImageIdx, &pair);
// @@ Where to do the color transform?
// - Color transform may not be linear, so we cannot do before computing mipmaps.
// - Should be done in linear space, that is, after gamma correction.
if (!initMipmap(mipmap, inputOptions, w, h, d, f, m))
{
if (outputOptions.errorHandler != NULL)
{
outputOptions.errorHandler->error(Error_InvalidInput);
return false;
}
}
pair.toFixed(inputOptions);
// @@ Quantization should be done in compressMipmap! @@ It should not modify the input image!!!
quantize(pair.fixedImage(), compressionOptions);
compressMipmap(pair.fixedImage(), outputOptions, compressionOptions);
*/
quantizeMipmap(mipmap, compressionOptions);
compressMipmap(mipmap, compressionOptions, outputOptions);
// Compute extents of next mipmap:
w = max(1U, w / 2);
@ -384,292 +413,234 @@ bool Compressor::Private::compressMipmaps(uint f, const InputOptions::Private &
return true;
}
#if 0
// Convert input image to linear float image.
static FloatImage * toFloatImage(const Image * image, const InputOptions::Private & inputOptions)
bool Compressor::Private::initMipmap(Mipmap & mipmap, const InputOptions::Private & inputOptions, uint w, uint h, uint d, uint f, uint m) const
{
nvDebugCheck(image != NULL);
// Find image from input.
int inputIdx = findExactMipmap(inputOptions, w, h, d, f);
FloatImage * floatImage = new FloatImage(image);
if (inputOptions.isNormalMap)
if (inputIdx == -1 && m != 0)
{
// Expand normals. to [-1, 1] range.
// floatImage->expandNormals(0);
// If input mipmap not found, and not top of the chain, then generate from last.
downsampleMipmap(mipmap, inputOptions);
}
else if (inputOptions.inputGamma != 1.0f)
else
{
// Convert to linear space.
floatImage->toLinear(0, 3, inputOptions.inputGamma);
if (inputIdx != -1)
{
// If input mipmap found, then get from input.
mipmap.setFromInput(inputOptions, inputIdx);
}
else
{
// If not found, resize closest mipmap.
inputIdx = findClosestMipmap(inputOptions, w, h, d, f);
if (inputIdx == -1)
{
return false;
}
mipmap.setFromInput(inputOptions, inputIdx);
scaleMipmap(mipmap, inputOptions, w, h, d);
}
processInputImage(mipmap, inputOptions);
}
return floatImage;
}
// Convert linear float image to fixed image ready for compression.
mipmap.toFixedImage(inputOptions);
return true;
}
// Convert linear float image to output image.
static Image * toFixedImage(const FloatImage * floatImage, const InputOptions::Private & inputOptions)
int Compressor::Private::findExactMipmap(const InputOptions::Private & inputOptions, uint w, uint h, uint d, uint f) const
{
nvDebugCheck(floatImage != NULL);
if (inputOptions.isNormalMap || inputOptions.outputGamma == 1.0f)
for (int m = 0; m < int(inputOptions.mipmapCount); m++)
{
return floatImage->createImage();
int idx = f * inputOptions.mipmapCount + m;
const InputOptions::Private::InputImage & inputImage = inputOptions.images[idx];
if (inputImage.width == int(w) && inputImage.height == int(h) && inputImage.depth == int(d))
{
if (inputImage.data != NULL)
{
return idx;
}
return -1;
}
else if (inputImage.width < int(w) || inputImage.height < int(h) || inputImage.depth < int(d))
{
return -1;
}
}
else
return -1;
}
int Compressor::Private::findClosestMipmap(const InputOptions::Private & inputOptions, uint w, uint h, uint d, uint f) const
{
int bestIdx = -1;
for (int m = 0; m < int(inputOptions.mipmapCount); m++)
{
return floatImage->createImageGammaCorrect(inputOptions.outputGamma);
int idx = f * inputOptions.mipmapCount + m;
const InputOptions::Private::InputImage & inputImage = inputOptions.images[idx];
if (inputImage.data != NULL)
{
int difference = (inputImage.width - w) + (inputImage.height - h) + (inputImage.depth - d);
if (difference < 0)
{
if (bestIdx == -1)
{
bestIdx = idx;
}
return bestIdx;
}
bestIdx = idx;
}
}
}
return bestIdx;
}
// Create mipmap from the given image.
static FloatImage * createMipmap(const FloatImage * floatImage, const InputOptions::Private & inputOptions)
void Compressor::Private::downsampleMipmap(Mipmap & mipmap, const InputOptions::Private & inputOptions) const
{
FloatImage * result = NULL;
// Make sure that floating point linear representation is available.
mipmap.toFloatImage(inputOptions);
const FloatImage * floatImage = mipmap.asFloatImage();
if (inputOptions.mipmapFilter == MipmapFilter_Box)
{
// Use fast downsample.
result = floatImage->fastDownSample();
mipmap.setImage(floatImage->fastDownSample());
}
else if (inputOptions.mipmapFilter == MipmapFilter_Triangle)
{
TriangleFilter filter;
result = floatImage->downSample(filter, (FloatImage::WrapMode)inputOptions.wrapMode);
mipmap.setImage(floatImage->downSample(filter, (FloatImage::WrapMode)inputOptions.wrapMode));
}
else /*if (inputOptions.mipmapFilter == MipmapFilter_Kaiser)*/
{
nvDebugCheck(inputOptions.mipmapFilter == MipmapFilter_Kaiser);
KaiserFilter filter(inputOptions.kaiserWidth);
filter.setParameters(inputOptions.kaiserAlpha, inputOptions.kaiserStretch);
result = floatImage->downSample(filter, (FloatImage::WrapMode)inputOptions.wrapMode);
mipmap.setImage(floatImage->downSample(filter, (FloatImage::WrapMode)inputOptions.wrapMode));
}
// Normalize mipmap.
if ((inputOptions.isNormalMap || inputOptions.convertToNormalMap) && inputOptions.normalizeMipmaps)
{
normalizeNormalMap(result);
normalizeNormalMap(mipmap.asFloatImage());
}
return result;
}
// Quantize the input image to the precision of the output format.
static void quantize(Image * img, const CompressionOptions::Private & compressionOptions)
void Compressor::Private::scaleMipmap(Mipmap & mipmap, const InputOptions::Private & inputOptions, uint w, uint h, uint d) const
{
if (compressionOptions.enableColorDithering)
{
if (compressionOptions.format >= Format_DXT1 && compressionOptions.format <= Format_DXT5)
{
Quantize::FloydSteinberg_RGB16(img);
}
}
if (compressionOptions.binaryAlpha)
{
if (compressionOptions.enableAlphaDithering)
{
Quantize::FloydSteinberg_BinaryAlpha(img, compressionOptions.alphaThreshold);
}
else
{
Quantize::BinaryAlpha(img, compressionOptions.alphaThreshold);
}
}
else
{
if (compressionOptions.enableAlphaDithering)
{
if (compressionOptions.format == Format_DXT3)
{
Quantize::Alpha4(img);
}
else if (compressionOptions.format == Format_DXT1a)
{
Quantize::BinaryAlpha(img, compressionOptions.alphaThreshold);
}
}
}
mipmap.toFloatImage(inputOptions);
// @@ Add more filters.
// @@ Select different filters for downscaling and reconstruction.
// Resize image.
BoxFilter boxFilter;
mipmap.setImage(mipmap.asFloatImage()->downSample(boxFilter, w, h, (FloatImage::WrapMode)inputOptions.wrapMode));
}
// Process the input, convert to normal map, normalize, or convert to linear space.
static FloatImage * processInput(const InputOptions::Private & inputOptions, int idx)
// Process an input image: Convert to normal map, normalize, or convert to linear space.
void Compressor::Private::processInputImage(Mipmap & mipmap, const InputOptions::Private & inputOptions) const
{
const InputOptions::Private::Image & mipmap = inputOptions.images[idx];
if (inputOptions.convertToNormalMap)
{
// Scale height factor by 1 / 2 ^ m // @@ Compute scale factor exactly...
Vector4 heightScale = inputOptions.heightFactors / float(1 << idx);
return createNormalMap(mipmap.data.ptr(), (FloatImage::WrapMode)inputOptions.wrapMode, heightScale, inputOptions.bumpFrequencyScale);
mipmap.toFixedImage(inputOptions);
// @@ Compute heighmap scale factor correctly.
// m = original_width / this_width
// Scale height factor by 1 / 2 ^ m
Vector4 heightScale = inputOptions.heightFactors; // / float(1 << m);
mipmap.setImage(createNormalMap(mipmap.asFixedImage(), (FloatImage::WrapMode)inputOptions.wrapMode, heightScale, inputOptions.bumpFrequencyScale));
}
else if (inputOptions.isNormalMap)
{
if (inputOptions.normalizeMipmaps)
{
FloatImage * img = new FloatImage(mipmap.data.ptr());
img->normalize(0);
return img;
}
// If floating point image available, normalize in place.
if (mipmap.asFloatImage() == NULL)
{
FloatImage * floatImage = new FloatImage(mipmap.asFixedImage());
normalizeNormalMap(floatImage);
mipmap.setImage(floatImage);
}
else
{
normalizeNormalMap(mipmap.asFloatImage());
mipmap.setImage(mipmap.asFloatImage());
}
}
}
else
{
if (inputOptions.inputGamma != inputOptions.outputGamma)
{
FloatImage * img = new FloatImage(mipmap.data.ptr());
img->toLinear(0, 3, inputOptions.inputGamma);
return img;
mipmap.toFloatImage(inputOptions);
}
}
return NULL;
}
struct ImagePair
{
ImagePair() : m_floatImage(NULL), m_fixedImage(NULL), m_deleteFixedImage(false) {}
~ImagePair()
{
if (m_deleteFixedImage) {
delete m_fixedImage;
}
}
void setFloatImage(FloatImage * image)
{
m_floatImage = image;
if (m_deleteFixedImage) delete m_fixedImage;
m_fixedImage = NULL;
}
void setFixedImage(Image * image, bool deleteImage)
{
m_floatImage = NULL;
if (m_deleteFixedImage) delete m_fixedImage;
m_fixedImage = image;
m_deleteFixedImage = deleteImage;
}
FloatImage * floatImage() const { return m_floatImage.ptr(); }
Image * fixedImage() const { return m_fixedImage; }
void toFixed(const InputOptions::Private & inputOptions)
{
if (m_floatImage != NULL)
{
// Convert to fixed.
m_fixedImage = toFixedImage(m_floatImage.ptr(), inputOptions);
}
}
private:
AutoPtr<FloatImage> m_floatImage;
Image * m_fixedImage;
bool m_deleteFixedImage;
};
// Find the first mipmap provided that is greater or equal to the target image size.
static int findMipmap(const InputOptions::Private & inputOptions, uint f, int firstMipmap, uint w, uint h, uint d)
// Quantize the given mipmap according to the compression options.
void Compressor::Private::quantizeMipmap(Mipmap & mipmap, const CompressionOptions::Private & compressionOptions) const
{
nvDebugCheck(firstMipmap >= 0);
nvDebugCheck(mipmap.asFixedImage() != NULL);
int bestIdx = -1;
for (int m = firstMipmap; m < int(inputOptions.mipmapCount); m++)
if (compressionOptions.enableColorDithering)
{
int idx = f * inputOptions.mipmapCount + m;
const InputOptions::Private::Image & mipmap = inputOptions.images[idx];
if (mipmap.width >= int(w) && mipmap.height >= int(h) && mipmap.depth >= int(d))
{
if (mipmap.data != NULL)
{
bestIdx = idx;
}
}
else
if (compressionOptions.format >= Format_DXT1 && compressionOptions.format <= Format_DXT5)
{
// Do not look further down.
break;
Quantize::FloydSteinberg_RGB16(mipmap.asMutableFixedImage());
}
}
return bestIdx;
}
static int findImage(const InputOptions::Private & inputOptions, uint f, uint w, uint h, uint d, int inputImageIdx, ImagePair * pair)
{
nvDebugCheck(w > 0 && h > 0);
nvDebugCheck(inputImageIdx >= 0 && inputImageIdx < int(inputOptions.mipmapCount));
nvDebugCheck(pair != NULL);
int bestIdx = findMipmap(inputOptions, f, inputImageIdx, w, h, d);
const InputOptions::Private::Image & mipmap = inputOptions.images[bestIdx];
if (mipmap.width == w && mipmap.height == h && mipmap.depth == d)
if (compressionOptions.binaryAlpha)
{
// Generate from input image.
AutoPtr<FloatImage> processedImage( processInput(inputOptions, bestIdx) );
if (processedImage != NULL)
if (compressionOptions.enableAlphaDithering)
{
pair->setFloatImage(processedImage.release());
Quantize::FloydSteinberg_BinaryAlpha(mipmap.asMutableFixedImage(), compressionOptions.alphaThreshold);
}
else
{
pair->setFixedImage(mipmap.data.ptr(), false);
Quantize::BinaryAlpha(mipmap.asMutableFixedImage(), compressionOptions.alphaThreshold);
}
return bestIdx;
}
else
{
if (pair->floatImage() == NULL && pair->fixedImage() == NULL)
if (compressionOptions.enableAlphaDithering)
{
// Generate from input image and resize.
AutoPtr<FloatImage> processedImage( processInput(inputOptions, bestIdx) );
if (processedImage == NULL)
if (compressionOptions.format == Format_DXT3)
{
processedImage = new FloatImage(mipmap.data.ptr());
Quantize::Alpha4(mipmap.asMutableFixedImage());
}
// Resize image. @@ Add more filters. @@ Distinguish between downscaling and reconstruction filters.
BoxFilter boxFilter;
pair->setFloatImage(processedImage->downSample(boxFilter, w, h, (FloatImage::WrapMode)inputOptions.wrapMode));
}
else
{
// Generate from previous mipmap.
if (pair->floatImage() == NULL)
else if (compressionOptions.format == Format_DXT1a)
{
nvDebugCheck(pair->fixedImage() != NULL);
pair->setFloatImage(toFloatImage(pair->fixedImage(), inputOptions));
Quantize::BinaryAlpha(mipmap.asMutableFixedImage(), compressionOptions.alphaThreshold);
}
// Create mipmap.
pair->setFloatImage(createMipmap(pair->floatImage(), inputOptions));
}
}
return bestIdx; // @@ ???
}
#endif // 0
bool Compressor::Private::compressMipmap(const Image * image, const CompressionOptions::Private & compressionOptions, const OutputOptions::Private & outputOptions) const
// Compress the given mipmap.
bool Compressor::Private::compressMipmap(const Mipmap & mipmap, const CompressionOptions::Private & compressionOptions, const OutputOptions::Private & outputOptions) const
{
const Image * image = mipmap.asFixedImage();
nvDebugCheck(image != NULL);
if (compressionOptions.format == Format_RGBA || compressionOptions.format == Format_RGB)

@ -33,6 +33,7 @@ namespace nv
namespace nvtt
{
struct Mipmap;
struct Compressor::Private
{
@ -45,7 +46,17 @@ namespace nvtt
bool outputHeader(const InputOptions::Private & inputOptions, const CompressionOptions::Private & compressionOptions, const OutputOptions::Private & outputOptions) const;
bool compressMipmaps(uint f, const InputOptions::Private & inputOptions, const CompressionOptions::Private & compressionOptions, const OutputOptions::Private & outputOptions) const;
bool compressMipmap(const nv::Image * image, const CompressionOptions::Private & compressionOptions, const OutputOptions::Private & outputOptions) const;
bool initMipmap(Mipmap & mipmap, const InputOptions::Private & inputOptions, uint w, uint h, uint d, uint f, uint m) const;
int findExactMipmap(const InputOptions::Private & inputOptions, uint w, uint h, uint d, uint f) const;
int findClosestMipmap(const InputOptions::Private & inputOptions, uint w, uint h, uint d, uint f) const;
void downsampleMipmap(Mipmap & mipmap, const InputOptions::Private & inputOptions) const;
void scaleMipmap(Mipmap & mipmap, const InputOptions::Private & inputOptions, uint w, uint h, uint d) const;
void processInputImage(Mipmap & mipmap, const InputOptions::Private & inputOptions) const;
void quantizeMipmap(Mipmap & mipmap, const CompressionOptions::Private & compressionOptions) const;
bool compressMipmap(const Mipmap & mipmap, const CompressionOptions::Private & compressionOptions, const OutputOptions::Private & outputOptions) const;
public:

@ -392,3 +392,12 @@ const Image * InputOptions::Private::image(uint face, uint mipmap) const
return image.data.ptr();
}
const Image * InputOptions::Private::image(uint idx) const
{
nvDebugCheck(idx < faceCount * mipmapCount);
const InputImage & image = this->images[idx];
return image.data.ptr();
}

@ -89,6 +89,7 @@ namespace nvtt
int realMipmapCount() const;
const nv::Image * image(uint face, uint mipmap) const;
const nv::Image * image(uint idx) const;
};

@ -21,666 +21,10 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#include <nvcore/Memory.h>
#include <nvcore/Ptr.h>
#include "nvtt.h"
#include <nvimage/DirectDrawSurface.h>
#include <nvimage/ColorBlock.h>
#include <nvimage/BlockDXT.h>
#include <nvimage/Image.h>
#include <nvimage/FloatImage.h>
#include <nvimage/Filter.h>
#include <nvimage/Quantize.h>
#include <nvimage/NormalMap.h>
#include "Compressor.h"
#include "InputOptions.h"
#include "CompressionOptions.h"
#include "OutputOptions.h"
#include "CompressDXT.h"
#include "FastCompressDXT.h"
#include "CompressRGB.h"
#include "cuda/CudaUtils.h"
#include "cuda/CudaCompressDXT.h"
using namespace nv;
using namespace nvtt;
namespace
{
static int blockSize(Format format)
{
if (format == Format_DXT1 || format == Format_DXT1a) {
return 8;
}
else if (format == Format_DXT3) {
return 16;
}
else if (format == Format_DXT5 || format == Format_DXT5n) {
return 16;
}
else if (format == Format_BC4) {
return 8;
}
else if (format == Format_BC5) {
return 16;
}
return 0;
}
inline uint computePitch(uint w, uint bitsize)
{
uint p = w * ((bitsize + 7) / 8);
// Align to 32 bits.
return ((p + 3) / 4) * 4;
}
static int computeImageSize(uint w, uint h, uint bitCount, Format format)
{
if (format == Format_RGBA) {
return h * computePitch(w, bitCount);
}
else {
return ((w + 3) / 4) * ((h + 3) / 4) * blockSize(format);
}
}
} // namespace
//
// compress
//
static bool outputHeader(const InputOptions::Private & inputOptions, const OutputOptions::Private & outputOptions, const CompressionOptions::Private & compressionOptions)
{
// Output DDS header.
if (outputOptions.outputHandler == NULL || !outputOptions.outputHeader)
{
return true;
}
DDSHeader header;
header.setWidth(inputOptions.targetWidth);
header.setHeight(inputOptions.targetHeight);
int mipmapCount = inputOptions.realMipmapCount();
nvDebugCheck(mipmapCount > 0);
header.setMipmapCount(mipmapCount);
if (inputOptions.textureType == TextureType_2D) {
header.setTexture2D();
}
else if (inputOptions.textureType == TextureType_Cube) {
header.setTextureCube();
}
/*else if (inputOptions.textureType == TextureType_3D) {
header.setTexture3D();
header.setDepth(inputOptions.targetDepth);
}*/
if (compressionOptions.format == Format_RGBA)
{
header.setPitch(4 * inputOptions.targetWidth);
header.setPixelFormat(compressionOptions.bitcount, compressionOptions.rmask, compressionOptions.gmask, compressionOptions.bmask, compressionOptions.amask);
}
else
{
header.setLinearSize(computeImageSize(inputOptions.targetWidth, inputOptions.targetHeight, compressionOptions.bitcount, compressionOptions.format));
if (compressionOptions.format == Format_DXT1 || compressionOptions.format == Format_DXT1a) {
header.setFourCC('D', 'X', 'T', '1');
if (inputOptions.isNormalMap) header.setNormalFlag(true);
}
else if (compressionOptions.format == Format_DXT3) {
header.setFourCC('D', 'X', 'T', '3');
}
else if (compressionOptions.format == Format_DXT5) {
header.setFourCC('D', 'X', 'T', '5');
}
else if (compressionOptions.format == Format_DXT5n) {
header.setFourCC('D', 'X', 'T', '5');
if (inputOptions.isNormalMap) header.setNormalFlag(true);
}
else if (compressionOptions.format == Format_BC4) {
header.setFourCC('A', 'T', 'I', '1');
}
else if (compressionOptions.format == Format_BC5) {
header.setFourCC('A', 'T', 'I', '2');
if (inputOptions.isNormalMap) header.setNormalFlag(true);
}
}
// Swap bytes if necessary.
header.swapBytes();
uint headerSize = 128;
if (header.hasDX10Header())
{
nvStaticCheck(sizeof(DDSHeader) == 128 + 20);
headerSize = 128 + 20;
}
bool writeSucceed = outputOptions.outputHandler->writeData(&header, headerSize);
if (!writeSucceed && outputOptions.errorHandler != NULL)
{
outputOptions.errorHandler->error(Error_FileWrite);
}
return writeSucceed;
}
static bool compressMipmap(const Image * image, const OutputOptions::Private & outputOptions, const CompressionOptions::Private & compressionOptions)
{
nvDebugCheck(image != NULL);
if (compressionOptions.format == Format_RGBA || compressionOptions.format == Format_RGB)
{
compressRGB(image, outputOptions, compressionOptions);
}
else if (compressionOptions.format == Format_DXT1)
{
#if defined(HAVE_S3QUANT)
if (compressionOptions.externalCompressor == "s3")
{
s3CompressDXT1(image, outputOptions);
}
else
#endif
#if defined(HAVE_ATITC)
if (compressionOptions.externalCompressor == "ati")
{
atiCompressDXT1(image, outputOptions);
}
else
#endif
if (compressionOptions.quality == Quality_Fastest)
{
fastCompressDXT1(image, outputOptions);
}
else
{
if (compressionOptions.useCuda && nv::cuda::isHardwarePresent())
{
cudaCompressDXT1(image, outputOptions, compressionOptions);
}
else
{
compressDXT1(image, outputOptions, compressionOptions);
}
}
}
else if (compressionOptions.format == Format_DXT1a)
{
if (compressionOptions.quality == Quality_Fastest)
{
fastCompressDXT1a(image, outputOptions);
}
else
{
if (compressionOptions.useCuda && nv::cuda::isHardwarePresent())
{
/*cuda*/compressDXT1a(image, outputOptions, compressionOptions);
}
else
{
compressDXT1a(image, outputOptions, compressionOptions);
}
}
}
else if (compressionOptions.format == Format_DXT3)
{
if (compressionOptions.quality == Quality_Fastest)
{
fastCompressDXT3(image, outputOptions);
}
else
{
if (compressionOptions.useCuda && nv::cuda::isHardwarePresent())
{
cudaCompressDXT3(image, outputOptions, compressionOptions);
}
else
{
compressDXT3(image, outputOptions, compressionOptions);
}
}
}
else if (compressionOptions.format == Format_DXT5)
{
if (compressionOptions.quality == Quality_Fastest)
{
fastCompressDXT5(image, outputOptions);
}
else
{
if (compressionOptions.useCuda && nv::cuda::isHardwarePresent())
{
cudaCompressDXT5(image, outputOptions, compressionOptions);
}
else
{
compressDXT5(image, outputOptions, compressionOptions);
}
}
}
else if (compressionOptions.format == Format_DXT5n)
{
if (compressionOptions.quality == Quality_Fastest)
{
fastCompressDXT5n(image, outputOptions);
}
else
{
compressDXT5n(image, outputOptions, compressionOptions);
}
}
else if (compressionOptions.format == Format_BC4)
{
compressBC4(image, outputOptions, compressionOptions);
}
else if (compressionOptions.format == Format_BC5)
{
compressBC5(image, outputOptions, compressionOptions);
}
return true;
}
// Convert input image to linear float image.
static FloatImage * toFloatImage(const Image * image, const InputOptions::Private & inputOptions)
{
nvDebugCheck(image != NULL);
FloatImage * floatImage = new FloatImage(image);
if (inputOptions.isNormalMap)
{
// Expand normals. to [-1, 1] range.
// floatImage->expandNormals(0);
}
else if (inputOptions.inputGamma != 1.0f)
{
// Convert to linear space.
floatImage->toLinear(0, 3, inputOptions.inputGamma);
}
return floatImage;
}
// Convert linear float image to output image.
static Image * toFixedImage(const FloatImage * floatImage, const InputOptions::Private & inputOptions)
{
nvDebugCheck(floatImage != NULL);
if (inputOptions.isNormalMap || inputOptions.outputGamma == 1.0f)
{
return floatImage->createImage();
}
else
{
return floatImage->createImageGammaCorrect(inputOptions.outputGamma);
}
}
// Create mipmap from the given image.
static FloatImage * createMipmap(const FloatImage * floatImage, const InputOptions::Private & inputOptions)
{
FloatImage * result = NULL;
if (inputOptions.mipmapFilter == MipmapFilter_Box)
{
// Use fast downsample.
result = floatImage->fastDownSample();
}
else if (inputOptions.mipmapFilter == MipmapFilter_Triangle)
{
TriangleFilter filter;
result = floatImage->downSample(filter, (FloatImage::WrapMode)inputOptions.wrapMode);
}
else /*if (inputOptions.mipmapFilter == MipmapFilter_Kaiser)*/
{
nvDebugCheck(inputOptions.mipmapFilter == MipmapFilter_Kaiser);
KaiserFilter filter(inputOptions.kaiserWidth);
filter.setParameters(inputOptions.kaiserAlpha, inputOptions.kaiserStretch);
result = floatImage->downSample(filter, (FloatImage::WrapMode)inputOptions.wrapMode);
}
// Normalize mipmap.
if ((inputOptions.isNormalMap || inputOptions.convertToNormalMap) && inputOptions.normalizeMipmaps)
{
normalizeNormalMap(result);
}
return result;
}
// Quantize the input image to the precision of the output format.
static void quantize(Image * img, const CompressionOptions::Private & compressionOptions)
{
if (compressionOptions.enableColorDithering)
{
if (compressionOptions.format >= Format_DXT1 && compressionOptions.format <= Format_DXT5)
{
Quantize::FloydSteinberg_RGB16(img);
}
}
if (compressionOptions.binaryAlpha)
{
if (compressionOptions.enableAlphaDithering)
{
Quantize::FloydSteinberg_BinaryAlpha(img, compressionOptions.alphaThreshold);
}
else
{
Quantize::BinaryAlpha(img, compressionOptions.alphaThreshold);
}
}
else
{
if (compressionOptions.enableAlphaDithering)
{
if (compressionOptions.format == Format_DXT3)
{
Quantize::Alpha4(img);
}
else if (compressionOptions.format == Format_DXT1a)
{
Quantize::BinaryAlpha(img, compressionOptions.alphaThreshold);
}
}
}
}
// Process the input, convert to normal map, normalize, or convert to linear space.
static FloatImage * processInput(const InputOptions::Private & inputOptions, int idx)
{
const InputOptions::Private::InputImage & mipmap = inputOptions.images[idx];
if (inputOptions.convertToNormalMap)
{
// Scale height factor by 1 / 2 ^ m // @@ Compute scale factor exactly...
Vector4 heightScale = inputOptions.heightFactors / float(1 << idx);
return createNormalMap(mipmap.data.ptr(), (FloatImage::WrapMode)inputOptions.wrapMode, heightScale, inputOptions.bumpFrequencyScale);
}
else if (inputOptions.isNormalMap)
{
if (inputOptions.normalizeMipmaps)
{
FloatImage * img = new FloatImage(mipmap.data.ptr());
normalizeNormalMap(img);
return img;
}
}
else
{
if (inputOptions.inputGamma != inputOptions.outputGamma)
{
FloatImage * img = new FloatImage(mipmap.data.ptr());
img->toLinear(0, 3, inputOptions.inputGamma);
return img;
}
}
return NULL;
}
struct ImagePair
{
ImagePair() : m_floatImage(NULL), m_fixedImage(NULL), m_deleteFixedImage(false) {}
~ImagePair()
{
if (m_deleteFixedImage) {
delete m_fixedImage;
}
}
void setFloatImage(FloatImage * image)
{
m_floatImage = image;
if (m_deleteFixedImage) delete m_fixedImage;
m_fixedImage = NULL;
}
void setFixedImage(Image * image, bool deleteImage)
{
m_floatImage = NULL;
if (m_deleteFixedImage) delete m_fixedImage;
m_fixedImage = image;
m_deleteFixedImage = deleteImage;
}
FloatImage * floatImage() const { return m_floatImage.ptr(); }
Image * fixedImage() const { return m_fixedImage; }
void toFixed(const InputOptions::Private & inputOptions)
{
if (m_floatImage != NULL)
{
// Convert to fixed.
m_fixedImage = toFixedImage(m_floatImage.ptr(), inputOptions);
}
}
private:
AutoPtr<FloatImage> m_floatImage;
Image * m_fixedImage;
bool m_deleteFixedImage;
};
// Find the first mipmap provided that is greater or equal to the target image size.
static int findMipmap(const InputOptions::Private & inputOptions, uint f, int firstMipmap, uint w, uint h, uint d)
{
nvDebugCheck(firstMipmap >= 0);
int bestIdx = -1;
for (int m = firstMipmap; m < int(inputOptions.mipmapCount); m++)
{
int idx = f * inputOptions.mipmapCount + m;
const InputOptions::Private::InputImage & mipmap = inputOptions.images[idx];
if (mipmap.width >= int(w) && mipmap.height >= int(h) && mipmap.depth >= int(d))
{
if (mipmap.data != NULL)
{
bestIdx = idx;
}
}
else
{
// Do not look further down.
break;
}
}
return bestIdx;
}
static int findImage(const InputOptions::Private & inputOptions, uint f, uint w, uint h, uint d, int inputImageIdx, ImagePair * pair)
{
nvDebugCheck(w > 0 && h > 0);
nvDebugCheck(inputImageIdx >= 0 && inputImageIdx < int(inputOptions.mipmapCount));
nvDebugCheck(pair != NULL);
int bestIdx = findMipmap(inputOptions, f, inputImageIdx, w, h, d);
const InputOptions::Private::InputImage & mipmap = inputOptions.images[bestIdx];
if (mipmap.width == w && mipmap.height == h && mipmap.depth == d)
{
// Generate from input image.
AutoPtr<FloatImage> processedImage( processInput(inputOptions, bestIdx) );
if (processedImage != NULL)
{
pair->setFloatImage(processedImage.release());
}
else
{
pair->setFixedImage(mipmap.data.ptr(), false);
}
return bestIdx;
}
else
{
if (pair->floatImage() == NULL && pair->fixedImage() == NULL)
{
// Generate from input image and resize.
AutoPtr<FloatImage> processedImage( processInput(inputOptions, bestIdx) );
if (processedImage == NULL)
{
processedImage = new FloatImage(mipmap.data.ptr());
}
// Resize image. @@ Add more filters. @@ Distinguish between downscaling and reconstruction filters.
BoxFilter boxFilter;
pair->setFloatImage(processedImage->downSample(boxFilter, w, h, (FloatImage::WrapMode)inputOptions.wrapMode));
}
else
{
// Generate from previous mipmap.
if (pair->floatImage() == NULL)
{
nvDebugCheck(pair->fixedImage() != NULL);
pair->setFloatImage(toFloatImage(pair->fixedImage(), inputOptions));
}
// Create mipmap.
pair->setFloatImage(createMipmap(pair->floatImage(), inputOptions));
}
}
return bestIdx; // @@ ???
}
static bool compressMipmaps(uint f, const InputOptions::Private & inputOptions, const OutputOptions::Private & outputOptions, const CompressionOptions::Private & compressionOptions)
{
uint w = inputOptions.targetWidth;
uint h = inputOptions.targetHeight;
uint d = inputOptions.targetDepth;
int inputImageIdx = findMipmap(inputOptions, f, 0, w, h, d);
if (inputImageIdx == -1)
{
// First mipmap missing.
if (outputOptions.errorHandler != NULL) outputOptions.errorHandler->error(Error_InvalidInput);
return false;
}
ImagePair pair;
const uint mipmapCount = inputOptions.realMipmapCount();
nvDebugCheck(mipmapCount > 0);
for (uint m = 0; m < mipmapCount; m++)
{
if (outputOptions.outputHandler)
{
int size = computeImageSize(w, h, compressionOptions.bitcount, compressionOptions.format);
outputOptions.outputHandler->mipmap(size, w, h, d, f, m);
}
inputImageIdx = findImage(inputOptions, f, w, h, d, inputImageIdx, &pair);
// @@ Where to do the color transform?
// - Color transform may not be linear, so we cannot do before computing mipmaps.
// - Should be done in linear space, that is, after gamma correction.
pair.toFixed(inputOptions);
// @@ Quantization should be done in compressMipmap! @@ It should not modify the input image!!!
quantize(pair.fixedImage(), compressionOptions);
compressMipmap(pair.fixedImage(), outputOptions, compressionOptions);
// Compute extents of next mipmap:
w = max(1U, w / 2);
h = max(1U, h / 2);
d = max(1U, d / 2);
}
return true;
}
static bool compress(const InputOptions::Private & inputOptions, const OutputOptions::Private & outputOptions, const CompressionOptions::Private & compressionOptions)
{
// Make sure enums match.
nvStaticCheck(FloatImage::WrapMode_Clamp == (FloatImage::WrapMode)WrapMode_Clamp);
nvStaticCheck(FloatImage::WrapMode_Mirror == (FloatImage::WrapMode)WrapMode_Mirror);
nvStaticCheck(FloatImage::WrapMode_Repeat == (FloatImage::WrapMode)WrapMode_Repeat);
// Get output handler.
if (!outputOptions.openFile())
{
if (outputOptions.errorHandler) outputOptions.errorHandler->error(Error_FileOpen);
return false;
}
inputOptions.computeTargetExtents();
// Output DDS header.
if (!outputHeader(inputOptions, outputOptions, compressionOptions))
{
return false;
}
for (uint f = 0; f < inputOptions.faceCount; f++)
{
if (!compressMipmaps(f, inputOptions, outputOptions, compressionOptions))
{
return false;
}
}
outputOptions.closeFile();
return true;
}
/// Compress the input texture with the given compression options.
bool Compressor::process(const InputOptions & inputOptions, const CompressionOptions & compressionOptions, const OutputOptions & outputOptions) const
{
// @@ Hack!
compressionOptions.m.useCuda = this->m.cudaEnabled;
return ::compress(inputOptions.m, outputOptions.m, compressionOptions.m);
}
/// Return a string for the given error.
const char * nvtt::errorString(Error e)
{

Loading…
Cancel
Save