Skip to content

Commit

Permalink
Improve Astc & BasisU normal map support (KhronosGroup#493)
Browse files Browse the repository at this point in the history
normalMap in ktxAstcParams and ktxBasisParams (`--normal_mode` in `toktx`), causes the
encoders to split the R & G components of the input into the RGB & alpha channels of the
encoded texture and to apply encoder-specific settings that improve quality for normal
maps.

Add `--normalize`  option to `toktx.

Add tests for normalMap and normalize functionality.

Deprecate `separateRGToRGB_A` and remove related code. Fixes KhronosGroup#455.

Co-authored-by: Wasim Abbas <[email protected]>
  • Loading branch information
wasimabbas-arm and abbaswasim authored Feb 17, 2022
1 parent 90fa441 commit bc08e30
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 102 deletions.
17 changes: 7 additions & 10 deletions include/ktx.h
Original file line number Diff line number Diff line change
Expand Up @@ -1291,22 +1291,19 @@ typedef struct ktxBasisParams {
This will override the value chosen by @c qualityLevel.
*/
char inputSwizzle[4];
/*!< A swizzle to apply before encoding. It must match the regular
/*!< A swizzle to apply before encoding. It must match the regular
expression /^[rgba01]{4}$/. If both this and preSwizzle
are specified ktxTexture_CompressBasisEx will raise
KTX_INVALID_OPERATION. */

KTX_INVALID_OPERATION.
*/
ktx_bool_t normalMap;
/*!< Tunes codec parameters for better quality on normal maps (no
selector RDO, no endpoint RDO). Only valid for linear textures.
selector RDO, no endpoint RDO) and sets the texture's DFD appropriately.
Only valid for linear textures.
*/
ktx_bool_t separateRGToRGB_A;
/*!< Separates the input R and G channels to RGB and A (for tangent
space XY normal maps). Equivalent to @c inputSwizzle "rrrg".
Separation is the default for 2 component textures. If both this
and inputSwizzle are set, the latter wins therefore set
@c inputSwizzle to change the default for 2 component
textures.
/*!< @deprecated. This was and is a no-op. 2-component inputs have always been
automatically separated using an "rrrg" inputSwizzle. @sa inputSwizzle and normalMode.
*/
ktx_bool_t preSwizzle;
/*!< If the texture has @c KTXswizzle metadata, apply it before
Expand Down
50 changes: 23 additions & 27 deletions lib/astc_encode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,6 @@ static ktxAstcParams
astcDefaultOptions() {
ktxAstcParams params{};
params.structSize = sizeof(params);
params.verbose = false;
params.threadCount = 1;
params.blockDimension = KTX_PACK_ASTC_BLOCK_DIMENSION_6x6;
params.mode = KTX_PACK_ASTC_ENCODER_MODE_LDR;
Expand Down Expand Up @@ -333,20 +332,27 @@ astcSwizzle(const ktxAstcParams &params) {
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 (int i = 0; i < 4; i++) {
if (params.inputSwizzle[i] == 'r')
*swizzle_array[i] = ASTCENC_SWZ_R;
else if (params.inputSwizzle[i] == 'g')
*swizzle_array[i] = ASTCENC_SWZ_G;
else if (params.inputSwizzle[i] == 'b')
*swizzle_array[i] = ASTCENC_SWZ_B;
else if (params.inputSwizzle[i] == 'a')
*swizzle_array[i] = ASTCENC_SWZ_A;
else if (params.inputSwizzle[i] == '0')
*swizzle_array[i] = ASTCENC_SWZ_0;
else if (params.inputSwizzle[i] == '1')
*swizzle_array[i] = ASTCENC_SWZ_1;
std::string inputSwizzle = params.inputSwizzle;

if (inputSwizzle.size() > 0) {
assert(inputSwizzle.size() == 4 && "InputSwizzle is invalid.");

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;
Expand Down Expand Up @@ -619,18 +625,14 @@ ktxTexture2_CompressAstcEx(ktxTexture2* This, ktxAstcParams* params) {
quality, flags,
&astc_config);

if (astc_error != ASTCENC_SUCCESS) {
std::cout << "ASTC config init failed with error " << astcenc_get_error_string(astc_error) << std::endl;
if (astc_error != ASTCENC_SUCCESS)
return KTX_INVALID_OPERATION;
}

astc_error = astcenc_context_alloc(&astc_config, threadCount,
&astc_context);

if (astc_error != ASTCENC_SUCCESS) {
std::cout << "ASTC context alloc failed with error " << astcenc_get_error_string(astc_error) << std::endl;
if (astc_error != ASTCENC_SUCCESS)
return KTX_INVALID_OPERATION;
}

// Walk in reverse on levels so we don't have to do this later
assert(prototype->dataSize && "Prototype texture size not initialized.\n");
Expand All @@ -657,12 +659,6 @@ ktxTexture2_CompressAstcEx(ktxTexture2* This, ktxAstcParams* params) {
ktx_size_t offset = ktxTexture2_levelDataOffset(This, level);

for (uint32_t image = 0; image < levelImages; image++) {
if (params->verbose)
std::cout << "ASTC compressor: compressing image " <<
(This->numLevels - level - 1) * levelImages + image + 1
<< " of " << This->numLevels * levelImages
<< std::endl;

astcenc_image *input_image = nullptr;
if (num_components == 1)
input_image = unorm8x1ArrayToImage(This->pData + offset,
Expand Down
96 changes: 59 additions & 37 deletions lib/basis_encode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,8 @@ swizzle_rg_to_rgb_a(uint8_t* rgbadst, uint8_t* rgsrc, ktx_size_t image_size,
static KTX_error_code
ktxTexture2_rewriteDfd4BasisLzETC1S(ktxTexture2* This,
alpha_content_e alphaContent,
bool isLuminance)
bool isLuminance,
swizzle_e swizzle[4])
{
uint32_t* cdfd = This->pDfd;
uint32_t* cbdb = cdfd + 1;
Expand Down Expand Up @@ -270,7 +271,9 @@ ktxTexture2_rewriteDfd4BasisLzETC1S(ktxTexture2* This,
uint16_t channelId, bitOffset;
if (sample == 0) {
bitOffset = 0;
if (getDFDNumComponents(cdfd) < 3 && !isLuminance)

if (!isLuminance && swizzle
&& swizzle[0] == swizzle[1] && swizzle[1] == swizzle[2])
channelId = KHR_DF_CHANNEL_ETC1S_RRR;
else
channelId = KHR_DF_CHANNEL_ETC1S_RGB;
Expand Down Expand Up @@ -300,7 +303,9 @@ ktxTexture2_rewriteDfd4BasisLzETC1S(ktxTexture2* This,

static KTX_error_code
ktxTexture2_rewriteDfd4Uastc(ktxTexture2* This,
alpha_content_e alphaContent)
alpha_content_e alphaContent,
bool isLuminance,
swizzle_e swizzle[4])
{
uint32_t* cdfd = This->pDfd;
uint32_t* cbdb = cdfd + 1;
Expand Down Expand Up @@ -335,8 +340,11 @@ ktxTexture2_rewriteDfd4Uastc(ktxTexture2* This,
if (alphaContent == eAlpha) {
channelId = KHR_DF_CHANNEL_UASTC_RGBA;
} else if (alphaContent == eGreen) {
channelId = KHR_DF_CHANNEL_UASTC_RRRG;
} else if (swizzle && swizzle[2] == 0 && swizzle[3] == 1) {
channelId = KHR_DF_CHANNEL_UASTC_RG;
} else if (getDFDNumComponents(cdfd) == 1) {
} else if (!isLuminance && swizzle
&& swizzle[0] == swizzle[1] && swizzle[1] == swizzle[2]) {
channelId = KHR_DF_CHANNEL_UASTC_RRR;
} else {
channelId = KHR_DF_CHANNEL_UASTC_RGB;
Expand Down Expand Up @@ -389,8 +397,8 @@ static bool basisuEncoderInitialized = false;
* @exception KTX_INVALID_OPERATION
* The texture image format's component size is not 8-bits.
* @exception KTX_INVALID_OPERATION
* @c separateRGToRGB_A is specified but the texture
* has only 1 component.
* @c normalMode is specified but the texture has only
* one component.
* @exception KTX_INVALID_OPERATION
* Both preSwizzle and and inputSwizzle are specified
* in @a params.
Expand Down Expand Up @@ -426,8 +434,8 @@ ktxTexture2_CompressBasisEx(ktxTexture2* This, ktxBasisParams* params)
if (component_size != 1)
return KTX_INVALID_OPERATION; // Basis must have 8-bit components.

if (params->separateRGToRGB_A && num_components == 1)
return KTX_INVALID_OPERATION;
if (num_components == 1 && params->normalMap)
return KTX_INVALID_OPERATION; // Not enough components.

if (This->pData == NULL) {
result = ktxTexture2_LoadImageData(This, NULL, 0);
Expand Down Expand Up @@ -465,29 +473,21 @@ ktxTexture2_CompressBasisEx(ktxTexture2* This, ktxBasisParams* params)
cparams.m_source_images.resize(num_images);
basisu::vector<image>::iterator iit = cparams.m_source_images.begin();

swizzle_e meta_mapping[4] = {};
// Since we have to copy the data into the vector image anyway do the
// separation here to avoid another loop over the image inside
// basis_compressor.
swizzle_e rg_to_rgba_mapping_etc1s[4] = { R, R, R, G };
swizzle_e rg_to_rgba_mapping_uastc[4] = { R, G, ZERO, ONE };
swizzle_e normal_map_mapping[4] = { R, R, R, G };
swizzle_e r_to_rgba_mapping[4] = { R, R, R, ONE };
swizzle_e meta_mapping[4] = {};
// All the above declarations need to stay here so they remain in scope
// until after the pixel copy loop as comp_mapping will ultimately point
// to one of them.
swizzle_e* comp_mapping = 0;

alpha_content_e alphaContent = eNone;
bool isLuminance = false;
if (num_components == 1) {
comp_mapping = r_to_rgba_mapping;
} else if (num_components == 2) {
if (params->uastc)
comp_mapping = rg_to_rgba_mapping_uastc;
else {
comp_mapping = rg_to_rgba_mapping_etc1s;
alphaContent = eGreen;
}
} else if (num_components == 4) {
alphaContent = eAlpha;
}

std::string swizzleString = params->inputSwizzle;
if (params->preSwizzle) {
Expand All @@ -512,7 +512,24 @@ ktxTexture2_CompressBasisEx(ktxTexture2* This, ktxBasisParams* params)
}
}

if (swizzleString.size() > 0) {
if (swizzleString.size() == 0) {
// Set appropriate default swizzle
if (params->normalMap) {
comp_mapping = normal_map_mapping;
alphaContent = eGreen;
} else if (num_components == 1) {
comp_mapping = r_to_rgba_mapping;
} else if (num_components == 2) {
if (params->uastc)
comp_mapping = rg_to_rgba_mapping_uastc;
else {
comp_mapping = rg_to_rgba_mapping_etc1s;
alphaContent = eGreen;
}
} else if (num_components == 4) {
alphaContent = eAlpha;
}
} else {
// Only set comp_mapping for cases we can't shortcut.
// If num_components < 3 we always swizzle so no shortcut there.
if (num_components < 3
Expand All @@ -531,19 +548,21 @@ ktxTexture2_CompressBasisEx(ktxTexture2* This, ktxBasisParams* params)
comp_mapping = meta_mapping;
}

// An incoming swizzle of RRR1 or RRRG is assumed to be for a
// luminance texture. Set isLuminance to distinguish from
// an identical swizzle generated internally for R & RG cases.
int i;
for (i = 0; i < 3; i++) {
if (meta_mapping[i] != r_to_rgba_mapping[i])
break;
}
if (i == 3) {
isLuminance = true;
}
if (meta_mapping[3] != ONE) {
alphaContent = eAlpha;
if (!params->normalMap) {
// An incoming swizzle of RRR1 or RRRG is assumed to be for a
// luminance texture. Set isLuminance so we can later distinguish
// this from the identical swizzle used for normal maps.
// cases for ETC1S.
if (meta_mapping[0] == meta_mapping[1]
&& meta_mapping[1] == meta_mapping[2]) {
// Same component in r, g & b
isLuminance = true;
}
if (meta_mapping[3] != ONE) {
alphaContent = eAlpha;
}
} else {
alphaContent = eGreen;
}
}

Expand Down Expand Up @@ -972,7 +991,9 @@ ktxTexture2_CompressBasisEx(ktxTexture2* This, ktxBasisParams* params)
// Delayed modifying texture until here so it's after points of
// possible failure.
if (params->uastc) {
result = ktxTexture2_rewriteDfd4Uastc(This, alphaContent);
result = ktxTexture2_rewriteDfd4Uastc(This, alphaContent,
isLuminance,
comp_mapping);
if (result != KTX_SUCCESS) goto cleanup;

// Reflect this in the formatSize
Expand All @@ -981,7 +1002,8 @@ ktxTexture2_CompressBasisEx(ktxTexture2* This, ktxBasisParams* params)
priv._requiredLevelAlignment = 4 * 4;
} else {
result = ktxTexture2_rewriteDfd4BasisLzETC1S(This, alphaContent,
isLuminance);
isLuminance,
comp_mapping);
if (result != KTX_SUCCESS) goto cleanup;

This->supercompressionScheme = KTX_SS_BASIS_LZ;
Expand Down
Loading

0 comments on commit bc08e30

Please sign in to comment.