diff options
Diffstat (limited to 'thirdparty/libktx/lib/basis_transcode.cpp')
-rw-r--r-- | thirdparty/libktx/lib/basis_transcode.cpp | 733 |
1 files changed, 733 insertions, 0 deletions
diff --git a/thirdparty/libktx/lib/basis_transcode.cpp b/thirdparty/libktx/lib/basis_transcode.cpp new file mode 100644 index 0000000000..8df65bcb68 --- /dev/null +++ b/thirdparty/libktx/lib/basis_transcode.cpp @@ -0,0 +1,733 @@ +/* -*- tab-width: 4; -*- */ +/* vi: set sw=2 ts=4 expandtab: */ + +/* + * Copyright 2019-2020 The Khronos Group Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @internal + * @file basis_transcode.cpp + * @~English + * + * @brief Functions for transcoding Basis Universal BasisLZ/ETC1S and UASTC textures. + * + * Two worlds collide here too. More uglyness! + * + * @author Mark Callow, www.edgewise-consulting.com + */ + +#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 "transcoder/basisu_file_headers.h" +#include "transcoder/basisu_transcoder.h" +#include "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); + +/** + * @memberof ktxTexture2 + * @ingroup reader + * @~English + * @brief Transcode a KTX2 texture with BasisLZ/ETC1S or UASTC images. + * + * If the texture contains BasisLZ supercompressed images, Inflates them from + * back to ETC1S then transcodes them to the specified block-compressed + * format. If the texture contains UASTC images, inflates them, if they have been + * supercompressed with zstd, then transcodes then to the specified format, The + * transcoded images replace the original images and the texture's fields including + * the DFD are modified to reflect the new format. + * + * These types of textures must be transcoded to a desired target + * block-compressed format before they can be uploaded to a GPU via a + * graphics API. + * + * The following block compressed transcode targets are available: @c KTX_TTF_ETC1_RGB, + * @c KTX_TTF_ETC2_RGBA, @c KTX_TTF_BC1_RGB, @c KTX_TTF_BC3_RGBA, + * @c KTX_TTF_BC4_R, @c KTX_TTF_BC5_RG, @c KTX_TTF_BC7_RGBA, + * @c @c KTX_TTF_PVRTC1_4_RGB, @c KTX_TTF_PVRTC1_4_RGBA, + * @c KTX_TTF_PVRTC2_4_RGB, @c KTX_TTF_PVRTC2_4_RGBA, @c KTX_TTF_ASTC_4x4_RGBA, + * @c KTX_TTF_ETC2_EAC_R11, @c KTX_TTF_ETC2_EAC_RG11, @c KTX_TTF_ETC and + * @c KTX_TTF_BC1_OR_3. + * + * @c KTX_TTF_ETC automatically selects between @c KTX_TTF_ETC1_RGB and + * @c KTX_TTF_ETC2_RGBA according to whether an alpha channel is available. @c KTX_TTF_BC1_OR_3 + * does likewise between @c KTX_TTF_BC1_RGB and @c KTX_TTF_BC3_RGBA. Note that if + * @c KTX_TTF_PVRTC1_4_RGBA or @c KTX_TTF_PVRTC2_4_RGBA is specified and there is no alpha + * channel @c KTX_TTF_PVRTC1_4_RGB or @c KTX_TTF_PVRTC2_4_RGB respectively will be selected. + * + * Transcoding to ATC & FXT1 formats is not supported by libktx as there + * are no equivalent Vulkan formats. + * + * The following uncompressed transcode targets are also available: @c KTX_TTF_RGBA32, + * @c KTX_TTF_RGB565, KTX_TTF_BGR565 and KTX_TTF_RGBA4444. + * + * The following @p transcodeFlags are available. + * + * @sa ktxtexture2_CompressBasis(). + * + * @param[in] This pointer to the ktxTexture2 object of interest. + * @param[in] outputFormat a value from the ktx_texture_transcode_fmt_e enum + * specifying the target format. + * @param[in] transcodeFlags bitfield of flags modifying the transcode + * operation. @sa ktx_texture_decode_flags_e. + * + * @return KTX_SUCCESS on success, other KTX_* enum values on error. + * + * @exception KTX_FILE_DATA_ERROR + * Supercompression global data is corrupted. + * @exception KTX_INVALID_OPERATION + * The texture's format is not transcodable (not + * ETC1S/BasisLZ or UASTC). + * @exception KTX_INVALID_OPERATION + * Supercompression global data is missing, i.e., + * the texture object is invalid. + * @exception KTX_INVALID_OPERATION + * Image data is missing, i.e., the texture object + * is invalid. + * @exception KTX_INVALID_OPERATION + * @p outputFormat is PVRTC1 but the texture does + * does not have power-of-two dimensions. + * @exception KTX_INVALID_VALUE @p outputFormat is invalid. + * @exception KTX_TRANSCODE_FAILED + * Something went wrong during transcoding. + * @exception KTX_UNSUPPORTED_FEATURE + * KTX_TF_PVRTC_DECODE_TO_NEXT_POW2 was requested + * or the specified transcode target has not been + * included in the library being used. + * @exception KTX_OUT_OF_MEMORY Not enough memory to carry out transcoding. + */ + 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 + // Constructor has checked color model matches BASIS_LZ. + && This->supercompressionScheme != KTX_SS_BASIS_LZ) + { + return KTX_INVALID_OPERATION; // Not in a transcodable format. + } + + 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 { + 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; + + // Do some format mapping. + 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: + // This transcoder does not write opaque alpha blocks. + outputFormat = alphaContent != eNone ? KTX_TTF_PVRTC1_4_RGBA + : KTX_TTF_PVRTC1_4_RGB; + break; + case KTX_TTF_PVRTC2_4_RGBA: + // This transcoder does not write opaque alpha blocks. + outputFormat = alphaContent != eNone ? KTX_TTF_PVRTC2_4_RGBA + : KTX_TTF_PVRTC2_4_RGB; + break; + default: + /*NOP*/; + } + + 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: + // Transcoding doesn't support BC1 alpha. + 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; + } + + + // Create a prototype texture to use for calculating sizes in the target + // format and, as useful side effects, provide us with a properly sized + // data allocation and the DFD for the target format. + 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); // The only run time error + return result; + } + + if (!This->pData) { + if (ktxTexture_isActiveStream((ktxTexture*)This)) { + // Load pending. Complete it. + result = ktxTexture2_LoadImageData(This, NULL, 0); + if (result != KTX_SUCCESS) + { + ktxTexture2_Destroy(prototype); + return result; + } + } else { + // No data to transcode. + ktxTexture2_Destroy(prototype); + return KTX_INVALID_OPERATION; + } + } + + // Transcoder global initialization. Requires ~9 milliseconds when compiled + // and executed natively on a Core i7 2.2 GHz. If this is too slow, the + // tables it computes can easily be moved to be compiled in. + 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) { + // Fix up the current texture + 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; + // Copy the levelIndex from the prototype to This. + memcpy(priv._levelIndex, protoPriv._levelIndex, + This->numLevels * sizeof(ktxLevelIndexEntry)); + // Move the DFD and data from the prototype to This. + 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; + } + +/** + * @memberof ktxTexture2 @private + * @ingroup reader + * @~English + * @brief Transcode a KTX2 texture with BasisLZ supercompressed ETC1S images. + * + * Inflates the images from BasisLZ supercompression back to ETC1S + * then transcodes them to the specified block-compressed format. The + * transcoded images replace the original images and the texture's fields + * including the DFD are modified to reflect the new format. + * + * BasisLZ supercompressed textures must be transcoded to a desired target + * block-compressed format before they can be uploaded to a GPU via a graphics + * API. + * + * The following block compressed transcode targets are available: @c KTX_TTF_ETC1_RGB, + * @c KTX_TTF_ETC2_RGBA, @c KTX_TTF_BC1_RGB, @c KTX_TTF_BC3_RGBA, + * @c KTX_TTF_BC4_R, @c KTX_TTF_BC5_RG, @c KTX_TTF_BC7_RGBA, + * @c @c KTX_TTF_PVRTC1_4_RGB, @c KTX_TTF_PVRTC1_4_RGBA, + * @c KTX_TTF_PVRTC2_4_RGB, @c KTX_TTF_PVRTC2_4_RGBA, @c KTX_TTF_ASTC_4x4_RGBA, + * @c KTX_TTF_ETC2_EAC_R11, @c KTX_TTF_ETC2_EAC_RG11, @c KTX_TTF_ETC and + * @c KTX_TTF_BC1_OR_3. + * + * @c KTX_TTF_ETC automatically selects between @c KTX_TTF_ETC1_RGB and + * @c KTX_TTF_ETC2_RGBA according to whether an alpha channel is available. @c KTX_TTF_BC1_OR_3 + * does likewise between @c KTX_TTF_BC1_RGB and @c KTX_TTF_BC3_RGBA. Note that if + * @c KTX_TTF_PVRTC1_4_RGBA or @c KTX_TTF_PVRTC2_4_RGBA is specified and there is no alpha + * channel @c KTX_TTF_PVRTC1_4_RGB or @c KTX_TTF_PVRTC2_4_RGB respectively will be selected. + * + * ATC & FXT1 formats are not supported by KTX2 & libktx as there are no equivalent Vulkan formats. + * + * The following uncompressed transcode targets are also available: @c KTX_TTF_RGBA32, + * @c KTX_TTF_RGB565, KTX_TTF_BGR565 and KTX_TTF_RGBA4444. + * + * The following @p transcodeFlags are available. + * + * @sa ktxtexture2_CompressBasis(). + * + * @param[in] This pointer to the ktxTexture2 object of interest. + * @param[in] outputFormat a value from the ktx_texture_transcode_fmt_e enum + * specifying the target format. + * @param[in] transcodeFlags bitfield of flags modifying the transcode + * operation. @sa ktx_texture_decode_flags_e. + * + * @return KTX_SUCCESS on success, other KTX_* enum values on error. + * + * @exception KTX_FILE_DATA_ERROR + * Supercompression global data is corrupted. + * @exception KTX_INVALID_OPERATION + * The texture's format is not transcodable (not + * ETC1S/BasisLZ or UASTC). + * @exception KTX_INVALID_OPERATION + * Supercompression global data is missing, i.e., + * the texture object is invalid. + * @exception KTX_INVALID_OPERATION + * Image data is missing, i.e., the texture object + * is invalid. + * @exception KTX_INVALID_OPERATION + * @p outputFormat is PVRTC1 but the texture does + * does not have power-of-two dimensions. + * @exception KTX_INVALID_VALUE @p outputFormat is invalid. + * @exception KTX_TRANSCODE_FAILED + * Something went wrong during transcoding. The + * texture object will be corrupted. + * @exception KTX_UNSUPPORTED_FEATURE + * KTX_TF_PVRTC_DECODE_TO_NEXT_POW2 was requested + * or the specified transcode target has not been + * included in the library being used. + * @exception KTX_OUT_OF_MEMORY Not enough memory to carry out transcoding. + */ +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; + } + + // Compute some helpful numbers. + // + // firstImages contains the indices of the first images for each level to + // ease finding the correct slice description when iterating from smallest + // level to largest or when randomly accessing them (t.b.c). The last array + // entry contains the total number of images, for calculating the offsets + // of the endpoints, etc. + uint32_t* firstImages = new uint32_t[This->numLevels+1]; + + // Temporary invariant value + uint32_t layersFaces = This->numLayers * This->numFaces; + firstImages[0] = 0; + for (uint32_t level = 1; level <= This->numLevels; level++) { + // NOTA BENE: numFaces * depth is only reasonable because they can't + // both be > 1. I.e there are no 3d cubemaps. + 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; + } + // FIXME: Do more validation. + + // Prepare low-level transcoder for transcoding slices. + basist::basisu_lowlevel_etc1s_transcoder bit; + + // basisu_transcoder_state is used to find the previous frame when + // decoding a video P-Frame. It tracks the previous frame for each mip + // level. For cube map array textures we need to find the previous frame + // for each face so we a state per face. Although providing this is only + // needed for video, it is easier to always pass our own. + 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); + + // Find matching VkFormat and calculate output sizes. + + const bool isVideo = This->isVideo; + + ktx_uint8_t* pXcodedData = prototype->pData; + // Inconveniently, the output buffer size parameter of transcode_image + // has to be in pixels for uncompressed output and in blocks for + // compressed output. The only reason for humouring the API is so + // its buffer size tests provide a real check. An alternative is to + // always provide the size in bytes which will always pass. + 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); + + // Finally we're ready to transcode the slices. + + // FIXME: Iframe flag needs to be queryable by the application. In Basis + // the app can query file_info and image_info from the transcoder which + // returns a structure with lots of info about the image. + + 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); + // ETC1S texel block dimensions + 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 == 1 ? depth : This->numFaces; + 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; + // FIXME: Figure out a way to get the size out of the transcoder. + levelImageSizeOut = ktxTexture2_GetImageSize(prototype, level); + for (; image < endImage; image++) { + const ktxBasisLzEtc1sImageDesc& imageDesc = imageDescs[image]; + + basisu_transcoder_state& xcoderState = xcoderStates[stateIndex]; + // We have face0 [face1 ...] within each layer. Use `stateIndex` + // rather than a double loop of layers and faceSlices as this + // works for 3d texture and non-array cube maps as well as + // cube map arrays without special casing. + if (++stateIndex == xcoderStates.size()) + stateIndex = 0; + + if (alphaContent != eNone) + { + // The slice descriptions should have alpha information. + 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, + // Our P-Frame flag is in the same bit as + // cSliceDescFlagsFrameIsIFrame. We have to + // invert it to make it an I-Frame flag. + // + // API currently doesn't have any way to pass + // the I-Frame flag. + //imageDesc.imageFlags ^ cSliceDescFlagsFrameIsIFrame, + 0, // output_row_pitch_in_blocks_or_pixels + &xcoderState, + 0 // output_rows_in_pixels + ); + if (!status) { + result = KTX_TRANSCODE_FAILED; + goto cleanup; + } + + writeOffset += levelImageSizeOut; + levelSizeOut += levelImageSizeOut; + } // end images loop + protoLevelIndex[level].byteOffset = levelOffsetWrite; + protoLevelIndex[level].byteLength = levelSizeOut; + protoLevelIndex[level].uncompressedByteLength = levelSizeOut; + levelOffsetWrite += levelSizeOut; + assert(levelOffsetWrite == writeOffset); + // In case of transcoding to uncompressed. + levelOffsetWrite = _KTX_PADN(protoPriv._requiredLevelAlignment, + levelOffsetWrite); + } // level loop + + 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; + // See comment on same declaration in transcodeEtc1s. + 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); + // UASTC texel block dimensions + 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]; + // See comment before same lines in transcodeEtc1s. + 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, // is_video + //imageDesc.imageFlags ^ cSliceDescFlagsFrameIsIFrame, + 0, // output_row_pitch_in_blocks_or_pixels + &xcoderState, // pState + 0, // output_rows_in_pixels, + -1, // channel0 + -1 // channel1 + ); + if (!status) + return KTX_TRANSCODE_FAILED; + writeOffset += levelImageSizeOut; + levelSizeOut += levelImageSizeOut; + levelImageOffsetIn += levelImageSizeIn; + } + protoLevelIndex[level].byteOffset = levelOffsetWrite; + // writeOffset will be equal to total size of the images in the level. + protoLevelIndex[level].byteLength = levelSizeOut; + protoLevelIndex[level].uncompressedByteLength = levelSizeOut; + levelOffsetWrite += levelSizeOut; + } + // In case of transcoding to uncompressed. + levelOffsetWrite = _KTX_PADN(protoPriv._requiredLevelAlignment, + levelOffsetWrite); + return KTX_SUCCESS; +} |