1238 lines
48 KiB
C++
1238 lines
48 KiB
C++
/* -*- tab-width: 4; -*- */
|
|
/* vi: set sw=2 ts=4 expandtab: */
|
|
|
|
/*
|
|
* Copyright (c) 2021, Arm Limited and Contributors
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @internal
|
|
* @file
|
|
* @~English
|
|
*
|
|
* @brief Functions for compressing a texture to ASTC format and decoding one in ASTC format..
|
|
*
|
|
* @author Wasim Abbas , www.arm.com
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <cstring>
|
|
#include <inttypes.h>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include <zstd.h>
|
|
#include <KHR/khr_df.h>
|
|
|
|
#include "dfdutils/dfd.h"
|
|
#include "ktx.h"
|
|
#include "ktxint.h"
|
|
#include "texture2.h"
|
|
#include "vkformat_enum.h"
|
|
|
|
#include "astc-encoder/Source/astcenc.h"
|
|
|
|
//************************************************************************
|
|
//* Functions common to decoder and encoder *
|
|
//************************************************************************
|
|
|
|
#if !defined(_WIN32) || defined(WIN32_HAS_PTHREADS)
|
|
#include <pthread.h>
|
|
#else
|
|
// Provide pthreads support on windows
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <Windows.h>
|
|
|
|
typedef HANDLE pthread_t;
|
|
typedef int pthread_attr_t;
|
|
|
|
/* Public function, see header file for detailed documentation */
|
|
static int
|
|
pthread_create(pthread_t* thread, const pthread_attr_t* attribs,
|
|
void* (*threadfunc)(void*), void* thread_arg) {
|
|
(void)attribs;
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wcast-function-type-mismatch"
|
|
#endif
|
|
LPTHREAD_START_ROUTINE func = (LPTHREAD_START_ROUTINE)(threadfunc);
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
*thread = CreateThread(nullptr, 0, func, thread_arg, 0, nullptr);
|
|
return 0;
|
|
}
|
|
|
|
/* Public function, see header file for detailed documentation */
|
|
static int
|
|
pthread_join(pthread_t thread, void** value) {
|
|
(void)value;
|
|
WaitForSingleObject(thread, INFINITE);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief Worker thread helper payload for launchThreads.
|
|
*/
|
|
struct LaunchDesc {
|
|
/** The native thread handle. */
|
|
pthread_t threadHandle;
|
|
/** The total number of threads in the thread pool. */
|
|
int threadCount;
|
|
/** The thread index in the thread pool. */
|
|
int threadId;
|
|
/** The user thread function to execute. */
|
|
void (*func)(int, int, void*);
|
|
/** The user thread payload. */
|
|
void* payload;
|
|
};
|
|
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief Helper function to translate thread entry points.
|
|
*
|
|
* Convert a (void*) thread entry to an (int, void*) thread entry, where the
|
|
* integer contains the thread ID in the thread pool.
|
|
*
|
|
* @param p The thread launch helper payload.
|
|
*/
|
|
static void*
|
|
launchThreadsHelper(void *p) {
|
|
LaunchDesc* ltd = (LaunchDesc*)p;
|
|
ltd->func(ltd->threadCount, ltd->threadId, ltd->payload);
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
static void
|
|
launchThreads(int threadCount, void (*func)(int, int, void*), void *payload) {
|
|
// Directly execute single threaded workloads on this thread
|
|
if (threadCount <= 1) {
|
|
func(1, 0, payload);
|
|
return;
|
|
}
|
|
|
|
// Otherwise spawn worker threads
|
|
LaunchDesc *threadDescs = new LaunchDesc[threadCount];
|
|
for (int i = 0; i < threadCount; i++) {
|
|
threadDescs[i].threadCount = threadCount;
|
|
threadDescs[i].threadId = i;
|
|
threadDescs[i].payload = payload;
|
|
threadDescs[i].func = func;
|
|
|
|
pthread_create(&(threadDescs[i].threadHandle), nullptr,
|
|
launchThreadsHelper, (void*)&(threadDescs[i]));
|
|
}
|
|
|
|
// ... and then wait for them to complete
|
|
for (int i = 0; i < threadCount; i++) {
|
|
pthread_join(threadDescs[i].threadHandle, nullptr);
|
|
}
|
|
|
|
delete[] threadDescs;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
* @~English
|
|
* @brief Map astcenc error code to KTX error code
|
|
*
|
|
* Asserts are fired on errors reflecting bad parameters passed by libktx
|
|
* or astcenc compilation settings that do not permit correct operation.
|
|
*
|
|
* @param astc_error The error code to be mapped.
|
|
* @return An equivalent KTX error code.
|
|
*/
|
|
static ktx_error_code_e
|
|
mapAstcError(astcenc_error astc_error) {
|
|
switch (astc_error) {
|
|
case ASTCENC_SUCCESS:
|
|
return KTX_SUCCESS;
|
|
case ASTCENC_ERR_OUT_OF_MEM:
|
|
return KTX_OUT_OF_MEMORY;
|
|
case ASTCENC_ERR_BAD_BLOCK_SIZE: //[[fallthrough]];
|
|
case ASTCENC_ERR_BAD_DECODE_MODE: //[[fallthrough]];
|
|
case ASTCENC_ERR_BAD_FLAGS: //[[fallthrough]];
|
|
case ASTCENC_ERR_BAD_PARAM: //[[fallthrough]];
|
|
case ASTCENC_ERR_BAD_PROFILE: //[[fallthrough]];
|
|
case ASTCENC_ERR_BAD_QUALITY: //[[fallthrough]];
|
|
case ASTCENC_ERR_BAD_SWIZZLE:
|
|
assert(false && "libktx passing bad parameter to astcenc");
|
|
return KTX_INVALID_VALUE;
|
|
case ASTCENC_ERR_BAD_CONTEXT:
|
|
assert(false && "libktx has set up astcenc context incorrectly");
|
|
return KTX_INVALID_OPERATION;
|
|
case ASTCENC_ERR_BAD_CPU_FLOAT:
|
|
assert(false && "Code compiled such that float operations do not meet codec's assumptions.");
|
|
// Most likely compiled with fast math enabled.
|
|
return KTX_INVALID_OPERATION;
|
|
case ASTCENC_ERR_NOT_IMPLEMENTED:
|
|
assert(false && "ASTCENC_BLOCK_MAX_TEXELS not enough for specified block size");
|
|
return KTX_UNSUPPORTED_FEATURE;
|
|
// gcc fails to detect that the switch handles all astcenc_error
|
|
// enumerators and raises a return-type error, "control reaches end of
|
|
// non-void function", hence this
|
|
default:
|
|
assert(false && "Unhandled astcenc error");
|
|
return KTX_INVALID_OPERATION;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @memberof ktxTexture
|
|
* @internal
|
|
* @ingroup reader writer
|
|
* @~English
|
|
* @brief Creates valid ASTC decoder profile from VkFormat
|
|
*
|
|
* @return Valid astc_profile from VkFormat
|
|
*/
|
|
static astcenc_profile
|
|
astcProfile(bool sRGB, bool ldr) {
|
|
|
|
if (sRGB && ldr)
|
|
return ASTCENC_PRF_LDR_SRGB;
|
|
else if (!sRGB) {
|
|
if (ldr)
|
|
return ASTCENC_PRF_LDR;
|
|
else
|
|
return ASTCENC_PRF_HDR;
|
|
}
|
|
// TODO: Add support for the following
|
|
// KTX_PACK_ASTC_ENCODER_ACTION_COMP_HDR_RGB_LDR_ALPHA; currently not supported
|
|
|
|
assert(ldr && "HDR sRGB profile not supported");
|
|
|
|
return ASTCENC_PRF_LDR_SRGB;
|
|
}
|
|
|
|
//************************************************************************
|
|
//* Decoder functions *
|
|
//************************************************************************
|
|
|
|
/**
|
|
* @memberof ktxTexture
|
|
* @internal
|
|
* @ingroup reader
|
|
* @~English
|
|
* @brief Used to check if an ASTC encoded texture is LDR format or not.
|
|
*
|
|
* @return true if the VkFormat is an ASTC LDR format.
|
|
*/
|
|
inline bool isFormatAstcLDR(ktxTexture2* This) noexcept {
|
|
return (KHR_DFDSVAL(This->pDfd + 1, 0, QUALIFIERS) & KHR_DF_SAMPLE_DATATYPE_FLOAT) == 0;
|
|
}
|
|
|
|
/**
|
|
* @memberof ktxTexture
|
|
* @internal
|
|
* @ingroup reader
|
|
* @~English
|
|
* @brief Should be used to get uncompressed version of ASTC VkFormat
|
|
*
|
|
* The decompressed format is calculated from corresponding ASTC format. There are
|
|
* only 3 possible options currently supported. RGBA8, SRGBA8 and RGBA32.
|
|
*
|
|
* @return Uncompressed version of VKFormat for a specific ASTC VkFormat
|
|
*/
|
|
inline VkFormat getUncompressedFormat(ktxTexture2* This) noexcept {
|
|
uint32_t* BDB = This->pDfd + 1;
|
|
|
|
if (KHR_DFDSVAL(BDB, 0, QUALIFIERS) & KHR_DF_SAMPLE_DATATYPE_FLOAT) {
|
|
return VK_FORMAT_R32G32B32A32_SFLOAT;
|
|
} else {
|
|
if (khr_df_transfer_e(KHR_DFDVAL(BDB, TRANSFER) == KHR_DF_TRANSFER_SRGB))
|
|
return VK_FORMAT_R8G8B8A8_SRGB;
|
|
else
|
|
return VK_FORMAT_R8G8B8A8_UNORM;
|
|
}
|
|
}
|
|
|
|
struct decompression_workload
|
|
{
|
|
astcenc_context* context;
|
|
uint8_t* data;
|
|
size_t data_len;
|
|
astcenc_image* image_out;
|
|
astcenc_swizzle swizzle;
|
|
astcenc_error error;
|
|
};
|
|
|
|
/**
|
|
* @internal
|
|
* @ingroup reader
|
|
* @brief Runner callback function for a decompression worker thread.
|
|
*
|
|
* @param thread_count The number of threads in the worker pool.
|
|
* @param thread_id The index of this thread in the worker pool.
|
|
* @param payload The parameters for this thread.
|
|
*/
|
|
static void decompression_workload_runner(int thread_count, int thread_id, void* payload) {
|
|
(void)thread_count;
|
|
|
|
decompression_workload* work = static_cast<decompression_workload*>(payload);
|
|
astcenc_error error = astcenc_decompress_image(work->context, work->data, work->data_len,
|
|
work->image_out, &work->swizzle, thread_id);
|
|
// This is a racy update, so which error gets returned is a random, but it
|
|
// will reliably report an error if an error occurs
|
|
if (error != ASTCENC_SUCCESS)
|
|
{
|
|
work->error = error;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Cannot use DECLARE_PRIVATE macro declared in texture.h because it calls the
|
|
* variable `private` which is obviously a no-no in c++. TODO: consider changing.
|
|
* Declare our own similar macros. Cognizant that the using functions handle both
|
|
* This and a prototype object, pass the object as a parameter.
|
|
*/
|
|
#define DECLARE_PRIVATE_EX(n,t2) ktxTexture2_private& n = *(t2->_private)
|
|
#define DECLARE_PROTECTED_EX(n,t2) ktxTexture_protected& n = *(t2->_protected)
|
|
|
|
/**
|
|
* @ingroup reader
|
|
* @brief Decodes a ktx2 texture object, if it is ASTC encoded.
|
|
|
|
* The decompressed format is calculated from corresponding ASTC format. There are
|
|
* only 3 possible options currently supported. RGBA8, SRGBA8 and RGBA32.
|
|
* @note 3d textures are decoded to a multi-slice 3d texture.
|
|
*
|
|
* Updates @p This with the decoded image.
|
|
*
|
|
* @param This The texture to decode
|
|
*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_FILE_DATA_ERROR
|
|
* DFD is incorrect: supercompression scheme or
|
|
* sample's channelId do not match ASTC colorModel.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The texture's images are not in ASTC format.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The texture object does not contain any data.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* ASTC decoder failed to decompress image.
|
|
* Possibly due to incorrect floating point
|
|
* compilation settings. Should not happen
|
|
* in release package.
|
|
* @exception KTX_OUT_OF_MEMORY Not enough memory to carry out decoding.
|
|
* @exception KTX_UNSUPPORTED_FEATURE
|
|
* The texture's images are supercompressed with an
|
|
* unsupported scheme.
|
|
* @exception KTX_UNSUPPORTED_FEATURE
|
|
* ASTC encoder not compiled with enough
|
|
* capacity for requested block size. Should
|
|
* not happen in release package.
|
|
*/
|
|
KTX_error_code
|
|
ktxTexture2_DecodeAstc(ktxTexture2 *This) {
|
|
// Decompress This using astc-decoder
|
|
uint32_t* BDB = This->pDfd + 1;
|
|
khr_df_model_e colorModel = (khr_df_model_e)KHR_DFDVAL(BDB, MODEL);
|
|
if (colorModel != KHR_DF_MODEL_ASTC) {
|
|
return KTX_INVALID_OPERATION; // Not in astc decodable format
|
|
}
|
|
if (This->supercompressionScheme == KTX_SS_BASIS_LZ) {
|
|
return KTX_FILE_DATA_ERROR; // Not a valid file.
|
|
}
|
|
// Safety check.
|
|
if (This->supercompressionScheme > KTX_SS_END_RANGE) {
|
|
return KTX_UNSUPPORTED_FEATURE; // Unsupported scheme.
|
|
}
|
|
// Other schemes are decoded in ktxTexture2_LoadImageData.
|
|
|
|
DECLARE_PRIVATE_EX(priv, This);
|
|
|
|
uint32_t channelId = KHR_DFDSVAL(BDB, 0, CHANNELID);
|
|
if (channelId != KHR_DF_CHANNEL_ASTC_DATA) {
|
|
return KTX_FILE_DATA_ERROR;
|
|
}
|
|
|
|
ktx_uint32_t vkformat = (ktx_uint32_t)getUncompressedFormat(This);
|
|
|
|
// 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 decode.
|
|
ktxTexture2_Destroy(prototype);
|
|
return KTX_INVALID_OPERATION;
|
|
}
|
|
}
|
|
|
|
// This is where I do the decompression from "This" to prototype target
|
|
astcenc_swizzle swizzle{ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A};
|
|
float quality{ASTCENC_PRE_MEDIUM};
|
|
uint32_t flags{0}; // TODO: Use normals mode to reconstruct normals params->normalMap ? ASTCENC_FLG_MAP_NORMAL : 0};
|
|
|
|
uint32_t block_size_x = KHR_DFDVAL(BDB, TEXELBLOCKDIMENSION0) + 1;
|
|
uint32_t block_size_y = KHR_DFDVAL(BDB, TEXELBLOCKDIMENSION1) + 1;
|
|
uint32_t block_size_z = KHR_DFDVAL(BDB, TEXELBLOCKDIMENSION2) + 1;
|
|
|
|
// quality = astcQuality(params->qualityLevel);
|
|
// swizzle = astcSwizzle(*params);
|
|
|
|
// if(params->perceptual) flags |= ASTCENC_FLG_USE_PERCEPTUAL;
|
|
|
|
ktx_uint32_t transfer = KHR_DFDVAL(BDB, TRANSFER);
|
|
bool ldr = isFormatAstcLDR(This);
|
|
astcenc_profile profile = astcProfile(transfer == KHR_DF_TRANSFER_SRGB, ldr);
|
|
|
|
uint32_t threadCount{1}; // Decompression isn't the bottleneck and only used when checking for psnr and ssim
|
|
astcenc_config astc_config;
|
|
astcenc_context *astc_context;
|
|
astcenc_error astc_error = astcenc_config_init(profile,
|
|
block_size_x, block_size_y, block_size_z,
|
|
quality, flags,
|
|
&astc_config);
|
|
|
|
if (astc_error != ASTCENC_SUCCESS)
|
|
return mapAstcError(astc_error);
|
|
|
|
astc_error = astcenc_context_alloc(&astc_config, threadCount, &astc_context);
|
|
|
|
if (astc_error != ASTCENC_SUCCESS)
|
|
return mapAstcError(astc_error);
|
|
|
|
decompression_workload work;
|
|
work.context = astc_context;
|
|
work.swizzle = swizzle;
|
|
work.error = ASTCENC_SUCCESS;
|
|
|
|
for (uint32_t levelIndex = 0; levelIndex < This->numLevels; ++levelIndex) {
|
|
const uint32_t imageWidth = std::max(This->baseWidth >> levelIndex, 1u);
|
|
const uint32_t imageHeight = std::max(This->baseHeight >> levelIndex, 1u);
|
|
const uint32_t imageDepths = std::max(This->baseDepth >> levelIndex, 1u);
|
|
|
|
for (uint32_t layerIndex = 0; layerIndex < This->numLayers; ++layerIndex) {
|
|
for (uint32_t faceIndex = 0; faceIndex < This->numFaces; ++faceIndex) {
|
|
for (uint32_t depthSliceIndex = 0; depthSliceIndex < imageDepths; ++depthSliceIndex) {
|
|
|
|
ktx_size_t levelImageSizeIn = ktxTexture_calcImageSize(ktxTexture(This), levelIndex, KTX_FORMAT_VERSION_TWO);
|
|
|
|
ktx_size_t imageOffsetIn;
|
|
ktx_size_t imageOffsetOut;
|
|
|
|
ktxTexture2_GetImageOffset(This, levelIndex, layerIndex, faceIndex + depthSliceIndex, &imageOffsetIn);
|
|
ktxTexture2_GetImageOffset(prototype, levelIndex, layerIndex, faceIndex + depthSliceIndex, &imageOffsetOut);
|
|
|
|
auto* imageDataIn = This->pData + imageOffsetIn;
|
|
auto* imageDataOut = prototype->pData + imageOffsetOut;
|
|
|
|
astcenc_image imageOut;
|
|
imageOut.dim_x = imageWidth;
|
|
imageOut.dim_y = imageHeight;
|
|
imageOut.dim_z = imageDepths;
|
|
imageOut.data_type = ASTCENC_TYPE_U8; // TODO: Fix for HDR types
|
|
imageOut.data = (void**)&imageDataOut; // TODO: Fix for HDR types
|
|
|
|
work.data = imageDataIn;
|
|
work.data_len = levelImageSizeIn;
|
|
work.image_out = &imageOut;
|
|
|
|
// Only launch worker threads for multi-threaded use - it makes basic
|
|
// single-threaded profiling and debugging a little less convoluted
|
|
if (threadCount > 1) {
|
|
launchThreads(threadCount, decompression_workload_runner, &work);
|
|
} else {
|
|
work.error = astcenc_decompress_image(work.context, work.data, work.data_len,
|
|
work.image_out, &work.swizzle, 0);
|
|
}
|
|
|
|
// Reset ASTC context for next image
|
|
astcenc_decompress_reset(astc_context);
|
|
|
|
if (work.error != ASTCENC_SUCCESS) {
|
|
//std::cout << "ASTC decompressor failed\n" << astcenc_get_error_string(work.error) << std::endl;
|
|
|
|
astcenc_context_free(astc_context);
|
|
return mapAstcError(work.error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// We are done with astcdecoder
|
|
astcenc_context_free(astc_context);
|
|
|
|
if (result == KTX_SUCCESS) {
|
|
// Fix up the current texture
|
|
DECLARE_PROTECTED_EX(thisPrtctd, This);
|
|
DECLARE_PRIVATE_EX(protoPriv, prototype);
|
|
DECLARE_PROTECTED_EX(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;
|
|
// Free SGD data
|
|
This->_private->_sgdByteLength = 0;
|
|
if (This->_private->_supercompressionGlobalData) {
|
|
free(This->_private->_supercompressionGlobalData);
|
|
This->_private->_supercompressionGlobalData = NULL;
|
|
}
|
|
}
|
|
ktxTexture2_Destroy(prototype);
|
|
return result;
|
|
}
|
|
|
|
//************************************************************************
|
|
//* Encoder functions *
|
|
//************************************************************************
|
|
|
|
#if KTX_FEATURE_WRITE
|
|
static astcenc_image*
|
|
imageAllocate(uint32_t bitness,
|
|
uint32_t dim_x, uint32_t dim_y, uint32_t dim_z) {
|
|
astcenc_image *img = new astcenc_image;
|
|
assert(img);
|
|
|
|
img->dim_x = dim_x;
|
|
img->dim_y = dim_y;
|
|
img->dim_z = dim_z;
|
|
|
|
if (bitness == 8) {
|
|
void **data = new void *[dim_z];
|
|
img->data_type = ASTCENC_TYPE_U8;
|
|
img->data = data;
|
|
|
|
for (uint32_t z = 0; z < dim_z; z++) {
|
|
data[z] = new uint8_t[dim_x * dim_y * 4];
|
|
}
|
|
}
|
|
else if (bitness == 16) {
|
|
void **data = new void *[dim_z];
|
|
img->data_type = ASTCENC_TYPE_F16;
|
|
img->data = data;
|
|
|
|
for (uint32_t z = 0; z < dim_z; z++) {
|
|
data[z] = new uint16_t[dim_x * dim_y * 4];
|
|
}
|
|
}
|
|
else { // if (bitness == 32)
|
|
assert(bitness == 32);
|
|
void **data = new void *[dim_z];
|
|
img->data_type = ASTCENC_TYPE_F32;
|
|
img->data = data;
|
|
|
|
for (uint32_t z = 0; z < dim_z; z++) {
|
|
data[z] = new float[dim_x * dim_y * 4];
|
|
}
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
static void
|
|
imageFree(astcenc_image *img) {
|
|
if (img == nullptr) {
|
|
return;
|
|
}
|
|
|
|
for (uint32_t z = 0; z < img->dim_z; z++) {
|
|
delete[](char *) img->data[z];
|
|
}
|
|
|
|
delete[] img->data;
|
|
delete img;
|
|
}
|
|
|
|
static astcenc_image*
|
|
unorm8x1ArrayToImage(const uint8_t *data, uint32_t dim_x, uint32_t dim_y) {
|
|
astcenc_image *img = imageAllocate(8, dim_x, dim_y, 1);
|
|
assert(img);
|
|
|
|
for (uint32_t y = 0; y < dim_y; y++) {
|
|
uint8_t * data8 = static_cast<uint8_t *>(img->data[0]);
|
|
const uint8_t *src = data + dim_x * y;
|
|
|
|
for (uint32_t x = 0; x < dim_x; x++) {
|
|
data8[(4 * dim_x * y) + (4 * x) ] = src[x];
|
|
data8[(4 * dim_x * y) + (4 * x + 1)] = src[x];
|
|
data8[(4 * dim_x * y) + (4 * x + 2)] = src[x];
|
|
data8[(4 * dim_x * y) + (4 * x + 3)] = 255;
|
|
}
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
static astcenc_image*
|
|
unorm8x2ArrayToImage(const uint8_t *data, uint32_t dim_x, uint32_t dim_y) {
|
|
astcenc_image *img = imageAllocate(8, dim_x, dim_y, 1);
|
|
assert(img);
|
|
|
|
for (uint32_t y = 0; y < dim_y; y++) {
|
|
uint8_t * data8 = static_cast<uint8_t *>(img->data[0]);
|
|
const uint8_t *src = data + 2 * dim_x * y;
|
|
|
|
for (uint32_t x = 0; x < dim_x; x++) {
|
|
data8[(4 * dim_x * y) + (4 * x) ] = src[2 * x ];
|
|
data8[(4 * dim_x * y) + (4 * x + 1)] = src[2 * x ];
|
|
data8[(4 * dim_x * y) + (4 * x + 2)] = src[2 * x ];
|
|
data8[(4 * dim_x * y) + (4 * x + 3)] = src[2 * x + 1];
|
|
}
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
static astcenc_image*
|
|
unorm8x3ArrayToImage(const uint8_t *data, uint32_t dim_x, uint32_t dim_y) {
|
|
astcenc_image *img = imageAllocate(8, dim_x, dim_y, 1);
|
|
assert(img);
|
|
|
|
for (uint32_t y = 0; y < dim_y; y++) {
|
|
uint8_t * data8 = static_cast<uint8_t *>(img->data[0]);
|
|
const uint8_t *src = data + 3 * dim_x * y;
|
|
|
|
for (uint32_t x = 0; x < dim_x; x++) {
|
|
data8[(4 * dim_x * y) + (4 * x) ] = src[3 * x ];
|
|
data8[(4 * dim_x * y) + (4 * x + 1)] = src[3 * x + 1];
|
|
data8[(4 * dim_x * y) + (4 * x + 2)] = src[3 * x + 2];
|
|
data8[(4 * dim_x * y) + (4 * x + 3)] = 255;
|
|
}
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
static astcenc_image*
|
|
unorm8x4ArrayToImage(const uint8_t *data, uint32_t dim_x, uint32_t dim_y) {
|
|
astcenc_image *img = imageAllocate(8, dim_x, dim_y, 1);
|
|
assert(img);
|
|
|
|
for (uint32_t y = 0; y < dim_y; y++) {
|
|
uint8_t * data8 = static_cast<uint8_t *>(img->data[0]);
|
|
const uint8_t *src = data + 4 * dim_x * y;
|
|
|
|
for (uint32_t x = 0; x < dim_x; x++) {
|
|
data8[(4 * dim_x * y) + (4 * x) ] = src[4 * x ];
|
|
data8[(4 * dim_x * y) + (4 * x + 1)] = src[4 * x + 1];
|
|
data8[(4 * dim_x * y) + (4 * x + 2)] = src[4 * x + 2];
|
|
data8[(4 * dim_x * y) + (4 * x + 3)] = src[4 * x + 3];
|
|
}
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
/**
|
|
* @memberof ktxTexture
|
|
* @internal
|
|
* @ingroup writer
|
|
* @~English
|
|
* @brief Creates default ASTC parameters
|
|
*
|
|
* @return ktxAstcParams with default options for ASTC compressor
|
|
*/
|
|
static ktxAstcParams
|
|
astcDefaultOptions() {
|
|
ktxAstcParams params{};
|
|
params.structSize = sizeof(params);
|
|
params.threadCount = 1;
|
|
params.blockDimension = KTX_PACK_ASTC_BLOCK_DIMENSION_6x6;
|
|
params.mode = KTX_PACK_ASTC_ENCODER_MODE_LDR;
|
|
params.qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM;
|
|
params.normalMap = false;
|
|
|
|
return params;
|
|
}
|
|
|
|
/**
|
|
* @memberof ktxTexture
|
|
* @internal
|
|
* @ingroup writer
|
|
* @~English
|
|
* @brief Should be used to get VkFormat from ASTC block enum
|
|
*
|
|
* @return VKFormat for a specific ASTC block size
|
|
*/
|
|
static VkFormat
|
|
astcVkFormat(ktx_uint32_t block_size, bool sRGB) {
|
|
if (sRGB) {
|
|
switch (block_size) {
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4: return VK_FORMAT_ASTC_4x4_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4: return VK_FORMAT_ASTC_5x4_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5: return VK_FORMAT_ASTC_5x5_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5: return VK_FORMAT_ASTC_6x5_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6: return VK_FORMAT_ASTC_6x6_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_8x5: return VK_FORMAT_ASTC_8x5_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_8x6: return VK_FORMAT_ASTC_8x6_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_8x8: return VK_FORMAT_ASTC_8x8_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_10x5: return VK_FORMAT_ASTC_10x5_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_10x6: return VK_FORMAT_ASTC_10x6_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_10x8: return VK_FORMAT_ASTC_10x8_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_10x10: return VK_FORMAT_ASTC_10x10_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_12x10: return VK_FORMAT_ASTC_12x10_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_12x12: return VK_FORMAT_ASTC_12x12_SRGB_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_3x3x3: return VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_4x3x3: return VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x3: return VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x4: return VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4x4: return VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x4: return VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x5: return VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5x5: return VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x5: return VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x6: return VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT;
|
|
}
|
|
} else {
|
|
switch (block_size) {
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4: return VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4: return VK_FORMAT_ASTC_5x4_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5: return VK_FORMAT_ASTC_5x5_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5: return VK_FORMAT_ASTC_6x5_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6: return VK_FORMAT_ASTC_6x6_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_8x5: return VK_FORMAT_ASTC_8x5_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_8x6: return VK_FORMAT_ASTC_8x6_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_8x8: return VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_10x5: return VK_FORMAT_ASTC_10x5_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_10x6: return VK_FORMAT_ASTC_10x6_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_10x8: return VK_FORMAT_ASTC_10x8_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_10x10: return VK_FORMAT_ASTC_10x10_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_12x10: return VK_FORMAT_ASTC_12x10_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_12x12: return VK_FORMAT_ASTC_12x12_UNORM_BLOCK;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_3x3x3: return VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_4x3x3: return VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x3: return VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x4: return VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4x4: return VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x4: return VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x5: return VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5x5: return VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x5: return VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x6: return VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT;
|
|
}
|
|
}
|
|
|
|
return VK_FORMAT_ASTC_6x6_SRGB_BLOCK; // Default is 6x6 sRGB image
|
|
}
|
|
|
|
/**
|
|
* @memberof ktxTexture
|
|
* @internal
|
|
* @ingroup writer
|
|
* @~English
|
|
* @brief Creates valid ASTC encoder swizzle from string.
|
|
*
|
|
* @return Valid astcenc_swizzle from string
|
|
*/
|
|
static astcenc_swizzle
|
|
astcSwizzle(const ktxAstcParams ¶ms) {
|
|
|
|
astcenc_swizzle swizzle{ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A};
|
|
|
|
std::vector<astcenc_swz*> swizzle_array{&swizzle.r, &swizzle.g, &swizzle.b, &swizzle.a};
|
|
// For historical reasons params.inputSwizzle[0] == '\0' is interpreted to mean no
|
|
// swizzle. The docs says it must match the regular expression /^[rgba01]{4}$/.
|
|
if (params.inputSwizzle[0] != '\0') {
|
|
std::string inputSwizzle(params.inputSwizzle, sizeof(params.inputSwizzle));
|
|
// TODO: Check for RE match.
|
|
for (int i = 0; i < 4; i++) {
|
|
if (inputSwizzle[i] == 'r')
|
|
*swizzle_array[i] = ASTCENC_SWZ_R;
|
|
else if (inputSwizzle[i] == 'g')
|
|
*swizzle_array[i] = ASTCENC_SWZ_G;
|
|
else if (inputSwizzle[i] == 'b')
|
|
*swizzle_array[i] = ASTCENC_SWZ_B;
|
|
else if (inputSwizzle[i] == 'a')
|
|
*swizzle_array[i] = ASTCENC_SWZ_A;
|
|
else if (inputSwizzle[i] == '0')
|
|
*swizzle_array[i] = ASTCENC_SWZ_0;
|
|
else if (inputSwizzle[i] == '1')
|
|
*swizzle_array[i] = ASTCENC_SWZ_1;
|
|
}
|
|
} else if (params.normalMap) {
|
|
return {ASTCENC_SWZ_R, ASTCENC_SWZ_R, ASTCENC_SWZ_R, ASTCENC_SWZ_G};
|
|
}
|
|
|
|
return swizzle;
|
|
}
|
|
|
|
static void
|
|
astcBlockDimensions(ktx_uint32_t block_size,
|
|
uint32_t& block_x, uint32_t& block_y, uint32_t& block_z) {
|
|
switch (block_size) {
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4 : block_x = 4; block_y = 4; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4 : block_x = 5; block_y = 4; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5 : block_x = 5; block_y = 5; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5 : block_x = 6; block_y = 5; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6 : block_x = 6; block_y = 6; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_8x5 : block_x = 8; block_y = 5; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_8x6 : block_x = 8; block_y = 6; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_10x5 : block_x = 10; block_y = 5; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_10x6 : block_x = 10; block_y = 6; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_8x8 : block_x = 8; block_y = 8; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_10x8 : block_x = 10; block_y = 8; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_10x10 : block_x = 10; block_y = 10; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_12x10 : block_x = 12; block_y = 10; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_12x12 : block_x = 12; block_y = 12; block_z = 1; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_3x3x3 : block_x = 3; block_y = 3; block_z = 3; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_4x3x3 : block_x = 4; block_y = 3; block_z = 3; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x3 : block_x = 4; block_y = 4; block_z = 3; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x4 : block_x = 4; block_y = 4; block_z = 4; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4x4 : block_x = 5; block_y = 4; block_z = 4; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x4 : block_x = 5; block_y = 5; block_z = 4; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x5 : block_x = 5; block_y = 5; block_z = 5; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5x5 : block_x = 6; block_y = 5; block_z = 5; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x5 : block_x = 6; block_y = 6; block_z = 5; break;
|
|
case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x6 : block_x = 6; block_y = 6; block_z = 6; break;
|
|
default:
|
|
block_x = 6; block_y = 6; block_z = 1; break;
|
|
}
|
|
}
|
|
|
|
static float
|
|
astcQuality(ktx_uint32_t quality_level) {
|
|
switch (quality_level) {
|
|
case KTX_PACK_ASTC_QUALITY_LEVEL_FASTEST: return ASTCENC_PRE_FASTEST;
|
|
case KTX_PACK_ASTC_QUALITY_LEVEL_FAST: return ASTCENC_PRE_FAST;
|
|
case KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM: return ASTCENC_PRE_MEDIUM;
|
|
case KTX_PACK_ASTC_QUALITY_LEVEL_THOROUGH: return ASTCENC_PRE_THOROUGH;
|
|
case KTX_PACK_ASTC_QUALITY_LEVEL_EXHAUSTIVE: return ASTCENC_PRE_EXHAUSTIVE;
|
|
}
|
|
|
|
return ASTCENC_PRE_MEDIUM;
|
|
}
|
|
|
|
struct CompressionWorkload {
|
|
astcenc_context* context;
|
|
astcenc_image* image;
|
|
astcenc_swizzle swizzle;
|
|
uint8_t* data_out;
|
|
size_t data_len;
|
|
astcenc_error error;
|
|
};
|
|
|
|
static void
|
|
compressionWorkloadRunner(int threadCount, int threadId, void* payload) {
|
|
(void)threadCount;
|
|
|
|
CompressionWorkload* work = static_cast<CompressionWorkload*>(payload);
|
|
astcenc_error error = astcenc_compress_image(
|
|
work->context, work->image, &work->swizzle,
|
|
work->data_out, work->data_len, threadId);
|
|
|
|
// This is a racy update, so which error gets returned is a random, but it
|
|
// will reliably report an error if an error occurs
|
|
if (error != ASTCENC_SUCCESS) {
|
|
work->error = error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @memberof ktxTexture2
|
|
* @ingroup writer
|
|
* @~English
|
|
* @brief Encode and compress a ktx texture with uncompressed images to astc.
|
|
*
|
|
* The images are encoded to ASTC block-compressed format. The encoded images
|
|
* replace the original images and the texture's fields including the DFD are
|
|
* modified to reflect the new state.
|
|
*
|
|
* Such textures can be directly uploaded to a GPU via a graphics API.
|
|
*
|
|
* @param[in] This pointer to the ktxTexture2 object of interest.
|
|
* @param[in] params pointer to ASTC params object.
|
|
*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The texture's images are supercompressed.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The texture's images are in a block compressed
|
|
* format.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The texture image's format is a packed format
|
|
* (e.g. RGB565).
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The texture image format's component size is not
|
|
* 8-bits.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The texture's images are 1D. Only 2D images can
|
|
* be supercompressed.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* Transfer function of @c This is not sRGB or Linear.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* @c params->mode is HDR but transfer function
|
|
* of @c This is sRGB.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* ASTC encoder failed to compress image.
|
|
* Possibly due to incorrect floating point
|
|
* compilation settings. Should not happen
|
|
* in release package.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* This->generateMipmaps is set.
|
|
* @exception KTX_OUT_OF_MEMORY Not enough memory to carry out compression.
|
|
* @exception KTX_UNSUPPORTED_FEATURE
|
|
* @c params->mode is HDR mode which is not
|
|
* yet implemented.
|
|
* @exception KTX_UNSUPPORTED_FEATURE
|
|
* ASTC encoder not compiled with enough
|
|
* capacity for requested block size. Should
|
|
* not happen in release package.
|
|
*/
|
|
extern "C" KTX_error_code
|
|
ktxTexture2_CompressAstcEx(ktxTexture2* This, ktxAstcParams* params) {
|
|
assert(This->classId == ktxTexture2_c && "Only support ktx2 ASTC.");
|
|
|
|
ktx_error_code_e result;
|
|
ktx_pack_astc_encoder_mode_e mode;
|
|
|
|
if (!params)
|
|
return KTX_INVALID_VALUE;
|
|
|
|
if (params->structSize != sizeof(struct ktxAstcParams))
|
|
return KTX_INVALID_VALUE;
|
|
|
|
if (This->generateMipmaps)
|
|
return KTX_INVALID_OPERATION;
|
|
|
|
if (This->supercompressionScheme != KTX_SS_NONE)
|
|
return KTX_INVALID_OPERATION; // Can't apply multiple schemes.
|
|
|
|
if (This->isCompressed)
|
|
return KTX_INVALID_OPERATION; // Only non-block compressed formats
|
|
// can be encoded into an ASTC format.
|
|
|
|
if (This->_protected->_formatSize.flags & KTX_FORMAT_SIZE_PACKED_BIT)
|
|
return KTX_INVALID_OPERATION;
|
|
|
|
// Basic descriptor block begins after the total size field.
|
|
const uint32_t* BDB = This->pDfd+1;
|
|
|
|
uint32_t num_components, component_size;
|
|
getDFDComponentInfoUnpacked(This->pDfd, &num_components, &component_size);
|
|
ktx_uint32_t transfer = KHR_DFDVAL(BDB, TRANSFER);
|
|
bool sRGB = transfer == KHR_DF_TRANSFER_SRGB;
|
|
|
|
if (component_size != 1)
|
|
return KTX_UNSUPPORTED_FEATURE; // Can only deal with 8-bit components at the moment
|
|
if (params->mode == KTX_PACK_ASTC_ENCODER_MODE_DEFAULT) {
|
|
if (component_size == 1 || sRGB)
|
|
mode = KTX_PACK_ASTC_ENCODER_MODE_LDR;
|
|
else
|
|
mode = KTX_PACK_ASTC_ENCODER_MODE_HDR;
|
|
} else {
|
|
mode = static_cast<ktx_pack_astc_encoder_mode_e>(params->mode);
|
|
}
|
|
|
|
if (mode == KTX_PACK_ASTC_ENCODER_MODE_HDR && sRGB)
|
|
return KTX_INVALID_OPERATION;
|
|
|
|
if (!(sRGB || transfer == KHR_DF_TRANSFER_LINEAR))
|
|
return KTX_INVALID_OPERATION;
|
|
|
|
if (This->pData == NULL) {
|
|
result = ktxTexture2_LoadImageData((ktxTexture2*)This, nullptr, 0);
|
|
|
|
if (result != KTX_SUCCESS)
|
|
return result;
|
|
}
|
|
|
|
ktx_uint32_t threadCount = params->threadCount;
|
|
if (threadCount < 1)
|
|
threadCount = 1;
|
|
|
|
VkFormat vkFormat = astcVkFormat(params->blockDimension, sRGB);
|
|
|
|
// This->numLevels = 0 not allowed for block compressed formats
|
|
// But just in case make sure its not zero
|
|
This->numLevels = MAX(1, This->numLevels);
|
|
|
|
// 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;
|
|
|
|
ktxTexture2* prototype;
|
|
result = ktxTexture2_Create(&createInfo, KTX_TEXTURE_CREATE_ALLOC_STORAGE,
|
|
&prototype);
|
|
|
|
if (result != KTX_SUCCESS) {
|
|
assert(result == KTX_OUT_OF_MEMORY && "Out of memory allocating texture.");
|
|
return result;
|
|
}
|
|
|
|
astcenc_profile profile{ASTCENC_PRF_LDR_SRGB};
|
|
|
|
astcenc_swizzle swizzle{ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A};
|
|
|
|
uint32_t block_size_x{6};
|
|
uint32_t block_size_y{6};
|
|
uint32_t block_size_z{1};
|
|
float quality{ASTCENC_PRE_MEDIUM};
|
|
uint32_t flags{params->normalMap ? ASTCENC_FLG_MAP_NORMAL : 0};
|
|
|
|
astcBlockDimensions(params->blockDimension,
|
|
block_size_x, block_size_y, block_size_z);
|
|
quality = astcQuality(params->qualityLevel);
|
|
profile = astcProfile(sRGB, mode);
|
|
swizzle = astcSwizzle(*params);
|
|
|
|
if(params->perceptual)
|
|
flags |= ASTCENC_FLG_USE_PERCEPTUAL;
|
|
|
|
astcenc_config astc_config;
|
|
astcenc_context *astc_context;
|
|
astcenc_error astc_error = astcenc_config_init(profile,
|
|
block_size_x, block_size_y, block_size_z,
|
|
quality, flags,
|
|
&astc_config);
|
|
|
|
if (astc_error != ASTCENC_SUCCESS)
|
|
return mapAstcError(astc_error);
|
|
|
|
astc_error = astcenc_context_alloc(&astc_config, threadCount,
|
|
&astc_context);
|
|
|
|
if (astc_error != ASTCENC_SUCCESS)
|
|
return mapAstcError(astc_error);
|
|
|
|
// Walk in reverse on levels so we don't have to do this later
|
|
assert(prototype->dataSize && "Prototype texture size not initialized.\n");
|
|
|
|
if (!prototype->pData) {
|
|
return KTX_OUT_OF_MEMORY;
|
|
}
|
|
|
|
uint8_t* buffer_out = prototype->pData;
|
|
|
|
for (int32_t level = This->numLevels - 1; level >= 0; level--) {
|
|
uint32_t width = MAX(1, This->baseWidth >> level);
|
|
uint32_t height = MAX(1, This->baseHeight >> level);
|
|
uint32_t depth = MAX(1, This->baseDepth >> level);
|
|
ktx_size_t levelImageSizeIn = 0;
|
|
ktx_size_t levelImageSizeOut = 0;
|
|
ktx_uint32_t levelImages = 0;
|
|
|
|
levelImages = 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);
|
|
ktx_size_t offset = ktxTexture2_levelDataOffset(This, level);
|
|
|
|
for (uint32_t image = 0; image < levelImages; image++) {
|
|
astcenc_image *input_image = nullptr;
|
|
if (num_components == 1)
|
|
input_image = unorm8x1ArrayToImage(This->pData + offset,
|
|
width, height);
|
|
else if (num_components == 2)
|
|
input_image = unorm8x2ArrayToImage(This->pData + offset,
|
|
width, height);
|
|
else if (num_components == 3)
|
|
input_image = unorm8x3ArrayToImage(This->pData + offset,
|
|
width, height);
|
|
else // assume (num_components == 4)
|
|
input_image = unorm8x4ArrayToImage(This->pData + offset,
|
|
width, height);
|
|
|
|
assert(input_image);
|
|
|
|
CompressionWorkload work;
|
|
work.context = astc_context;
|
|
work.image = input_image;
|
|
work.swizzle = swizzle;
|
|
work.data_out = buffer_out;
|
|
work.data_len = levelImageSizeOut;
|
|
work.error = ASTCENC_SUCCESS;
|
|
|
|
launchThreads(threadCount, compressionWorkloadRunner, &work);
|
|
|
|
buffer_out += levelImageSizeOut;
|
|
|
|
// Reset ASTC context for next image
|
|
astcenc_compress_reset(astc_context);
|
|
offset += levelImageSizeIn;
|
|
|
|
imageFree(input_image);
|
|
|
|
if (work.error != ASTCENC_SUCCESS) {
|
|
//std::cout << "ASTC compressor failed\n" <<
|
|
// astcenc_get_error_string(work.error) << std::endl;
|
|
|
|
astcenc_context_free(astc_context);
|
|
return mapAstcError(work.error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We are done with astcencoder
|
|
astcenc_context_free(astc_context);
|
|
|
|
assert(KHR_DFDVAL(prototype->pDfd+1, MODEL) == KHR_DF_MODEL_ASTC
|
|
&& "Invalid dfd generated for ASTC image\n");
|
|
assert((transfer == KHR_DF_TRANSFER_SRGB
|
|
? KHR_DFDVAL(prototype->pDfd+1, TRANSFER) == KHR_DF_TRANSFER_SRGB &&
|
|
KHR_DFDVAL(prototype->pDfd+1, PRIMARIES) == KHR_DF_PRIMARIES_SRGB
|
|
: true) && "Not a valid sRGB image\n");
|
|
|
|
// Fix up the current (This) texture
|
|
#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)
|
|
|
|
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;
|
|
This->_private->_requiredLevelAlignment = protoPriv._requiredLevelAlignment;
|
|
// Copy the levelIndex from the prototype to This.
|
|
memcpy(This->_private->_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 KTX_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @memberof ktxTexture2
|
|
* @ingroup writer
|
|
* @~English
|
|
* @brief Encode and compress a ktx texture with uncompressed images to astc.
|
|
*
|
|
* The images are either encoded to ASTC block-compressed format. The encoded images
|
|
* replace the original images and the texture's fields including the DFD are modified to reflect the new
|
|
* state.
|
|
*
|
|
* Such textures can be directly uploaded to a GPU via a graphics API.
|
|
*
|
|
* @param[in] This pointer to the ktxTexture2 object of interest.
|
|
* @param[in] quality Compression quality, a value from 0 - 100.
|
|
Higher=higher quality/slower speed.
|
|
Lower=lower quality/faster speed.
|
|
Negative values for quality are considered > 100.
|
|
*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The texture's images are supercompressed.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The texture's image are in a block compressed
|
|
* format.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The texture image's format is a packed format
|
|
* (e.g. RGB565).
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The texture image format's component size is not 8-bits.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The texture's images are 1D. Only 2D images can
|
|
* be supercompressed.
|
|
* @exception KTX_OUT_OF_MEMORY Not enough memory to carry out supercompression.
|
|
*/
|
|
extern "C" KTX_error_code
|
|
ktxTexture2_CompressAstc(ktxTexture2* This, ktx_uint32_t quality) {
|
|
ktxAstcParams params = astcDefaultOptions();
|
|
|
|
if (quality >= KTX_PACK_ASTC_QUALITY_LEVEL_FASTEST)
|
|
params.qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_FASTEST;
|
|
|
|
if (quality >= KTX_PACK_ASTC_QUALITY_LEVEL_FAST)
|
|
params.qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_FAST;
|
|
|
|
if (quality >= KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM)
|
|
params.qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_MEDIUM;
|
|
|
|
if (quality >= KTX_PACK_ASTC_QUALITY_LEVEL_THOROUGH)
|
|
params.qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_THOROUGH;
|
|
|
|
if (quality >= KTX_PACK_ASTC_QUALITY_LEVEL_EXHAUSTIVE)
|
|
params.qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_EXHAUSTIVE;
|
|
|
|
return ktxTexture2_CompressAstcEx(This, ¶ms);
|
|
}
|
|
#else
|
|
extern "C" KTX_error_code
|
|
ktxTexture2_CompressAstcEx(ktxTexture2*, ktxAstcParams*) {
|
|
return KTX_INVALID_OPERATION;
|
|
}
|
|
|
|
extern "C" KTX_error_code
|
|
ktxTexture2_CompressAstc(ktxTexture2*, ktx_uint32_t) {
|
|
return KTX_INVALID_OPERATION;
|
|
}
|
|
#endif /* KTX_FEATURE_WRITE */
|
|
|
|
|