Initial support for the ktx container. Currently, it only supports BC formats and has no support for key-value pairs. Updated the compress utility with a ktx option and exposed sRGB setting.
This commit is contained in:
parent
8659d3fb4a
commit
8ac7fdb3f2
|
@ -10,7 +10,7 @@ SET(IMAGE_SRCS
|
||||||
FloatImage.h FloatImage.cpp
|
FloatImage.h FloatImage.cpp
|
||||||
Image.h Image.cpp
|
Image.h Image.cpp
|
||||||
ImageIO.h ImageIO.cpp
|
ImageIO.h ImageIO.cpp
|
||||||
#KtxFile.h KtxFile.cpp
|
KtxFile.h KtxFile.cpp
|
||||||
NormalMap.h NormalMap.cpp
|
NormalMap.h NormalMap.cpp
|
||||||
PixelFormat.h
|
PixelFormat.h
|
||||||
PsdFile.h
|
PsdFile.h
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// This code is in the public domain -- Ignacio Castaño <castano@gmail.com>
|
// This code is in the public domain -- Ignacio Castaño <castano@gmail.com>
|
||||||
|
|
||||||
#include "KtxFile.h"
|
#include "KtxFile.h"
|
||||||
|
#include "nvcore/StdStream.h"
|
||||||
|
|
||||||
using namespace nv;
|
using namespace nv;
|
||||||
|
|
||||||
|
@ -10,7 +11,8 @@ static const uint8 fileIdentifier[12] = {
|
||||||
0x0D, 0x0A, 0x1A, 0x0A
|
0x0D, 0x0A, 0x1A, 0x0A
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace nv
|
||||||
|
{
|
||||||
KtxHeader::KtxHeader() {
|
KtxHeader::KtxHeader() {
|
||||||
memcpy(identifier, fileIdentifier, 12);
|
memcpy(identifier, fileIdentifier, 12);
|
||||||
|
|
||||||
|
@ -19,8 +21,8 @@ KtxHeader::KtxHeader() {
|
||||||
glType = 0;
|
glType = 0;
|
||||||
glTypeSize = 1;
|
glTypeSize = 1;
|
||||||
glFormat = 0;
|
glFormat = 0;
|
||||||
glInternalFormat = KTX_RGBA;
|
glInternalFormat = KTX_INTERNAL_COMPRESSED_SRGB_S3TC_DXT1;
|
||||||
glBaseInternalFormat = KTX_RGBA;
|
glBaseInternalFormat = KTX_BASE_INTERNAL_RGB;
|
||||||
pixelWidth = 0;
|
pixelWidth = 0;
|
||||||
pixelHeight = 0;
|
pixelHeight = 0;
|
||||||
pixelDepth = 0;
|
pixelDepth = 0;
|
||||||
|
@ -31,9 +33,9 @@ KtxHeader::KtxHeader() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Stream & operator<< (Stream & s, DDSHeader & header) {
|
Stream & operator<< (Stream & s, KtxHeader & header) {
|
||||||
s.serialize(header.identifier, 12);
|
s.serialize(header.identifier, 12);
|
||||||
s << header.endiannes << header.glType << header.glTypeSize << header.glFormat << header.glInternalFormat << header.glBaseInternalFormat;
|
s << header.endianness << header.glType << header.glTypeSize << header.glFormat << header.glInternalFormat << header.glBaseInternalFormat;
|
||||||
s << header.pixelWidth << header.pixelHeight << header.pixelDepth;
|
s << header.pixelWidth << header.pixelHeight << header.pixelDepth;
|
||||||
s << header.numberOfArrayElements << header.numberOfFaces << header.numberOfMipmapLevels;
|
s << header.numberOfArrayElements << header.numberOfFaces << header.numberOfMipmapLevels;
|
||||||
s << header.bytesOfKeyValueData;
|
s << header.bytesOfKeyValueData;
|
||||||
|
@ -41,7 +43,7 @@ Stream & operator<< (Stream & s, DDSHeader & header) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
KtxFile::KtxFile() {
|
/*KtxFile::KtxFile() {
|
||||||
}
|
}
|
||||||
KtxFile::~KtxFile() {
|
KtxFile::~KtxFile() {
|
||||||
}
|
}
|
||||||
|
@ -49,7 +51,7 @@ KtxFile::~KtxFile() {
|
||||||
void KtxFile::addKeyValue(const char * key, const char * value) {
|
void KtxFile::addKeyValue(const char * key, const char * value) {
|
||||||
keyArray.append(key);
|
keyArray.append(key);
|
||||||
valueArray.append(value);
|
valueArray.append(value);
|
||||||
bytesOfKeyValueData += strlen(key) + 1 + strlen(value) + 1;
|
header.bytesOfKeyValueData += strlen(key) + 1 + strlen(value) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,7 +79,7 @@ Stream & operator<< (Stream & s, KtxFile & file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "nvimage.h"
|
#include "nvimage.h"
|
||||||
#include "nvcore/StrLib.h"
|
#include "nvcore/StrLib.h"
|
||||||
|
#include "nvcore/Array.h"
|
||||||
|
|
||||||
// KTX File format specification:
|
// KTX File format specification:
|
||||||
// http://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/#key
|
// http://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/#key
|
||||||
|
@ -14,22 +15,78 @@ namespace nv
|
||||||
{
|
{
|
||||||
class Stream;
|
class Stream;
|
||||||
|
|
||||||
// GL types (Table 3.2)
|
// GL types
|
||||||
const uint KTX_UNSIGNED_BYTE;
|
const uint KTX_UNSIGNED_BYTE = 0x1401;
|
||||||
const uint KTX_UNSIGNED_SHORT_5_6_5;
|
const uint KTX_BYTE = 0x1400;
|
||||||
// ...
|
const uint KTX_UNSIGNED_SHORT = 0x1403;
|
||||||
|
const uint KTX_SHORT = 0x1402;
|
||||||
|
const uint KTX_UNSIGNED_INT = 0x1405;
|
||||||
|
const uint KTX_INT = 0x1404;
|
||||||
|
const uint KTX_FLOAT = 0x1406;
|
||||||
|
const uint KTX_UNSIGNED_BYTE_3_3_2 = 0x8032;
|
||||||
|
const uint KTX_UNSIGNED_BYTE_2_3_3_REV = 0x8362;
|
||||||
|
const uint KTX_UNSIGNED_SHORT_5_6_5 = 0x8363;
|
||||||
|
const uint KTX_UNSIGNED_SHORT_5_6_5_REV = 0x8364;
|
||||||
|
const uint KTX_UNSIGNED_SHORT_4_4_4_4 = 0x8033;
|
||||||
|
const uint KTX_UNSIGNED_SHORT_4_4_4_4_REV = 0x8365;
|
||||||
|
const uint KTX_UNSIGNED_SHORT_5_5_5_1 = 0x8034;
|
||||||
|
const uint KTX_UNSIGNED_SHORT_1_5_5_5_REV = 0x8366;
|
||||||
|
const uint KTX_UNSIGNED_INT_8_8_8_8 = 0x8035;
|
||||||
|
const uint KTX_UNSIGNED_INT_8_8_8_8_REV = 0x8367;
|
||||||
|
const uint KTX_UNSIGNED_INT_10_10_10_2 = 0x8036;
|
||||||
|
const uint KTX_UNSIGNED_INT_2_10_10_10_REV = 0x8368;
|
||||||
|
|
||||||
// GL formats (Table 3.3)
|
// GL formats
|
||||||
// ...
|
const uint KTX_FORMAT_RED = 0x1903;
|
||||||
|
const uint KTX_FORMAT_RG = 0x8227;
|
||||||
|
const uint KTX_FORMAT_RGB = 0x1907;
|
||||||
|
const uint KTX_FORMAT_BGR = 0x80E0;
|
||||||
|
const uint KTX_FORMAT_RGBA = 0x1908;
|
||||||
|
const uint KTX_FORMAT_BGRA = 0x80E1;
|
||||||
|
const uint KTX_FORMAT_RED_INTEGER = 0x8D94;
|
||||||
|
const uint KTX_FORMAT_RG_INTEGER = 0x8228;
|
||||||
|
const uint KTX_FORMAT_RGB_INTEGER = 0x8D98;
|
||||||
|
const uint KTX_FORMAT_BGR_INTEGER = 0x8D9A;
|
||||||
|
const uint KTX_FORMAT_RGBA_INTEGER = 0x8D99;
|
||||||
|
const uint KTX_FORMAT_BGRA_INTEGER = 0x8D9B;
|
||||||
|
const uint KTX_FORMAT_STENCIL_INDEX = 0x1901;
|
||||||
|
const uint KTX_FORMAT_DEPTH_COMPONENT = 0x1902;
|
||||||
|
const uint KTX_FORMAT_DEPTH_STENCIL = 0x84F9;
|
||||||
|
|
||||||
// GL internal formats (Table 3.12, 3.13)
|
// GL internal formats
|
||||||
// ...
|
// BC1
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_RGB_S3TC_DXT1 = 0x83F0;
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_SRGB_S3TC_DXT1 = 0x8C4C;
|
||||||
|
// BC1a
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_RGBA_S3TC_DXT1 = 0x83F1;
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1 = 0x8C4D;
|
||||||
|
// BC2
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_RGBA_S3TC_DXT3 = 0x83F2;
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3 = 0x8C4E;
|
||||||
|
// BC3
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_RGBA_S3TC_DXT5 = 0x83F3;
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5 = 0x8C4F;
|
||||||
|
// BC4
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_RED_RGTC1 = 0x8DBB;
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_SIGNED_RED_RGTC1 = 0x8DBC;
|
||||||
|
// BC5
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_RG_RGTC2 = 0x8DBD;
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_SIGNED_RG_RGTC2 = 0x8DBE;
|
||||||
|
// BC6
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT = 0x8E8F;
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT = 0x8E8E;
|
||||||
|
// BC7
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C;
|
||||||
|
const uint KTX_INTERNAL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM = 0x8E8D;
|
||||||
|
|
||||||
// GL base internal format. (Table 3.11)
|
// GL base internal formats
|
||||||
const uint KTX_RGB;
|
const uint KTX_BASE_INTERNAL_DEPTH_COMPONENT = 0x1902;
|
||||||
const uint KTX_RGBA;
|
const uint KTX_BASE_INTERNAL_DEPTH_STENCIL = 0x84F9;
|
||||||
const uint KTX_ALPHA;
|
const uint KTX_BASE_INTERNAL_RED = 0x1903;
|
||||||
// ...
|
const uint KTX_BASE_INTERNAL_RG = 0x8227;
|
||||||
|
const uint KTX_BASE_INTERNAL_RGB = 0x1907;
|
||||||
|
const uint KTX_BASE_INTERNAL_RGBA = 0x1908;
|
||||||
|
const uint KTX_BASE_INTERNAL_STENCIL_INDEX = 0x1901;
|
||||||
|
|
||||||
|
|
||||||
struct KtxHeader {
|
struct KtxHeader {
|
||||||
|
@ -52,10 +109,10 @@ namespace nv
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
NVIMAGE_API Stream & operator<< (Stream & s, DDSHeader & header);
|
NVIMAGE_API Stream & operator<< (Stream & s, KtxHeader & header);
|
||||||
|
|
||||||
|
|
||||||
struct KtxFile {
|
/* struct KtxFile {
|
||||||
KtxFile();
|
KtxFile();
|
||||||
~KtxFile();
|
~KtxFile();
|
||||||
|
|
||||||
|
@ -66,10 +123,9 @@ namespace nv
|
||||||
|
|
||||||
Array<String> keyArray;
|
Array<String> keyArray;
|
||||||
Array<String> valueArray;
|
Array<String> valueArray;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
NVIMAGE_API Stream & operator<< (Stream & s, KtxFile & file);
|
NVIMAGE_API Stream & operator<< (Stream & s, KtxFile & file);*/
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
#include "cuda/CudaCompressorDXT.h"
|
#include "cuda/CudaCompressorDXT.h"
|
||||||
|
|
||||||
#include "nvimage/DirectDrawSurface.h"
|
#include "nvimage/DirectDrawSurface.h"
|
||||||
|
#include "nvimage/KtxFile.h"
|
||||||
#include "nvimage/ColorBlock.h"
|
#include "nvimage/ColorBlock.h"
|
||||||
#include "nvimage/BlockDXT.h"
|
#include "nvimage/BlockDXT.h"
|
||||||
#include "nvimage/Image.h"
|
#include "nvimage/Image.h"
|
||||||
|
@ -51,6 +52,7 @@
|
||||||
|
|
||||||
#include "nvcore/Memory.h"
|
#include "nvcore/Memory.h"
|
||||||
#include "nvcore/Ptr.h"
|
#include "nvcore/Ptr.h"
|
||||||
|
#include "nvcore/Array.inl"
|
||||||
|
|
||||||
using namespace nv;
|
using namespace nv;
|
||||||
using namespace nvtt;
|
using namespace nvtt;
|
||||||
|
@ -222,11 +224,6 @@ bool Compressor::Private::compress(const InputOptions::Private & inputOptions, c
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
nvtt::Surface img;
|
|
||||||
img.setWrapMode(inputOptions.wrapMode);
|
|
||||||
img.setAlphaMode(inputOptions.alphaMode);
|
|
||||||
img.setNormalMap(inputOptions.isNormalMap);
|
|
||||||
|
|
||||||
const int faceCount = inputOptions.faceCount;
|
const int faceCount = inputOptions.faceCount;
|
||||||
int width = inputOptions.width;
|
int width = inputOptions.width;
|
||||||
int height = inputOptions.height;
|
int height = inputOptions.height;
|
||||||
|
@ -244,97 +241,246 @@ bool Compressor::Private::compress(const InputOptions::Private & inputOptions, c
|
||||||
if (inputOptions.maxLevel > 0) mipmapCount = min(mipmapCount, inputOptions.maxLevel);
|
if (inputOptions.maxLevel > 0) mipmapCount = min(mipmapCount, inputOptions.maxLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!outputHeader(inputOptions.textureType, width, height, depth, arraySize, mipmapCount, img.isNormalMap(), compressionOptions, outputOptions)) {
|
if (!outputHeader(inputOptions.textureType, width, height, depth, arraySize, mipmapCount, inputOptions.isNormalMap, compressionOptions, outputOptions)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Output images.
|
// Output images.
|
||||||
for (int f = 0; f < faceCount; f++)
|
if (outputOptions.container != Container_KTX)
|
||||||
{
|
{
|
||||||
int w = width;
|
nvtt::Surface img;
|
||||||
int h = height;
|
img.setWrapMode(inputOptions.wrapMode);
|
||||||
int d = depth;
|
img.setAlphaMode(inputOptions.alphaMode);
|
||||||
bool canUseSourceImagesForThisFace = canUseSourceImages;
|
img.setNormalMap(inputOptions.isNormalMap);
|
||||||
|
|
||||||
img.setImage(inputOptions.inputFormat, inputOptions.width, inputOptions.height, inputOptions.depth, inputOptions.images[f]);
|
for (int f = 0; f < faceCount; f++)
|
||||||
|
{
|
||||||
|
int w = width;
|
||||||
|
int h = height;
|
||||||
|
int d = depth;
|
||||||
|
bool canUseSourceImagesForThisFace = canUseSourceImages;
|
||||||
|
|
||||||
// To normal map.
|
img.setImage(inputOptions.inputFormat, inputOptions.width, inputOptions.height, inputOptions.depth, inputOptions.images[f]);
|
||||||
if (inputOptions.convertToNormalMap) {
|
|
||||||
img.toGreyScale(inputOptions.heightFactors.x, inputOptions.heightFactors.y, inputOptions.heightFactors.z, inputOptions.heightFactors.w);
|
|
||||||
img.toNormalMap(inputOptions.bumpFrequencyScale.x, inputOptions.bumpFrequencyScale.y, inputOptions.bumpFrequencyScale.z, inputOptions.bumpFrequencyScale.w);
|
|
||||||
}
|
|
||||||
|
|
||||||
// To linear space.
|
// To normal map.
|
||||||
if (!img.isNormalMap()) {
|
if (inputOptions.convertToNormalMap) {
|
||||||
img.toLinear(inputOptions.inputGamma);
|
img.toGreyScale(inputOptions.heightFactors.x, inputOptions.heightFactors.y, inputOptions.heightFactors.z, inputOptions.heightFactors.w);
|
||||||
}
|
img.toNormalMap(inputOptions.bumpFrequencyScale.x, inputOptions.bumpFrequencyScale.y, inputOptions.bumpFrequencyScale.z, inputOptions.bumpFrequencyScale.w);
|
||||||
|
|
||||||
// Resize input.
|
|
||||||
img.resize(w, h, d, ResizeFilter_Box);
|
|
||||||
|
|
||||||
nvtt::Surface tmp = img;
|
|
||||||
if (!img.isNormalMap()) {
|
|
||||||
tmp.toGamma(inputOptions.outputGamma);
|
|
||||||
}
|
|
||||||
|
|
||||||
quantize(tmp, compressionOptions);
|
|
||||||
compress(tmp, f, 0, compressionOptions, outputOptions);
|
|
||||||
|
|
||||||
for (int m = 1; m < mipmapCount; m++) {
|
|
||||||
w = max(1, w/2);
|
|
||||||
h = max(1, h/2);
|
|
||||||
d = max(1, d/2);
|
|
||||||
|
|
||||||
int idx = m * faceCount + f;
|
|
||||||
|
|
||||||
bool useSourceImages = false;
|
|
||||||
if (canUseSourceImagesForThisFace) {
|
|
||||||
if (inputOptions.images[idx] == NULL) { // One face is missing in this mipmap level.
|
|
||||||
canUseSourceImagesForThisFace = false; // If one level is missing, ignore the following source images.
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
useSourceImages = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useSourceImages) {
|
// To linear space.
|
||||||
img.setImage(inputOptions.inputFormat, w, h, d, inputOptions.images[idx]);
|
if (!img.isNormalMap()) {
|
||||||
|
img.toLinear(inputOptions.inputGamma);
|
||||||
|
}
|
||||||
|
|
||||||
// For already generated mipmaps, we need to convert to linear.
|
// Resize input.
|
||||||
if (!img.isNormalMap()) {
|
img.resize(w, h, d, ResizeFilter_Box);
|
||||||
img.toLinear(inputOptions.inputGamma);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (inputOptions.mipmapFilter == MipmapFilter_Kaiser) {
|
|
||||||
float params[2] = { inputOptions.kaiserStretch, inputOptions.kaiserAlpha };
|
|
||||||
img.buildNextMipmap(MipmapFilter_Kaiser, inputOptions.kaiserWidth, params);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
img.buildNextMipmap(inputOptions.mipmapFilter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nvDebugCheck(img.width() == w);
|
|
||||||
nvDebugCheck(img.height() == h);
|
|
||||||
nvDebugCheck(img.depth() == d);
|
|
||||||
|
|
||||||
if (img.isNormalMap()) {
|
nvtt::Surface tmp = img;
|
||||||
if (inputOptions.normalizeMipmaps) {
|
if (!img.isNormalMap()) {
|
||||||
img.normalizeNormalMap();
|
|
||||||
}
|
|
||||||
tmp = img;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tmp = img;
|
|
||||||
tmp.toGamma(inputOptions.outputGamma);
|
tmp.toGamma(inputOptions.outputGamma);
|
||||||
}
|
}
|
||||||
|
|
||||||
quantize(tmp, compressionOptions);
|
quantize(tmp, compressionOptions);
|
||||||
compress(tmp, f, m, compressionOptions, outputOptions);
|
compress(tmp, f, 0, compressionOptions, outputOptions);
|
||||||
|
|
||||||
|
for (int m = 1; m < mipmapCount; m++) {
|
||||||
|
w = max(1, w/2);
|
||||||
|
h = max(1, h/2);
|
||||||
|
d = max(1, d/2);
|
||||||
|
|
||||||
|
int idx = m * faceCount + f;
|
||||||
|
|
||||||
|
bool useSourceImages = false;
|
||||||
|
if (canUseSourceImagesForThisFace) {
|
||||||
|
if (inputOptions.images[idx] == NULL) { // One face is missing in this mipmap level.
|
||||||
|
canUseSourceImagesForThisFace = false; // If one level is missing, ignore the following source images.
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
useSourceImages = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useSourceImages) {
|
||||||
|
img.setImage(inputOptions.inputFormat, w, h, d, inputOptions.images[idx]);
|
||||||
|
|
||||||
|
// For already generated mipmaps, we need to convert to linear.
|
||||||
|
if (!img.isNormalMap()) {
|
||||||
|
img.toLinear(inputOptions.inputGamma);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (inputOptions.mipmapFilter == MipmapFilter_Kaiser) {
|
||||||
|
float params[2] = { inputOptions.kaiserStretch, inputOptions.kaiserAlpha };
|
||||||
|
img.buildNextMipmap(MipmapFilter_Kaiser, inputOptions.kaiserWidth, params);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
img.buildNextMipmap(inputOptions.mipmapFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nvDebugCheck(img.width() == w);
|
||||||
|
nvDebugCheck(img.height() == h);
|
||||||
|
nvDebugCheck(img.depth() == d);
|
||||||
|
|
||||||
|
if (img.isNormalMap()) {
|
||||||
|
if (inputOptions.normalizeMipmaps) {
|
||||||
|
img.normalizeNormalMap();
|
||||||
|
}
|
||||||
|
tmp = img;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tmp = img;
|
||||||
|
tmp.toGamma(inputOptions.outputGamma);
|
||||||
|
}
|
||||||
|
|
||||||
|
quantize(tmp, compressionOptions);
|
||||||
|
compress(tmp, f, m, compressionOptions, outputOptions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Array<nvtt::Surface> images(faceCount);
|
||||||
|
Array<bool> mipChainBroken(faceCount);
|
||||||
|
|
||||||
|
int w = width;
|
||||||
|
int h = height;
|
||||||
|
int d = depth;
|
||||||
|
|
||||||
|
uint32 imageSize = 0;
|
||||||
|
|
||||||
|
// https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/#2.16
|
||||||
|
if (faceCount == 6 && arraySize == 1)
|
||||||
|
{
|
||||||
|
imageSize = estimateSize(w, h, 1, 1, compressionOptions) * faceCount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
imageSize = estimateSize(w, h, d, 1, compressionOptions) * faceCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputOptions.writeData(&imageSize, sizeof(uint32));
|
||||||
|
|
||||||
|
for (int f = 0; f < faceCount; f++)
|
||||||
|
{
|
||||||
|
nvtt::Surface s;
|
||||||
|
s.setWrapMode(inputOptions.wrapMode);
|
||||||
|
s.setAlphaMode(inputOptions.alphaMode);
|
||||||
|
s.setNormalMap(inputOptions.isNormalMap);
|
||||||
|
|
||||||
|
s.setImage(inputOptions.inputFormat, inputOptions.width, inputOptions.height, inputOptions.depth, inputOptions.images[f]);
|
||||||
|
|
||||||
|
// To normal map.
|
||||||
|
if (inputOptions.convertToNormalMap) {
|
||||||
|
s.toGreyScale(inputOptions.heightFactors.x, inputOptions.heightFactors.y, inputOptions.heightFactors.z, inputOptions.heightFactors.w);
|
||||||
|
s.toNormalMap(inputOptions.bumpFrequencyScale.x, inputOptions.bumpFrequencyScale.y, inputOptions.bumpFrequencyScale.z, inputOptions.bumpFrequencyScale.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
// To linear space.
|
||||||
|
if (!s.isNormalMap()) {
|
||||||
|
s.toLinear(inputOptions.inputGamma);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize input.
|
||||||
|
s.resize(w, h, d, ResizeFilter_Box);
|
||||||
|
|
||||||
|
nvtt::Surface tmp = s;
|
||||||
|
if (!s.isNormalMap()) {
|
||||||
|
tmp.toGamma(inputOptions.outputGamma);
|
||||||
|
}
|
||||||
|
|
||||||
|
quantize(tmp, compressionOptions);
|
||||||
|
compress(tmp, f, 0, compressionOptions, outputOptions);
|
||||||
|
|
||||||
|
images.push_back(s);
|
||||||
|
mipChainBroken.push_back(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char padding[3] = {0, 0, 0};
|
||||||
|
for (int m = 1; m < mipmapCount; m++)
|
||||||
|
{
|
||||||
|
w = max(1, w/2);
|
||||||
|
h = max(1, h/2);
|
||||||
|
d = max(1, d/2);
|
||||||
|
|
||||||
|
// https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/#2.16
|
||||||
|
if (faceCount == 6 && arraySize == 1)
|
||||||
|
{
|
||||||
|
imageSize = estimateSize(w, h, 1, 1, compressionOptions) * faceCount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
imageSize = estimateSize(w, h, d, 1, compressionOptions) * faceCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputOptions.writeData(&imageSize, sizeof(uint32));
|
||||||
|
|
||||||
|
nvtt::Surface tmp;
|
||||||
|
|
||||||
|
for (int f = 0; f < faceCount; f++)
|
||||||
|
{
|
||||||
|
nvtt::Surface& img = images[f];
|
||||||
|
int idx = m * faceCount + f;
|
||||||
|
|
||||||
|
bool useSourceImages = false;
|
||||||
|
if (!mipChainBroken[f]) {
|
||||||
|
if (inputOptions.images[idx] == NULL) { // One face is missing in this mipmap level.
|
||||||
|
mipChainBroken[f] = false; // If one level is missing, ignore the following source images.
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
useSourceImages = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useSourceImages) {
|
||||||
|
img.setImage(inputOptions.inputFormat, w, h, d, inputOptions.images[idx]);
|
||||||
|
|
||||||
|
// For already generated mipmaps, we need to convert to linear.
|
||||||
|
if (!img.isNormalMap()) {
|
||||||
|
img.toLinear(inputOptions.inputGamma);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (inputOptions.mipmapFilter == MipmapFilter_Kaiser) {
|
||||||
|
float params[2] = { inputOptions.kaiserStretch, inputOptions.kaiserAlpha };
|
||||||
|
img.buildNextMipmap(MipmapFilter_Kaiser, inputOptions.kaiserWidth, params);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
img.buildNextMipmap(inputOptions.mipmapFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nvDebugCheck(img.width() == w);
|
||||||
|
nvDebugCheck(img.height() == h);
|
||||||
|
nvDebugCheck(img.depth() == d);
|
||||||
|
|
||||||
|
if (img.isNormalMap()) {
|
||||||
|
if (inputOptions.normalizeMipmaps) {
|
||||||
|
img.normalizeNormalMap();
|
||||||
|
}
|
||||||
|
tmp = img;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tmp = img;
|
||||||
|
tmp.toGamma(inputOptions.outputGamma);
|
||||||
|
}
|
||||||
|
|
||||||
|
quantize(tmp, compressionOptions);
|
||||||
|
compress(tmp, f, m, compressionOptions, outputOptions);
|
||||||
|
|
||||||
|
//cube padding
|
||||||
|
if (faceCount == 6 && arraySize == 1)
|
||||||
|
{
|
||||||
|
//TODO calc offset for uncompressed images
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int mipPadding = 3 - ((imageSize + 3) % 4);
|
||||||
|
if (mipPadding != 0) {
|
||||||
|
outputOptions.writeData(&padding, mipPadding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -670,6 +816,112 @@ bool Compressor::Private::outputHeader(nvtt::TextureType textureType, int w, int
|
||||||
|
|
||||||
return writeSucceed;
|
return writeSucceed;
|
||||||
}
|
}
|
||||||
|
else if (outputOptions.container == Container_KTX)
|
||||||
|
{
|
||||||
|
KtxHeader header;
|
||||||
|
// TODO cube arrays
|
||||||
|
if (textureType == TextureType_2D) {
|
||||||
|
nvCheck(arraySize == 1);
|
||||||
|
header.numberOfArrayElements = 0;
|
||||||
|
header.numberOfFaces = 1;
|
||||||
|
header.pixelDepth = 0;
|
||||||
|
}
|
||||||
|
else if (textureType == TextureType_Cube) {
|
||||||
|
nvCheck(arraySize == 1);
|
||||||
|
header.numberOfArrayElements = 0;
|
||||||
|
header.numberOfFaces = 6;
|
||||||
|
header.pixelDepth = 0;
|
||||||
|
}
|
||||||
|
else if (textureType == TextureType_3D) {
|
||||||
|
nvCheck(arraySize == 1);
|
||||||
|
header.numberOfArrayElements = 0;
|
||||||
|
header.numberOfFaces = 1;
|
||||||
|
header.pixelDepth = d;
|
||||||
|
}
|
||||||
|
else if (textureType == TextureType_Array) {
|
||||||
|
header.numberOfArrayElements = arraySize;
|
||||||
|
header.numberOfFaces = 1;
|
||||||
|
header.pixelDepth = 0; // Is it?
|
||||||
|
}
|
||||||
|
|
||||||
|
header.pixelWidth = w;
|
||||||
|
header.pixelHeight = h;
|
||||||
|
header.numberOfMipmapLevels = mipmapCount;
|
||||||
|
|
||||||
|
bool supported = true;
|
||||||
|
|
||||||
|
// TODO non-compressed formats
|
||||||
|
if (compressionOptions.format == Format_RGBA)
|
||||||
|
{
|
||||||
|
//header.glType = ?;
|
||||||
|
//header.glTypeSize = ?;
|
||||||
|
//header.glFormat = ?;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
header.glType = 0;
|
||||||
|
header.glTypeSize = 1;
|
||||||
|
header.glFormat = 0;
|
||||||
|
|
||||||
|
if (compressionOptions.format == Format_DXT1) {
|
||||||
|
header.glInternalFormat = outputOptions.srgb ? KTX_INTERNAL_COMPRESSED_SRGB_S3TC_DXT1 : KTX_INTERNAL_COMPRESSED_RGB_S3TC_DXT1;
|
||||||
|
header.glBaseInternalFormat = KTX_BASE_INTERNAL_RGB;
|
||||||
|
}
|
||||||
|
else if (compressionOptions.format == Format_DXT1a) {
|
||||||
|
header.glInternalFormat = outputOptions.srgb ? KTX_INTERNAL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1 : KTX_INTERNAL_COMPRESSED_RGBA_S3TC_DXT1;
|
||||||
|
header.glBaseInternalFormat = KTX_BASE_INTERNAL_RGBA;
|
||||||
|
}
|
||||||
|
else if (compressionOptions.format == Format_DXT3) {
|
||||||
|
header.glInternalFormat = outputOptions.srgb ? KTX_INTERNAL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3 : KTX_INTERNAL_COMPRESSED_RGBA_S3TC_DXT3;
|
||||||
|
header.glBaseInternalFormat = KTX_BASE_INTERNAL_RGBA;
|
||||||
|
}
|
||||||
|
else if (compressionOptions.format == Format_DXT5 || compressionOptions.format == Format_BC3_RGBM) {
|
||||||
|
header.glInternalFormat = outputOptions.srgb ? KTX_INTERNAL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5 : KTX_INTERNAL_COMPRESSED_RGBA_S3TC_DXT5;
|
||||||
|
header.glBaseInternalFormat = KTX_BASE_INTERNAL_RGBA;
|
||||||
|
}
|
||||||
|
else if (compressionOptions.format == Format_BC4) {
|
||||||
|
header.glInternalFormat = KTX_INTERNAL_COMPRESSED_RED_RGTC1; // KTX_INTERNAL_COMPRESSED_SIGNED_RED_RGTC1 ?
|
||||||
|
header.glBaseInternalFormat = KTX_BASE_INTERNAL_RED;
|
||||||
|
}
|
||||||
|
else if (compressionOptions.format == Format_BC5) {
|
||||||
|
header.glInternalFormat = KTX_INTERNAL_COMPRESSED_RG_RGTC2; // KTX_INTERNAL_COMPRESSED_SIGNED_RG_RGTC2 ?
|
||||||
|
header.glBaseInternalFormat = KTX_BASE_INTERNAL_RG;
|
||||||
|
}
|
||||||
|
else if (compressionOptions.format == Format_BC6) {
|
||||||
|
if (compressionOptions.pixelType == PixelType_Float) header.glInternalFormat = KTX_INTERNAL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT;
|
||||||
|
/*if (compressionOptions.pixelType == PixelType_UnsignedFloat)*/ header.glInternalFormat = KTX_INTERNAL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; // By default we assume unsigned.
|
||||||
|
header.glBaseInternalFormat = KTX_BASE_INTERNAL_RGB;
|
||||||
|
}
|
||||||
|
else if (compressionOptions.format == Format_BC7) {
|
||||||
|
header.glInternalFormat = outputOptions.srgb ? KTX_INTERNAL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM : KTX_INTERNAL_COMPRESSED_RGBA_BPTC_UNORM;
|
||||||
|
header.glBaseInternalFormat = KTX_BASE_INTERNAL_RGBA;
|
||||||
|
}
|
||||||
|
else if (compressionOptions.format == Format_CTX1) {
|
||||||
|
supported = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
supported = false;
|
||||||
|
}//TODO compressionOptions.format == Format_DXT1n, Format_DXT5n ? There seems to be no way to indicate a normal map using ktx. Maybe via key value data?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supported)
|
||||||
|
{
|
||||||
|
// This container does not support the requested format.
|
||||||
|
outputOptions.error(Error_UnsupportedOutputFormat);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint headerSize = 64;
|
||||||
|
nvStaticCheck(sizeof(KtxHeader) == 64);
|
||||||
|
|
||||||
|
bool writeSucceed = outputOptions.writeData(&header, headerSize);
|
||||||
|
if (!writeSucceed)
|
||||||
|
{
|
||||||
|
outputOptions.error(Error_FileWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeSucceed;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -857,3 +1109,24 @@ CompressorInterface * Compressor::Private::chooseGpuCompressor(const Compression
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Compressor::Private::estimateSize(int w, int h, int d, int mipmapCount, const CompressionOptions::Private & compressionOptions) const
|
||||||
|
{
|
||||||
|
const Format format = compressionOptions.format;
|
||||||
|
|
||||||
|
const uint bitCount = compressionOptions.bitcount;
|
||||||
|
const uint pitchAlignment = compressionOptions.pitchAlignment;
|
||||||
|
|
||||||
|
int size = 0;
|
||||||
|
for (int m = 0; m < mipmapCount; m++)
|
||||||
|
{
|
||||||
|
size += computeImageSize(w, h, d, bitCount, pitchAlignment, format);
|
||||||
|
|
||||||
|
// Compute extents of next mipmap:
|
||||||
|
w = max(1, w / 2);
|
||||||
|
h = max(1, h / 2);
|
||||||
|
d = max(1, d / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ namespace nvtt
|
||||||
nv::CompressorInterface * chooseCpuCompressor(const CompressionOptions::Private & compressionOptions) const;
|
nv::CompressorInterface * chooseCpuCompressor(const CompressionOptions::Private & compressionOptions) const;
|
||||||
nv::CompressorInterface * chooseGpuCompressor(const CompressionOptions::Private & compressionOptions) const;
|
nv::CompressorInterface * chooseGpuCompressor(const CompressionOptions::Private & compressionOptions) const;
|
||||||
|
|
||||||
|
int estimateSize(int w, int h, int d, int mipmapCount, const CompressionOptions::Private & compressionOptions) const;
|
||||||
|
|
||||||
bool cudaSupported;
|
bool cudaSupported;
|
||||||
bool cudaEnabled;
|
bool cudaEnabled;
|
||||||
|
|
|
@ -345,7 +345,7 @@ namespace nvtt
|
||||||
{
|
{
|
||||||
Container_DDS,
|
Container_DDS,
|
||||||
Container_DDS10,
|
Container_DDS10,
|
||||||
// Container_KTX, // Khronos Texture: http://www.khronos.org/opengles/sdk/tools/KTX/
|
Container_KTX, // Khronos Texture: http://www.khronos.org/opengles/sdk/tools/KTX/
|
||||||
// Container_VTF, // Valve Texture Format: http://developer.valvesoftware.com/wiki/Valve_Texture_Format
|
// Container_VTF, // Valve Texture Format: http://developer.valvesoftware.com/wiki/Valve_Texture_Format
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -154,11 +154,13 @@ int main(int argc, char *argv[])
|
||||||
bool loadAsFloat = false;
|
bool loadAsFloat = false;
|
||||||
bool rgbm = false;
|
bool rgbm = false;
|
||||||
bool rangescale = false;
|
bool rangescale = false;
|
||||||
|
bool srgb = false;
|
||||||
|
|
||||||
const char * externalCompressor = NULL;
|
const char * externalCompressor = NULL;
|
||||||
|
|
||||||
bool silent = false;
|
bool silent = false;
|
||||||
bool dds10 = false;
|
bool dds10 = false;
|
||||||
|
bool ktx = false;
|
||||||
|
|
||||||
nv::Path input;
|
nv::Path input;
|
||||||
nv::Path output;
|
nv::Path output;
|
||||||
|
@ -309,7 +311,15 @@ int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
dds10 = true;
|
dds10 = true;
|
||||||
}
|
}
|
||||||
|
else if (strcmp("-ktx", argv[i]) == 0)
|
||||||
|
{
|
||||||
|
ktx = true;
|
||||||
|
}
|
||||||
|
else if (strcmp("-srgb", argv[i]) == 0)
|
||||||
|
{
|
||||||
|
srgb = true;
|
||||||
|
}
|
||||||
|
|
||||||
else if (argv[i][0] != '-')
|
else if (argv[i][0] != '-')
|
||||||
{
|
{
|
||||||
input = argv[i];
|
input = argv[i];
|
||||||
|
@ -321,7 +331,15 @@ int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
output.copy(input.str());
|
output.copy(input.str());
|
||||||
output.stripExtension();
|
output.stripExtension();
|
||||||
output.append(".dds");
|
|
||||||
|
if (ktx)
|
||||||
|
{
|
||||||
|
output.append(".ktx");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
output.append(".dds");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -380,7 +398,9 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
printf("Output options:\n");
|
printf("Output options:\n");
|
||||||
printf(" -silent \tDo not output progress messages\n");
|
printf(" -silent \tDo not output progress messages\n");
|
||||||
printf(" -dds10 \tUse DirectX 10 DDS format (enabled by default for BC6/7)\n\n");
|
printf(" -dds10 \tUse DirectX 10 DDS format (enabled by default for BC6/7, unless ktx is being used)\n");
|
||||||
|
printf(" -ktx \tUse KTX container format\n");
|
||||||
|
printf(" -srgb \tIf the requested format allows it, output will be in sRGB color space\n\n");
|
||||||
|
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
@ -494,7 +514,7 @@ int main(int argc, char *argv[])
|
||||||
nvDebugCheck(dds.isTextureArray());
|
nvDebugCheck(dds.isTextureArray());
|
||||||
inputOptions.setTextureLayout(nvtt::TextureType_Array, dds.width(), dds.height(), 1, dds.arraySize());
|
inputOptions.setTextureLayout(nvtt::TextureType_Array, dds.width(), dds.height(), 1, dds.arraySize());
|
||||||
faceCount = dds.arraySize();
|
faceCount = dds.arraySize();
|
||||||
dds10 = true;
|
dds10 = ktx ? false : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint mipmapCount = dds.mipmapCount();
|
uint mipmapCount = dds.mipmapCount();
|
||||||
|
@ -721,7 +741,7 @@ int main(int argc, char *argv[])
|
||||||
outputOptions.setErrorHandler(&errorHandler);
|
outputOptions.setErrorHandler(&errorHandler);
|
||||||
|
|
||||||
// Automatically use dds10 if compressing to BC6 or BC7
|
// Automatically use dds10 if compressing to BC6 or BC7
|
||||||
if (format == nvtt::Format_BC6 || format == nvtt::Format_BC7)
|
if (format == nvtt::Format_BC6 || format == nvtt::Format_BC7 && !ktx)
|
||||||
{
|
{
|
||||||
dds10 = true;
|
dds10 = true;
|
||||||
}
|
}
|
||||||
|
@ -730,6 +750,15 @@ int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
outputOptions.setContainer(nvtt::Container_DDS10);
|
outputOptions.setContainer(nvtt::Container_DDS10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ktx)
|
||||||
|
{
|
||||||
|
outputOptions.setContainer(nvtt::Container_KTX);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srgb) {
|
||||||
|
outputOptions.setSrgbFlag(true);
|
||||||
|
}
|
||||||
|
|
||||||
// printf("Press ENTER.\n");
|
// printf("Press ENTER.\n");
|
||||||
// fflush(stdout);
|
// fflush(stdout);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user