From 8ac7fdb3f2e55674c3331c7b9b0eb7e4da680998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manvydas=20=C5=A0liamka?= Date: Wed, 17 Aug 2016 00:40:29 +0300 Subject: [PATCH] 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. --- src/nvimage/CMakeLists.txt | 2 +- src/nvimage/KtxFile.cpp | 18 +- src/nvimage/KtxFile.h | 96 +++++++-- src/nvtt/Context.cpp | 397 ++++++++++++++++++++++++++++++------ src/nvtt/Context.h | 1 + src/nvtt/nvtt.h | 2 +- src/nvtt/tools/compress.cpp | 39 +++- 7 files changed, 458 insertions(+), 97 deletions(-) diff --git a/src/nvimage/CMakeLists.txt b/src/nvimage/CMakeLists.txt index dce627d..080902e 100644 --- a/src/nvimage/CMakeLists.txt +++ b/src/nvimage/CMakeLists.txt @@ -10,7 +10,7 @@ SET(IMAGE_SRCS FloatImage.h FloatImage.cpp Image.h Image.cpp ImageIO.h ImageIO.cpp - #KtxFile.h KtxFile.cpp + KtxFile.h KtxFile.cpp NormalMap.h NormalMap.cpp PixelFormat.h PsdFile.h diff --git a/src/nvimage/KtxFile.cpp b/src/nvimage/KtxFile.cpp index de075bd..810496d 100644 --- a/src/nvimage/KtxFile.cpp +++ b/src/nvimage/KtxFile.cpp @@ -1,6 +1,7 @@ // This code is in the public domain -- Ignacio Castaņo #include "KtxFile.h" +#include "nvcore/StdStream.h" using namespace nv; @@ -10,7 +11,8 @@ static const uint8 fileIdentifier[12] = { 0x0D, 0x0A, 0x1A, 0x0A }; - +namespace nv +{ KtxHeader::KtxHeader() { memcpy(identifier, fileIdentifier, 12); @@ -19,8 +21,8 @@ KtxHeader::KtxHeader() { glType = 0; glTypeSize = 1; glFormat = 0; - glInternalFormat = KTX_RGBA; - glBaseInternalFormat = KTX_RGBA; + glInternalFormat = KTX_INTERNAL_COMPRESSED_SRGB_S3TC_DXT1; + glBaseInternalFormat = KTX_BASE_INTERNAL_RGB; pixelWidth = 0; pixelHeight = 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 << 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.numberOfArrayElements << header.numberOfFaces << header.numberOfMipmapLevels; s << header.bytesOfKeyValueData; @@ -41,7 +43,7 @@ Stream & operator<< (Stream & s, DDSHeader & header) { } -KtxFile::KtxFile() { +/*KtxFile::KtxFile() { } KtxFile::~KtxFile() { } @@ -49,7 +51,7 @@ KtxFile::~KtxFile() { void KtxFile::addKeyValue(const char * key, const char * value) { keyArray.append(key); 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; +}*/ } - diff --git a/src/nvimage/KtxFile.h b/src/nvimage/KtxFile.h index 9f89590..f18e050 100644 --- a/src/nvimage/KtxFile.h +++ b/src/nvimage/KtxFile.h @@ -6,6 +6,7 @@ #include "nvimage.h" #include "nvcore/StrLib.h" +#include "nvcore/Array.h" // KTX File format specification: // http://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/#key @@ -14,22 +15,78 @@ namespace nv { class Stream; - // GL types (Table 3.2) - const uint KTX_UNSIGNED_BYTE; - const uint KTX_UNSIGNED_SHORT_5_6_5; - // ... - - // GL formats (Table 3.3) - // ... - - // GL internal formats (Table 3.12, 3.13) - // ... - - // GL base internal format. (Table 3.11) - const uint KTX_RGB; - const uint KTX_RGBA; - const uint KTX_ALPHA; - // ... + // GL types + const uint KTX_UNSIGNED_BYTE = 0x1401; + 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 + 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 + // 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 formats + const uint KTX_BASE_INTERNAL_DEPTH_COMPONENT = 0x1902; + const uint KTX_BASE_INTERNAL_DEPTH_STENCIL = 0x84F9; + 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 { @@ -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(); @@ -66,10 +123,9 @@ namespace nv Array keyArray; Array valueArray; - }; - NVIMAGE_API Stream & operator<< (Stream & s, KtxFile & file); + NVIMAGE_API Stream & operator<< (Stream & s, KtxFile & file);*/ /* diff --git a/src/nvtt/Context.cpp b/src/nvtt/Context.cpp index 82759c2..33ab353 100644 --- a/src/nvtt/Context.cpp +++ b/src/nvtt/Context.cpp @@ -39,6 +39,7 @@ #include "cuda/CudaCompressorDXT.h" #include "nvimage/DirectDrawSurface.h" +#include "nvimage/KtxFile.h" #include "nvimage/ColorBlock.h" #include "nvimage/BlockDXT.h" #include "nvimage/Image.h" @@ -51,6 +52,7 @@ #include "nvcore/Memory.h" #include "nvcore/Ptr.h" +#include "nvcore/Array.inl" using namespace nv; using namespace nvtt; @@ -222,11 +224,6 @@ bool Compressor::Private::compress(const InputOptions::Private & inputOptions, c return false; } - nvtt::Surface img; - img.setWrapMode(inputOptions.wrapMode); - img.setAlphaMode(inputOptions.alphaMode); - img.setNormalMap(inputOptions.isNormalMap); - const int faceCount = inputOptions.faceCount; int width = inputOptions.width; 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 (!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; } - // Output images. - for (int f = 0; f < faceCount; f++) + if (outputOptions.container != Container_KTX) { + nvtt::Surface img; + img.setWrapMode(inputOptions.wrapMode); + img.setAlphaMode(inputOptions.alphaMode); + img.setNormalMap(inputOptions.isNormalMap); + + for (int f = 0; f < faceCount; f++) + { + int w = width; + int h = height; + int d = depth; + bool canUseSourceImagesForThisFace = canUseSourceImages; + + img.setImage(inputOptions.inputFormat, inputOptions.width, inputOptions.height, inputOptions.depth, inputOptions.images[f]); + + // To normal map. + 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. + if (!img.isNormalMap()) { + img.toLinear(inputOptions.inputGamma); + } + + // 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) { + 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 images(faceCount); + Array mipChainBroken(faceCount); + int w = width; int h = height; int d = depth; - bool canUseSourceImagesForThisFace = canUseSourceImages; - img.setImage(inputOptions.inputFormat, inputOptions.width, inputOptions.height, inputOptions.depth, inputOptions.images[f]); + uint32 imageSize = 0; - // To normal map. - 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); + // 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; } - - // To linear space. - if (!img.isNormalMap()) { - img.toLinear(inputOptions.inputGamma); + else + { + imageSize = estimateSize(w, h, d, 1, compressionOptions) * faceCount; } - // Resize input. - img.resize(w, h, d, ResizeFilter_Box); + 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); + } - nvtt::Surface tmp = img; - if (!img.isNormalMap()) { - tmp.toGamma(inputOptions.outputGamma); - } + // To linear space. + if (!s.isNormalMap()) { + s.toLinear(inputOptions.inputGamma); + } - quantize(tmp, compressionOptions); - compress(tmp, f, 0, compressionOptions, outputOptions); + // Resize input. + s.resize(w, h, d, ResizeFilter_Box); + + nvtt::Surface tmp = s; + if (!s.isNormalMap()) { + tmp.toGamma(inputOptions.outputGamma); + } - for (int m = 1; m < mipmapCount; m++) { + 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); - int idx = m * faceCount + f; + // 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; + } - 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; + 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]); + 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); + // 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 { + 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 { - img.buildNextMipmap(inputOptions.mipmapFilter); + tmp = img; + tmp.toGamma(inputOptions.outputGamma); } - } - nvDebugCheck(img.width() == w); - nvDebugCheck(img.height() == h); - nvDebugCheck(img.depth() == d); - if (img.isNormalMap()) { - if (inputOptions.normalizeMipmaps) { - img.normalizeNormalMap(); + quantize(tmp, compressionOptions); + compress(tmp, f, m, compressionOptions, outputOptions); + + //cube padding + if (faceCount == 6 && arraySize == 1) + { + //TODO calc offset for uncompressed images } - tmp = img; } - else { - tmp = img; - tmp.toGamma(inputOptions.outputGamma); + + int mipPadding = 3 - ((imageSize + 3) % 4); + if (mipPadding != 0) { + outputOptions.writeData(&padding, mipPadding); } - - quantize(tmp, compressionOptions); - compress(tmp, f, m, compressionOptions, outputOptions); } } - + return true; } @@ -670,6 +816,112 @@ bool Compressor::Private::outputHeader(nvtt::TextureType textureType, int w, int 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; } @@ -857,3 +1109,24 @@ CompressorInterface * Compressor::Private::chooseGpuCompressor(const Compression 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; +} diff --git a/src/nvtt/Context.h b/src/nvtt/Context.h index c497bb1..de801c0 100644 --- a/src/nvtt/Context.h +++ b/src/nvtt/Context.h @@ -56,6 +56,7 @@ namespace nvtt nv::CompressorInterface * chooseCpuCompressor(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 cudaEnabled; diff --git a/src/nvtt/nvtt.h b/src/nvtt/nvtt.h index f2b27ce..d83befb 100644 --- a/src/nvtt/nvtt.h +++ b/src/nvtt/nvtt.h @@ -345,7 +345,7 @@ namespace nvtt { Container_DDS, 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 }; diff --git a/src/nvtt/tools/compress.cpp b/src/nvtt/tools/compress.cpp index 412ba5a..7b816e9 100644 --- a/src/nvtt/tools/compress.cpp +++ b/src/nvtt/tools/compress.cpp @@ -154,11 +154,13 @@ int main(int argc, char *argv[]) bool loadAsFloat = false; bool rgbm = false; bool rangescale = false; + bool srgb = false; const char * externalCompressor = NULL; bool silent = false; bool dds10 = false; + bool ktx = false; nv::Path input; nv::Path output; @@ -309,7 +311,15 @@ int main(int argc, char *argv[]) { 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] != '-') { input = argv[i]; @@ -321,7 +331,15 @@ int main(int argc, char *argv[]) { output.copy(input.str()); output.stripExtension(); - output.append(".dds"); + + if (ktx) + { + output.append(".ktx"); + } + else + { + output.append(".dds"); + } } break; @@ -380,7 +398,9 @@ int main(int argc, char *argv[]) printf("Output options:\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; } @@ -494,7 +514,7 @@ int main(int argc, char *argv[]) nvDebugCheck(dds.isTextureArray()); inputOptions.setTextureLayout(nvtt::TextureType_Array, dds.width(), dds.height(), 1, dds.arraySize()); faceCount = dds.arraySize(); - dds10 = true; + dds10 = ktx ? false : true; } uint mipmapCount = dds.mipmapCount(); @@ -721,7 +741,7 @@ int main(int argc, char *argv[]) outputOptions.setErrorHandler(&errorHandler); // 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; } @@ -730,6 +750,15 @@ int main(int argc, char *argv[]) { outputOptions.setContainer(nvtt::Container_DDS10); } + + if (ktx) + { + outputOptions.setContainer(nvtt::Container_KTX); + } + + if (srgb) { + outputOptions.setSrgbFlag(true); + } // printf("Press ENTER.\n"); // fflush(stdout);