/* -*- 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 #include #include #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(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 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 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; }