summaryrefslogtreecommitdiffstats
path: root/thirdparty/libktx/lib/basis_transcode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'thirdparty/libktx/lib/basis_transcode.cpp')
-rw-r--r--thirdparty/libktx/lib/basis_transcode.cpp733
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;
+}