#include <inttypes.h>
#include <stdio.h>
#include <KHR/khr_df.h>
#include "dfdutils/dfd.h"
#include "ktx.h"
#include "ktxint.h"
#include "texture2.h"
#include "vkformat_enum.h"
#include "vk_format.h"
#include "basis_sgd.h"
#include "basisu/transcoder/basisu_file_headers.h"
#include "basisu/transcoder/basisu_transcoder.h"
#include "basisu/transcoder/basisu_transcoder_internal.h"
#undef DECLARE_PRIVATE
#undef DECLARE_PROTECTED
#define DECLARE_PRIVATE(n,t2) ktxTexture2_private& n = *(t2->_private)
#define DECLARE_PROTECTED(n,t2) ktxTexture_protected& n = *(t2->_protected)
using namespace basisu;
using namespace basist;
inline bool isPow2(uint32_t x) { return x && ((x & (x - 1U)) == 0U); }
inline bool isPow2(uint64_t x) { return x && ((x & (x - 1U)) == 0U); }
KTX_error_code
ktxTexture2_transcodeLzEtc1s(ktxTexture2* This,
alpha_content_e alphaContent,
ktxTexture2* prototype,
ktx_transcode_fmt_e outputFormat,
ktx_transcode_flags transcodeFlags);
KTX_error_code
ktxTexture2_transcodeUastc(ktxTexture2* This,
alpha_content_e alphaContent,
ktxTexture2* prototype,
ktx_transcode_fmt_e outputFormat,
ktx_transcode_flags transcodeFlags);
KTX_error_code
ktxTexture2_TranscodeBasis(ktxTexture2* This,
ktx_transcode_fmt_e outputFormat,
ktx_transcode_flags transcodeFlags)
{
uint32_t* BDB = This->pDfd + 1;
khr_df_model_e colorModel = (khr_df_model_e)KHR_DFDVAL(BDB, MODEL);
if (colorModel != KHR_DF_MODEL_UASTC
&& This->supercompressionScheme != KTX_SS_BASIS_LZ)
return KTX_INVALID_OPERATION;
DECLARE_PRIVATE(priv, This);
if (This->supercompressionScheme == KTX_SS_BASIS_LZ) {
if (!priv._supercompressionGlobalData || priv._sgdByteLength == 0)
return KTX_INVALID_OPERATION;
}
if (transcodeFlags & KTX_TF_PVRTC_DECODE_TO_NEXT_POW2) {
debug_printf("ktxTexture_TranscodeBasis: KTX_TF_PVRTC_DECODE_TO_NEXT_POW2 currently unsupported\n");
return KTX_UNSUPPORTED_FEATURE;
}
if (outputFormat == KTX_TTF_PVRTC1_4_RGB
|| outputFormat == KTX_TTF_PVRTC1_4_RGBA) {
if ((!isPow2(This->baseWidth)) || (!isPow2(This->baseHeight))) {
debug_printf("ktxTexture_TranscodeBasis: PVRTC1 only supports power of 2 dimensions\n");
return KTX_INVALID_OPERATION;
}
}
const bool srgb = (KHR_DFDVAL(BDB, TRANSFER) == KHR_DF_TRANSFER_SRGB);
alpha_content_e alphaContent = eNone;
if (colorModel == KHR_DF_MODEL_ETC1S) {
if (KHR_DFDSAMPLECOUNT(BDB) == 2) {
uint32_t channelId = KHR_DFDSVAL(BDB, 1, CHANNELID);
if (channelId == KHR_DF_CHANNEL_ETC1S_AAA) {
alphaContent = eAlpha;
} else if (channelId == KHR_DF_CHANNEL_ETC1S_GGG){
alphaContent = eGreen;
} else {
return KTX_FILE_DATA_ERROR;
}
}
} else {
assert(colorModel == KHR_DF_MODEL_UASTC);
uint32_t channelId = KHR_DFDSVAL(BDB, 0, CHANNELID);
if (channelId == KHR_DF_CHANNEL_UASTC_RGBA)
alphaContent = eAlpha;
else if (channelId == KHR_DF_CHANNEL_UASTC_RRRG)
alphaContent = eGreen;
}
VkFormat vkFormat;
switch (outputFormat) {
case KTX_TTF_BC1_OR_3:
outputFormat = alphaContent != eNone ? KTX_TTF_BC3_RGBA
: KTX_TTF_BC1_RGB;
break;
case KTX_TTF_ETC:
outputFormat = alphaContent != eNone ? KTX_TTF_ETC2_RGBA
: KTX_TTF_ETC1_RGB;
break;
case KTX_TTF_PVRTC1_4_RGBA:
outputFormat = alphaContent != eNone ? KTX_TTF_PVRTC1_4_RGBA
: KTX_TTF_PVRTC1_4_RGB;
break;
case KTX_TTF_PVRTC2_4_RGBA:
outputFormat = alphaContent != eNone ? KTX_TTF_PVRTC2_4_RGBA
: KTX_TTF_PVRTC2_4_RGB;
break;
default:
;
}
switch (outputFormat) {
case KTX_TTF_ETC1_RGB:
vkFormat = srgb ? VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK
: VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
break;
case KTX_TTF_ETC2_RGBA:
vkFormat = srgb ? VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK
: VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;
break;
case KTX_TTF_ETC2_EAC_R11:
vkFormat = VK_FORMAT_EAC_R11_UNORM_BLOCK;
break;
case KTX_TTF_ETC2_EAC_RG11:
vkFormat = VK_FORMAT_EAC_R11G11_UNORM_BLOCK;
break;
case KTX_TTF_BC1_RGB:
vkFormat = srgb ? VK_FORMAT_BC1_RGB_SRGB_BLOCK
: VK_FORMAT_BC1_RGB_UNORM_BLOCK;
break;
case KTX_TTF_BC3_RGBA:
vkFormat = srgb ? VK_FORMAT_BC3_SRGB_BLOCK
: VK_FORMAT_BC3_UNORM_BLOCK;
break;
case KTX_TTF_BC4_R:
vkFormat = VK_FORMAT_BC4_UNORM_BLOCK;
break;
case KTX_TTF_BC5_RG:
vkFormat = VK_FORMAT_BC5_UNORM_BLOCK;
break;
case KTX_TTF_PVRTC1_4_RGB:
case KTX_TTF_PVRTC1_4_RGBA:
vkFormat = srgb ? VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG
: VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG;
break;
case KTX_TTF_PVRTC2_4_RGB:
case KTX_TTF_PVRTC2_4_RGBA:
vkFormat = srgb ? VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG
: VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG;
break;
case KTX_TTF_BC7_RGBA:
vkFormat = srgb ? VK_FORMAT_BC7_SRGB_BLOCK
: VK_FORMAT_BC7_UNORM_BLOCK;
break;
case KTX_TTF_ASTC_4x4_RGBA:
vkFormat = srgb ? VK_FORMAT_ASTC_4x4_SRGB_BLOCK
: VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
break;
case KTX_TTF_RGB565:
vkFormat = VK_FORMAT_R5G6B5_UNORM_PACK16;
break;
case KTX_TTF_BGR565:
vkFormat = VK_FORMAT_B5G6R5_UNORM_PACK16;
break;
case KTX_TTF_RGBA4444:
vkFormat = VK_FORMAT_R4G4B4A4_UNORM_PACK16;
break;
case KTX_TTF_RGBA32:
vkFormat = srgb ? VK_FORMAT_R8G8B8A8_SRGB
: VK_FORMAT_R8G8B8A8_UNORM;
break;
default:
return KTX_INVALID_VALUE;
}
basis_tex_format textureFormat;
if (colorModel == KHR_DF_MODEL_UASTC)
textureFormat = basis_tex_format::cUASTC4x4;
else
textureFormat = basis_tex_format::cETC1S;
if (!basis_is_format_supported((transcoder_texture_format)outputFormat,
textureFormat)) {
return KTX_UNSUPPORTED_FEATURE;
}
ktxTextureCreateInfo createInfo;
createInfo.glInternalformat = 0;
createInfo.vkFormat = vkFormat;
createInfo.baseWidth = This->baseWidth;
createInfo.baseHeight = This->baseHeight;
createInfo.baseDepth = This->baseDepth;
createInfo.generateMipmaps = This->generateMipmaps;
createInfo.isArray = This->isArray;
createInfo.numDimensions = This->numDimensions;
createInfo.numFaces = This->numFaces;
createInfo.numLayers = This->numLayers;
createInfo.numLevels = This->numLevels;
createInfo.pDfd = nullptr;
KTX_error_code result;
ktxTexture2* prototype;
result = ktxTexture2_Create(&createInfo, KTX_TEXTURE_CREATE_ALLOC_STORAGE,
&prototype);
if (result != KTX_SUCCESS) {
assert(result == KTX_OUT_OF_MEMORY); return result;
}
if (!This->pData) {
if (ktxTexture_isActiveStream((ktxTexture*)This)) {
result = ktxTexture2_LoadImageData(This, NULL, 0);
if (result != KTX_SUCCESS)
return result;
} else {
return KTX_INVALID_OPERATION;
}
}
static bool transcoderInitialized;
if (!transcoderInitialized) {
basisu_transcoder_init();
transcoderInitialized = true;
}
if (textureFormat == basis_tex_format::cETC1S) {
result = ktxTexture2_transcodeLzEtc1s(This, alphaContent,
prototype, outputFormat,
transcodeFlags);
} else {
result = ktxTexture2_transcodeUastc(This, alphaContent,
prototype, outputFormat,
transcodeFlags);
}
if (result == KTX_SUCCESS) {
DECLARE_PROTECTED(thisPrtctd, This);
DECLARE_PRIVATE(protoPriv, prototype);
DECLARE_PROTECTED(protoPrtctd, prototype);
memcpy(&thisPrtctd._formatSize, &protoPrtctd._formatSize,
sizeof(ktxFormatSize));
This->vkFormat = vkFormat;
This->isCompressed = prototype->isCompressed;
This->supercompressionScheme = KTX_SS_NONE;
priv._requiredLevelAlignment = protoPriv._requiredLevelAlignment;
memcpy(priv._levelIndex, protoPriv._levelIndex,
This->numLevels * sizeof(ktxLevelIndexEntry));
free(This->pDfd);
This->pDfd = prototype->pDfd;
prototype->pDfd = 0;
free(This->pData);
This->pData = prototype->pData;
This->dataSize = prototype->dataSize;
prototype->pData = 0;
prototype->dataSize = 0;
}
ktxTexture2_Destroy(prototype);
return result;
}
static basist::etc1_global_selector_codebook
global_codebook(g_global_selector_cb_size, g_global_selector_cb);
KTX_error_code
ktxTexture2_transcodeLzEtc1s(ktxTexture2* This,
alpha_content_e alphaContent,
ktxTexture2* prototype,
ktx_transcode_fmt_e outputFormat,
ktx_transcode_flags transcodeFlags)
{
DECLARE_PRIVATE(priv, This);
DECLARE_PRIVATE(protoPriv, prototype);
KTX_error_code result = KTX_SUCCESS;
assert(This->supercompressionScheme == KTX_SS_BASIS_LZ);
uint8_t* bgd = priv._supercompressionGlobalData;
ktxBasisLzGlobalHeader& bgdh = *reinterpret_cast<ktxBasisLzGlobalHeader*>(bgd);
if (!(bgdh.endpointsByteLength && bgdh.selectorsByteLength && bgdh.tablesByteLength)) {
debug_printf("ktxTexture_TranscodeBasis: missing endpoints, selectors or tables");
return KTX_FILE_DATA_ERROR;
}
uint32_t* firstImages = new uint32_t[This->numLevels+1];
uint32_t layersFaces = This->numLayers * This->numFaces;
firstImages[0] = 0;
for (uint32_t level = 1; level <= This->numLevels; level++) {
firstImages[level] = firstImages[level - 1]
+ layersFaces * MAX(This->baseDepth >> (level - 1), 1);
}
uint32_t& imageCount = firstImages[This->numLevels];
if (BGD_TABLES_ADDR(0, bgdh, imageCount) + bgdh.tablesByteLength > priv._sgdByteLength) {
return KTX_FILE_DATA_ERROR;
}
basist::basisu_lowlevel_etc1s_transcoder bit(&global_codebook);
std::vector<basisu_transcoder_state> xcoderStates;
xcoderStates.resize(This->isVideo ? This->numFaces : 1);
bit.decode_palettes(bgdh.endpointCount, BGD_ENDPOINTS_ADDR(bgd, imageCount),
bgdh.endpointsByteLength,
bgdh.selectorCount, BGD_SELECTORS_ADDR(bgd, bgdh, imageCount),
bgdh.selectorsByteLength);
bit.decode_tables(BGD_TABLES_ADDR(bgd, bgdh, imageCount),
bgdh.tablesByteLength);
const bool isVideo = This->isVideo;
ktx_uint8_t* pXcodedData = prototype->pData;
ktx_uint32_t outputBlockByteLength
= prototype->_protected->_formatSize.blockSizeInBits / 8;
ktx_size_t xcodedDataLength
= prototype->dataSize / outputBlockByteLength;
ktxLevelIndexEntry* protoLevelIndex;
uint64_t levelOffsetWrite;
const ktxBasisLzEtc1sImageDesc* imageDescs = BGD_ETC1S_IMAGE_DESCS(bgd);
protoLevelIndex = protoPriv._levelIndex;
levelOffsetWrite = 0;
for (int32_t level = This->numLevels - 1; level >= 0; level--) {
uint64_t levelOffset = ktxTexture2_levelDataOffset(This, level);
uint64_t writeOffset = levelOffsetWrite;
uint64_t writeOffsetBlocks = levelOffsetWrite / outputBlockByteLength;
uint32_t levelWidth = MAX(1, This->baseWidth >> level);
uint32_t levelHeight = MAX(1, This->baseHeight >> level);
const uint32_t bw = 4, bh = 4;
uint32_t levelBlocksX = (levelWidth + (bw - 1)) / bw;
uint32_t levelBlocksY = (levelHeight + (bh - 1)) / bh;
uint32_t depth = MAX(1, This->baseDepth >> level);
uint32_t faceSlices = This->numFaces * depth;
uint32_t numImages = This->numLayers * faceSlices;
uint32_t image = firstImages[level];
uint32_t endImage = image + numImages;
ktx_size_t levelImageSizeOut, levelSizeOut;
uint32_t stateIndex = 0;
levelSizeOut = 0;
levelImageSizeOut = ktxTexture2_GetImageSize(prototype, level);
for (; image < endImage; image++) {
const ktxBasisLzEtc1sImageDesc& imageDesc = imageDescs[image];
basisu_transcoder_state& xcoderState = xcoderStates[stateIndex];
if (++stateIndex == xcoderStates.size())
stateIndex = 0;
if (alphaContent != eNone)
{
if (imageDesc.alphaSliceByteOffset == 0
|| imageDesc.alphaSliceByteLength == 0)
return KTX_FILE_DATA_ERROR;
}
bool status;
status = bit.transcode_image(
(transcoder_texture_format)outputFormat,
pXcodedData + writeOffset,
(uint32_t)(xcodedDataLength - writeOffsetBlocks),
This->pData,
(uint32_t)This->dataSize,
levelBlocksX,
levelBlocksY,
levelWidth,
levelHeight,
level,
(uint32_t)(levelOffset + imageDesc.rgbSliceByteOffset),
imageDesc.rgbSliceByteLength,
(uint32_t)(levelOffset + imageDesc.alphaSliceByteOffset),
imageDesc.alphaSliceByteLength,
transcodeFlags,
alphaContent != eNone,
isVideo,
0, &xcoderState,
0 );
if (!status) {
result = KTX_TRANSCODE_FAILED;
goto cleanup;
}
writeOffset += levelImageSizeOut;
levelSizeOut += levelImageSizeOut;
} protoLevelIndex[level].byteOffset = levelOffsetWrite;
protoLevelIndex[level].byteLength = levelSizeOut;
protoLevelIndex[level].uncompressedByteLength = levelSizeOut;
levelOffsetWrite += levelSizeOut;
assert(levelOffsetWrite == writeOffset);
levelOffsetWrite = _KTX_PADN(protoPriv._requiredLevelAlignment,
levelOffsetWrite);
}
result = KTX_SUCCESS;
cleanup:
delete[] firstImages;
return result;
}
KTX_error_code
ktxTexture2_transcodeUastc(ktxTexture2* This,
alpha_content_e alphaContent,
ktxTexture2* prototype,
ktx_transcode_fmt_e outputFormat,
ktx_transcode_flags transcodeFlags)
{
assert(This->supercompressionScheme != KTX_SS_BASIS_LZ);
ktx_uint8_t* pXcodedData = prototype->pData;
ktx_uint32_t outputBlockByteLength
= prototype->_protected->_formatSize.blockSizeInBits / 8;
ktx_size_t xcodedDataLength
= prototype->dataSize / outputBlockByteLength;
DECLARE_PRIVATE(protoPriv, prototype);
ktxLevelIndexEntry* protoLevelIndex = protoPriv._levelIndex;
ktx_size_t levelOffsetWrite = 0;
basisu_lowlevel_uastc_transcoder uit;
std::vector<basisu_transcoder_state> xcoderStates;
xcoderStates.resize(This->isVideo ? This->numFaces : 1);
for (ktx_int32_t level = This->numLevels - 1; level >= 0; level--)
{
ktx_uint32_t depth;
uint64_t writeOffset = levelOffsetWrite;
uint64_t writeOffsetBlocks = levelOffsetWrite / outputBlockByteLength;
ktx_size_t levelImageSizeIn, levelImageOffsetIn;
ktx_size_t levelImageSizeOut, levelSizeOut;
ktx_uint32_t levelImageCount;
uint32_t levelWidth = MAX(1, This->baseWidth >> level);
uint32_t levelHeight = MAX(1, This->baseHeight >> level);
const uint32_t bw = 4, bh = 4;
uint32_t levelBlocksX = (levelWidth + (bw - 1)) / bw;
uint32_t levelBlocksY = (levelHeight + (bh - 1)) / bh;
uint32_t stateIndex = 0;
depth = MAX(1, This->baseDepth >> level);
levelImageCount = This->numLayers * This->numFaces * depth;
levelImageSizeIn = ktxTexture_calcImageSize(ktxTexture(This), level,
KTX_FORMAT_VERSION_TWO);
levelImageSizeOut = ktxTexture_calcImageSize(ktxTexture(prototype),
level,
KTX_FORMAT_VERSION_TWO);
levelImageOffsetIn = ktxTexture2_levelDataOffset(This, level);
levelSizeOut = 0;
bool status;
for (uint32_t image = 0; image < levelImageCount; image++) {
basisu_transcoder_state& xcoderState = xcoderStates[stateIndex];
if (++stateIndex == xcoderStates.size())
stateIndex = 0;
status = uit.transcode_image(
(transcoder_texture_format)outputFormat,
pXcodedData + writeOffset,
(uint32_t)(xcodedDataLength - writeOffsetBlocks),
This->pData,
(uint32_t)This->dataSize,
levelBlocksX,
levelBlocksY,
levelWidth,
levelHeight,
level,
(uint32_t)levelImageOffsetIn,
(uint32_t)levelImageSizeIn,
transcodeFlags,
alphaContent != eNone,
This->isVideo, 0, &xcoderState, 0, -1, -1 );
if (!status)
return KTX_TRANSCODE_FAILED;
writeOffset += levelImageSizeOut;
levelSizeOut += levelImageSizeOut;
levelImageOffsetIn += levelImageSizeIn;
}
protoLevelIndex[level].byteOffset = levelOffsetWrite;
protoLevelIndex[level].byteLength = levelSizeOut;
protoLevelIndex[level].uncompressedByteLength = levelSizeOut;
levelOffsetWrite += levelSizeOut;
}
levelOffsetWrite = _KTX_PADN(protoPriv._requiredLevelAlignment,
levelOffsetWrite);
return KTX_SUCCESS;
}