|
|
|
@ -38,6 +38,7 @@
|
|
|
|
|
#include "CompressRGB.h"
|
|
|
|
|
#include "InputOptions.h"
|
|
|
|
|
#include "CompressionOptions.h"
|
|
|
|
|
#include "OutputOptions.h"
|
|
|
|
|
#include "cuda/CudaUtils.h"
|
|
|
|
|
#include "cuda/CudaCompressDXT.h"
|
|
|
|
|
|
|
|
|
@ -96,24 +97,21 @@ namespace
|
|
|
|
|
// compress
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
static void outputHeader(const InputOptions::Private & inputOptions, const OutputOptions & outputOptions, const CompressionOptions::Private & compressionOptions)
|
|
|
|
|
static void outputHeader(const InputOptions::Private & inputOptions, const OutputOptions::Private & outputOptions, const CompressionOptions::Private & compressionOptions)
|
|
|
|
|
{
|
|
|
|
|
// Output DDS header.
|
|
|
|
|
if (outputOptions.outputHandler != NULL && outputOptions.outputHeader)
|
|
|
|
|
{
|
|
|
|
|
DDSHeader header;
|
|
|
|
|
|
|
|
|
|
InputOptions::Private::Image * img = inputOptions.images;
|
|
|
|
|
nvCheck(img != NULL);
|
|
|
|
|
header.setWidth(inputOptions.targetWidth);
|
|
|
|
|
header.setHeight(inputOptions.targetHeight);
|
|
|
|
|
|
|
|
|
|
header.setWidth(img->width);
|
|
|
|
|
header.setHeight(img->height);
|
|
|
|
|
int mipmapCount = inputOptions.realMipmapCount();
|
|
|
|
|
nvDebugCheck(mipmapCount > 0);
|
|
|
|
|
|
|
|
|
|
header.setMipmapCount(mipmapCount - 1);
|
|
|
|
|
|
|
|
|
|
int mipmapCount = inputOptions.mipmapCount;
|
|
|
|
|
if (!inputOptions.generateMipmaps) mipmapCount = 0;
|
|
|
|
|
else if (inputOptions.maxLevel != -1 && inputOptions.maxLevel < mipmapCount) mipmapCount = inputOptions.maxLevel;
|
|
|
|
|
header.setMipmapCount(mipmapCount);
|
|
|
|
|
|
|
|
|
|
if (inputOptions.textureType == TextureType_2D) {
|
|
|
|
|
header.setTexture2D();
|
|
|
|
|
}
|
|
|
|
@ -122,17 +120,17 @@ static void outputHeader(const InputOptions::Private & inputOptions, const Outpu
|
|
|
|
|
}
|
|
|
|
|
/*else if (inputOptions.textureType == TextureType_3D) {
|
|
|
|
|
header.setTexture3D();
|
|
|
|
|
header.setDepth(img->depth);
|
|
|
|
|
header.setDepth(inputOptions.targetDepth);
|
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
if (compressionOptions.format == Format_RGBA)
|
|
|
|
|
{
|
|
|
|
|
header.setPitch(4 * img->width);
|
|
|
|
|
header.setPitch(4 * inputOptions.targetWidth);
|
|
|
|
|
header.setPixelFormat(compressionOptions.bitcount, compressionOptions.rmask, compressionOptions.gmask, compressionOptions.bmask, compressionOptions.amask);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
header.setLinearSize(computeImageSize(img->width, img->height, compressionOptions.bitcount, compressionOptions.format));
|
|
|
|
|
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');
|
|
|
|
@ -145,14 +143,14 @@ static void outputHeader(const InputOptions::Private & inputOptions, const Outpu
|
|
|
|
|
}
|
|
|
|
|
else if (compressionOptions.format == Format_DXT5n) {
|
|
|
|
|
header.setFourCC('D', 'X', 'T', '5');
|
|
|
|
|
header.setNormalFlag(true);
|
|
|
|
|
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');
|
|
|
|
|
header.setNormalFlag(true);
|
|
|
|
|
if (inputOptions.isNormalMap) header.setNormalFlag(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -175,7 +173,7 @@ static void outputHeader(const InputOptions::Private & inputOptions, const Outpu
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool compressMipmap(const Image * image, const OutputOptions & outputOptions, const CompressionOptions::Private & compressionOptions)
|
|
|
|
|
static bool compressMipmap(const Image * image, const OutputOptions::Private & outputOptions, const CompressionOptions::Private & compressionOptions)
|
|
|
|
|
{
|
|
|
|
|
nvDebugCheck(image != NULL);
|
|
|
|
|
|
|
|
|
@ -196,7 +194,6 @@ static bool compressMipmap(const Image * image, const OutputOptions & outputOpti
|
|
|
|
|
#if defined(HAVE_ATITC)
|
|
|
|
|
if (compressionOptions.externalCompressor == "ati")
|
|
|
|
|
{
|
|
|
|
|
printf("ATI\n");
|
|
|
|
|
atiCompressDXT1(image, outputOptions);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
@ -289,7 +286,7 @@ static FloatImage * toFloatImage(const Image * image, const InputOptions::Privat
|
|
|
|
|
|
|
|
|
|
FloatImage * floatImage = new FloatImage(image);
|
|
|
|
|
|
|
|
|
|
if (inputOptions.normalMap)
|
|
|
|
|
if (inputOptions.isNormalMap)
|
|
|
|
|
{
|
|
|
|
|
// Expand normals. to [-1, 1] range.
|
|
|
|
|
// floatImage->expandNormals(0);
|
|
|
|
@ -309,7 +306,14 @@ static Image * toFixedImage(const FloatImage * floatImage, const InputOptions::P
|
|
|
|
|
{
|
|
|
|
|
nvDebugCheck(floatImage != NULL);
|
|
|
|
|
|
|
|
|
|
return floatImage->createImageGammaCorrect(inputOptions.outputGamma);
|
|
|
|
|
if (inputOptions.isNormalMap || inputOptions.outputGamma == 1.0f)
|
|
|
|
|
{
|
|
|
|
|
return floatImage->createImage();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return floatImage->createImageGammaCorrect(inputOptions.outputGamma);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -331,13 +335,13 @@ static FloatImage * createMipmap(const FloatImage * floatImage, const InputOptio
|
|
|
|
|
else /*if (inputOptions.mipmapFilter == MipmapFilter_Kaiser)*/
|
|
|
|
|
{
|
|
|
|
|
nvDebugCheck(inputOptions.mipmapFilter == MipmapFilter_Kaiser);
|
|
|
|
|
KaiserFilter filter(float(inputOptions.kaiserWidth));
|
|
|
|
|
KaiserFilter filter(inputOptions.kaiserWidth);
|
|
|
|
|
filter.setParameters(inputOptions.kaiserAlpha, inputOptions.kaiserStretch);
|
|
|
|
|
result = floatImage->downSample(filter, (FloatImage::WrapMode)inputOptions.wrapMode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Normalize mipmap.
|
|
|
|
|
if (inputOptions.normalizeMipmaps)
|
|
|
|
|
if ((inputOptions.isNormalMap || inputOptions.convertToNormalMap) && inputOptions.normalizeMipmaps)
|
|
|
|
|
{
|
|
|
|
|
normalizeNormalMap(result);
|
|
|
|
|
}
|
|
|
|
@ -383,50 +387,297 @@ static void quantize(Image * img, const InputOptions::Private & inputOptions, Fo
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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::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);
|
|
|
|
|
}
|
|
|
|
|
else if (inputOptions.isNormalMap)
|
|
|
|
|
{
|
|
|
|
|
if (inputOptions.normalizeMipmaps)
|
|
|
|
|
{
|
|
|
|
|
FloatImage * img = new FloatImage(mipmap.data.ptr());
|
|
|
|
|
img->normalize(0);
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Compress the input texture with the given compression options.
|
|
|
|
|
bool nvtt::compress(const InputOptions & inputOptions, const OutputOptions & outputOptions, const CompressionOptions & compressionOptions)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
int bestIdx = -1;
|
|
|
|
|
|
|
|
|
|
for (int m = firstMipmap; m < inputOptions.mipmapCount; m++)
|
|
|
|
|
{
|
|
|
|
|
int idx = f * inputOptions.mipmapCount + m;
|
|
|
|
|
const InputOptions::Private::Image & mipmap = inputOptions.images[idx];
|
|
|
|
|
|
|
|
|
|
if (mipmap.width >= w && mipmap.height >= h && mipmap.depth >= 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 < 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)
|
|
|
|
|
{
|
|
|
|
|
// 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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
for (uint m = 0; m < inputOptions.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(), inputOptions, compressionOptions.format);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
// @@ Should return here?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inputOptions.computeTargetExtents();
|
|
|
|
|
|
|
|
|
|
uint mipmapCount = inputOptions.realMipmapCount();
|
|
|
|
|
nvDebugCheck(mipmapCount > 0);
|
|
|
|
|
|
|
|
|
|
// Output DDS header.
|
|
|
|
|
outputHeader(inputOptions.m, outputOptions, compressionOptions.m);
|
|
|
|
|
outputHeader(inputOptions, outputOptions, compressionOptions);
|
|
|
|
|
|
|
|
|
|
Format format = compressionOptions.m.format;
|
|
|
|
|
const uint bitCount = compressionOptions.m.bitcount;
|
|
|
|
|
|
|
|
|
|
for (int f = 0; f < inputOptions.m.faceCount; f++)
|
|
|
|
|
for (uint f = 0; f < inputOptions.faceCount; f++)
|
|
|
|
|
{
|
|
|
|
|
if (!compressMipmaps(f, inputOptions, outputOptions, compressionOptions))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Image * lastImage = NULL;
|
|
|
|
|
AutoPtr<FloatImage> floatImage(NULL);
|
|
|
|
|
|
|
|
|
|
for (int m = 0; m < inputOptions.m.mipmapCount; m++)
|
|
|
|
|
uint w = inputOptions.targetWidth;
|
|
|
|
|
uint h = inputOptions.targetHeight;
|
|
|
|
|
uint d = inputOptions.targetDepth;
|
|
|
|
|
|
|
|
|
|
for (uint m = 0; m < mipmapCount; m++)
|
|
|
|
|
{
|
|
|
|
|
int idx = f * inputOptions.m.mipmapCount + m;
|
|
|
|
|
InputOptions::Private::Image & mipmap = inputOptions.m.images[idx];
|
|
|
|
|
|
|
|
|
|
if (outputOptions.outputHandler)
|
|
|
|
|
{
|
|
|
|
|
int size = computeImageSize(mipmap.width, mipmap.height, bitCount, format);
|
|
|
|
|
outputOptions.outputHandler->mipmap(size, mipmap.width, mipmap.height, mipmap.depth, mipmap.face, mipmap.mipLevel);
|
|
|
|
|
int size = computeImageSize(w, h, bitCount, format);
|
|
|
|
|
outputOptions.outputHandler->mipmap(size, w, h, d, f, m);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Image * img; // Image to compress.
|
|
|
|
|
// @@ Write a more sofisticated get input image, that:
|
|
|
|
|
// - looks for the nearest image in the input mipmap chain, resizes it to desired extents.
|
|
|
|
|
// - uses previous floating point image, if available.
|
|
|
|
|
// - uses previous byte image if available.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int idx = f * inputOptions.mipmapCount + m;
|
|
|
|
|
InputOptions::Private::Image & mipmap = inputOptions.images[idx];
|
|
|
|
|
|
|
|
|
|
// @@ Prescale not implemented yet.
|
|
|
|
|
nvCheck(w == mipmap.width);
|
|
|
|
|
nvCheck(h == mipmap.height);
|
|
|
|
|
nvCheck(d == mipmap.depth);
|
|
|
|
|
|
|
|
|
|
Image * img = NULL; // Image to compress.
|
|
|
|
|
|
|
|
|
|
if (mipmap.data != NULL) // Mipmap provided.
|
|
|
|
|
{
|
|
|
|
|
// Convert to normal map.
|
|
|
|
|
if (inputOptions.m.convertToNormalMap)
|
|
|
|
|
if (inputOptions.convertToNormalMap)
|
|
|
|
|
{
|
|
|
|
|
floatImage = createNormalMap(mipmap.data.ptr(), (FloatImage::WrapMode)inputOptions.m.wrapMode, inputOptions.m.heightFactors, inputOptions.m.bumpFrequencyScale);
|
|
|
|
|
// Scale height factor by 1 / 2 ^ m
|
|
|
|
|
Vector4 heightScale = inputOptions.heightFactors / float(1 << m);
|
|
|
|
|
floatImage = createNormalMap(mipmap.data.ptr(), (FloatImage::WrapMode)inputOptions.wrapMode, heightScale, inputOptions.bumpFrequencyScale);
|
|
|
|
|
}
|
|
|
|
|
/*else if (inputOptions.m.convertToConeMap)
|
|
|
|
|
{
|
|
|
|
|
floatImage = createConeMap(mipmap.data, inputOptions.m.heightFactors);
|
|
|
|
|
}*/
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
lastImage = img = mipmap.data.ptr();
|
|
|
|
@ -446,70 +697,88 @@ bool nvtt::compress(const InputOptions & inputOptions, const OutputOptions & out
|
|
|
|
|
if (floatImage == NULL)
|
|
|
|
|
{
|
|
|
|
|
nvDebugCheck(lastImage != NULL);
|
|
|
|
|
floatImage = toFloatImage(lastImage, inputOptions.m);
|
|
|
|
|
floatImage = toFloatImage(lastImage, inputOptions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create mipmap.
|
|
|
|
|
floatImage = createMipmap(floatImage.ptr(), inputOptions.m);
|
|
|
|
|
floatImage = createMipmap(floatImage.ptr(), inputOptions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (floatImage != NULL)
|
|
|
|
|
{
|
|
|
|
|
// Convert to fixed.
|
|
|
|
|
img = toFixedImage(floatImage.ptr(), inputOptions.m);
|
|
|
|
|
img = toFixedImage(floatImage.ptr(), inputOptions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @@ 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.
|
|
|
|
|
|
|
|
|
|
// @@ Error! gamma correction is not performed when mipmap data provied.
|
|
|
|
|
// @@ Error! gamma correction is not performed when mipmap data provided. (only if inputGamma != outputGamma)
|
|
|
|
|
|
|
|
|
|
// @@ This code is too complicated, too prone to erros, and hard to understand. Must be simplified!
|
|
|
|
|
|
|
|
|
|
quantize(img, inputOptions.m, format);
|
|
|
|
|
|
|
|
|
|
compressMipmap(img, outputOptions, compressionOptions.m);
|
|
|
|
|
|
|
|
|
|
// @@ Quantization should be done in compressMipmap!
|
|
|
|
|
quantize(img, inputOptions, format);
|
|
|
|
|
|
|
|
|
|
compressMipmap(img, outputOptions, compressionOptions);
|
|
|
|
|
|
|
|
|
|
if (img != mipmap.data)
|
|
|
|
|
{
|
|
|
|
|
delete img;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!inputOptions.m.generateMipmaps || (inputOptions.m.maxLevel >= 0 && m >= inputOptions.m.maxLevel)) {
|
|
|
|
|
// continue with next face.
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// Compute extents of next mipmap:
|
|
|
|
|
w = max(1U, w / 2);
|
|
|
|
|
h = max(1U, h / 2);
|
|
|
|
|
d = max(1U, d / 2);
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
outputOptions.closeFile();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Compress the input texture with the given compression options.
|
|
|
|
|
bool nvtt::compress(const InputOptions & inputOptions, const OutputOptions & outputOptions, const CompressionOptions & compressionOptions)
|
|
|
|
|
{
|
|
|
|
|
// @@ Hack this is necessary because of the pimpl transition.
|
|
|
|
|
initOptions(const_cast<OutputOptions *>(&outputOptions));
|
|
|
|
|
|
|
|
|
|
return ::compress(inputOptions.m, outputOptions.m, compressionOptions.m);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Estimate the size of compressing the input with the given options.
|
|
|
|
|
int nvtt::estimateSize(const InputOptions & inputOptions, const CompressionOptions & compressionOptions)
|
|
|
|
|
{
|
|
|
|
|
Format format = compressionOptions.m.format;
|
|
|
|
|
const Format format = compressionOptions.m.format;
|
|
|
|
|
const uint bitCount = compressionOptions.m.bitcount;
|
|
|
|
|
|
|
|
|
|
inputOptions.m.computeTargetExtents();
|
|
|
|
|
|
|
|
|
|
uint mipmapCount = inputOptions.m.realMipmapCount();
|
|
|
|
|
|
|
|
|
|
int size = 0;
|
|
|
|
|
|
|
|
|
|
for (int f = 0; f < inputOptions.m.faceCount; f++)
|
|
|
|
|
for (uint f = 0; f < inputOptions.m.faceCount; f++)
|
|
|
|
|
{
|
|
|
|
|
for (int m = 0; m < inputOptions.m.mipmapCount; m++)
|
|
|
|
|
uint w = inputOptions.m.targetWidth;
|
|
|
|
|
uint h = inputOptions.m.targetHeight;
|
|
|
|
|
uint d = inputOptions.m.targetDepth;
|
|
|
|
|
|
|
|
|
|
for (uint m = 0; m < mipmapCount; m++)
|
|
|
|
|
{
|
|
|
|
|
int idx = f * inputOptions.m.mipmapCount + m;
|
|
|
|
|
const InputOptions::Private::Image & img = inputOptions.m.images[idx];
|
|
|
|
|
|
|
|
|
|
size += computeImageSize(img.width, img.height, bitCount, format);
|
|
|
|
|
size += computeImageSize(w, h, bitCount, format);
|
|
|
|
|
|
|
|
|
|
if (!inputOptions.m.generateMipmaps || (inputOptions.m.maxLevel >= 0 && m >= inputOptions.m.maxLevel)) {
|
|
|
|
|
// continue with next face.
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// Compute extents of next mipmap:
|
|
|
|
|
w = max(1U, w / 2);
|
|
|
|
|
h = max(1U, h / 2);
|
|
|
|
|
d = max(1U, d / 2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -530,9 +799,15 @@ const char * nvtt::errorString(Error e)
|
|
|
|
|
return "Unsupported feature";
|
|
|
|
|
case Error_CudaError:
|
|
|
|
|
return "CUDA error";
|
|
|
|
|
case Error_FileOpen:
|
|
|
|
|
return "Error opening file";
|
|
|
|
|
case Error_FileWrite:
|
|
|
|
|
return "Error writing through output handler";
|
|
|
|
|
case Error_Unknown:
|
|
|
|
|
default:
|
|
|
|
|
//default:
|
|
|
|
|
return "Unknown error";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "Invalid error";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|