File size: 41,677 Bytes
8b83ace | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 | /* -*- 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
* @~English
*
* @brief Functions for supercompressing a texture with Basis Universal.
*
* This is where two worlds collide. Ugly!
*
* @author Mark Callow, github.com/MarkCallow
*/
#include <inttypes.h>
#include <stdlib.h>
#include <zstd.h>
#include <KHR/khr_df.h>
#include "ktx.h"
#include "ktxint.h"
#include "texture2.h"
#include "vkformat_enum.h"
#include "vk_format.h"
#include "basis_sgd.h"
#if (EMSCRIPTEN)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#endif
#include "basisu/encoder/basisu_comp.h"
#if (EMSCRIPTEN)
#pragma clang diagnostic pop
#endif
#include "basisu/transcoder/basisu_file_headers.h"
#include "basisu/transcoder/basisu_transcoder.h"
#include "dfdutils/dfd.h"
using namespace basisu;
using namespace basist;
typedef struct ktxBasisParamsV1 {
ktx_uint32_t structSize;
ktx_uint32_t threadCount;
ktx_uint32_t compressionLevel;
ktx_uint32_t qualityLevel;
ktx_uint32_t maxEndpoints;
float endpointRDOThreshold;
ktx_uint32_t maxSelectors;
float selectorRDOThreshold;
ktx_bool_t normalMap;
ktx_bool_t separateRGToRGB_A;
ktx_bool_t preSwizzle;
ktx_bool_t noEndpointRDO;
ktx_bool_t noSelectorRDO;
} ktxBasisParamsV1;
enum swizzle_e {
R = 1,
G = 2,
B = 3,
A = 4,
ZERO = 5,
ONE = 6,
};
typedef void
(* PFNBUCOPYCB)(uint8_t* rgbadst, uint8_t* rgbasrc, uint32_t src_len,
ktx_size_t image_size, swizzle_e swizzle[4]);
// All callbacks expect source images to have no row padding and expect
// component size to be 8 bits.
static void
copy_rgba_to_rgba(uint8_t* rgbadst, uint8_t* rgbasrc, uint32_t,
ktx_size_t image_size, swizzle_e[4])
{
memcpy(rgbadst, rgbasrc, image_size);
}
// Copy rgb to rgba. No swizzle.
static void
copy_rgb_to_rgba(uint8_t* rgbadst, uint8_t* rgbsrc, uint32_t,
ktx_size_t image_size, swizzle_e[4])
{
for (ktx_size_t i = 0; i < image_size; i += 3) {
memcpy(rgbadst, rgbsrc, 3);
rgbadst[3] = 0xff; // Convince Basis there is no alpha.
rgbadst += 4; rgbsrc += 3;
}
}
// This is not static only so the unit tests can access it.
void
swizzle_to_rgba(uint8_t* rgbadst, uint8_t* rgbasrc, uint32_t src_len,
ktx_size_t image_size, swizzle_e swizzle[4])
{
for (ktx_size_t i = 0; i < image_size; i += src_len) {
for (uint32_t c = 0; c < 4; c++) {
switch (swizzle[c]) {
case R:
rgbadst[c] = rgbasrc[0];
break;
case G:
rgbadst[c] = rgbasrc[1];
break;
case B:
rgbadst[c] = rgbasrc[2];
break;
case A:
rgbadst[c] = rgbasrc[3];
break;
case ZERO:
rgbadst[c] = 0x00;
break;
case ONE:
rgbadst[c] = 0xff;
break;
default:
assert(false);
}
}
rgbadst +=4; rgbasrc += src_len;
}
}
#if 0
static void
swizzle_rgba_to_rgba(uint8_t* rgbadst, uint8_t* rgbasrc, ktx_size_t image_size,
swizzle_e swizzle[4])
{
for (ktx_size_t i = 0; i < image_size; i += 4) {
for (uint32_t c = 0; c < 4; c++) {
switch (swizzle[c]) {
case 0:
rgbadst[c] = rgbasrc[0];
break;
case 1:
rgbadst[c] = rgbasrc[1];
break;
case 2:
rgbadst[c] = rgbasrc[2];
break;
case 3:
rgbadst[c] = rgbasrc[3];
break;
case 4:
rgbadst[c] = 0x00;
break;
case 5:
rgbadst[i+c] = 0xff;
break;
default:
assert(false);
}
}
rgbadst +=4; rgbasrc += 4;
}
}
static void
swizzle_rgb_to_rgba(uint8_t* rgbadst, uint8_t* rgbsrc, ktx_size_t image_size,
swizzle_e swizzle[4])
{
for (ktx_size_t i = 0; i < image_size; i += 3) {
for (uint32_t c = 0; c < 3; c++) {
switch (swizzle[c]) {
case 0:
rgbadst[c] = rgbsrc[0];
break;
case 1:
rgbadst[c] = rgbsrc[i];
break;
case 2:
rgbadst[c] = rgbsrc[2];
break;
case 3:
assert(false); // Shouldn't happen for an RGB texture.
break;
case 4:
rgbadst[c] = 0x00;
break;
case 5:
rgbadst[c] = 0xff;
break;
default:
assert(false);
}
}
rgbadst +=4; rgbsrc += 3;
}
}
static void
swizzle_rg_to_rgb_a(uint8_t* rgbadst, uint8_t* rgsrc, ktx_size_t image_size,
swizzle_e swizzle[4])
{
for (ktx_size_t i = 0; i < image_size; i += 2) {
for (uint32_t c = 0; c < 2; c++) {
switch (swizzle[c]) {
case 0:
rgbadst[c] = rgsrc[0];
break;
case 1:
rgbadst[c] = rgsrc[1];
break;
case 2:
assert(false); // Shouldn't happen for an RG texture.
break;
case 3:
assert(false); // Shouldn't happen for an RG texture.
break;
case 4:
rgbadst[c] = 0x00;
break;
case 5:
rgbadst[c] = 0xff;
break;
default:
assert(false);
}
}
}
}
#endif
// Rewrite DFD changing it to unsized. Account for the Basis compressor
// not including an all 1's alpha channel, which would have been removed before
// encoding and supercompression, by using hasAlpha.
static KTX_error_code
ktxTexture2_rewriteDfd4BasisLzETC1S(ktxTexture2* This,
alpha_content_e alphaContent,
bool isLuminance,
swizzle_e swizzle[4])
{
uint32_t* cdfd = This->pDfd;
uint32_t* cbdb = cdfd + 1;
uint32_t newSampleCount = alphaContent != eNone ? 2 : 1;
uint32_t ndbSize = KHR_DF_WORD_SAMPLESTART
+ newSampleCount * KHR_DF_WORD_SAMPLEWORDS;
ndbSize *= sizeof(uint32_t);
uint32_t ndfdSize = ndbSize + 1 * sizeof(uint32_t);
uint32_t* ndfd = (uint32_t *)malloc(ndfdSize);
uint32_t* nbdb = ndfd + 1;
if (!ndfd)
return KTX_OUT_OF_MEMORY;
*ndfd = ndfdSize;
KHR_DFDSETVAL(nbdb, VENDORID, KHR_DF_VENDORID_KHRONOS);
KHR_DFDSETVAL(nbdb, DESCRIPTORTYPE, KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT);
KHR_DFDSETVAL(nbdb, VERSIONNUMBER, KHR_DF_VERSIONNUMBER_LATEST);
KHR_DFDSETVAL(nbdb, DESCRIPTORBLOCKSIZE, ndbSize);
KHR_DFDSETVAL(nbdb, MODEL, KHR_DF_MODEL_ETC1S);
KHR_DFDSETVAL(nbdb, PRIMARIES, KHR_DFDVAL(cbdb, PRIMARIES));
KHR_DFDSETVAL(nbdb, TRANSFER, KHR_DFDVAL(cbdb, TRANSFER));
KHR_DFDSETVAL(nbdb, FLAGS, KHR_DFDVAL(cbdb, FLAGS));
nbdb[KHR_DF_WORD_TEXELBLOCKDIMENSION0] =
3 | (3 << KHR_DF_SHIFT_TEXELBLOCKDIMENSION1);
nbdb[KHR_DF_WORD_BYTESPLANE0] = 0; /* bytesPlane3..0 = 0 */
KHR_DFDSETVAL(nbdb, BYTESPLANE0, 8);
if (alphaContent != eNone)
KHR_DFDSETVAL(nbdb, BYTESPLANE1, 8);
nbdb[KHR_DF_WORD_BYTESPLANE4] = 0; /* bytesPlane7..5 = 0 */
for (uint32_t sample = 0; sample < newSampleCount; sample++) {
uint16_t channelId, bitOffset;
if (sample == 0) {
bitOffset = 0;
if (!isLuminance && swizzle
&& swizzle[0] == swizzle[1] && swizzle[1] == swizzle[2])
channelId = KHR_DF_CHANNEL_ETC1S_RRR;
else
channelId = KHR_DF_CHANNEL_ETC1S_RGB;
} else {
assert(sample == 1 && alphaContent != eNone);
bitOffset = 64;
if (alphaContent == eAlpha)
channelId = KHR_DF_CHANNEL_ETC1S_AAA;
else if (alphaContent == eGreen)
channelId = KHR_DF_CHANNEL_ETC1S_GGG;
else // This is just to quiet a compiler warning.
channelId = KHR_DF_CHANNEL_ETC1S_RGB;
}
KHR_DFDSETSVAL(nbdb, sample, CHANNELID, channelId);
KHR_DFDSETSVAL(nbdb, sample, QUALIFIERS, 0);
KHR_DFDSETSVAL(nbdb, sample, SAMPLEPOSITION_ALL, 0);
KHR_DFDSETSVAL(nbdb, sample, BITOFFSET, bitOffset);
KHR_DFDSETSVAL(nbdb, sample, BITLENGTH, 63);
KHR_DFDSETSVAL(nbdb, sample, SAMPLELOWER, 0);
KHR_DFDSETSVAL(nbdb, sample, SAMPLEUPPER, UINT32_MAX);
}
This->pDfd = ndfd;
free(cdfd);
return KTX_SUCCESS;
}
static KTX_error_code
ktxTexture2_rewriteDfd4Uastc(ktxTexture2* This,
alpha_content_e alphaContent,
bool isLuminance,
swizzle_e swizzle[4])
{
uint32_t* cdfd = This->pDfd;
uint32_t* cbdb = cdfd + 1;
uint32_t ndbSize = KHR_DF_WORD_SAMPLESTART
+ 1 * KHR_DF_WORD_SAMPLEWORDS;
ndbSize *= sizeof(uint32_t);
uint32_t ndfdSize = ndbSize + 1 * sizeof(uint32_t);
uint32_t* ndfd = (uint32_t *)malloc(ndfdSize);
uint32_t* nbdb = ndfd + 1;
if (!ndfd)
return KTX_OUT_OF_MEMORY;
*ndfd = ndfdSize;
KHR_DFDSETVAL(nbdb, VENDORID, KHR_DF_VENDORID_KHRONOS);
KHR_DFDSETVAL(nbdb, DESCRIPTORTYPE, KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT);
KHR_DFDSETVAL(nbdb, VERSIONNUMBER, KHR_DF_VERSIONNUMBER_LATEST);
KHR_DFDSETVAL(nbdb, DESCRIPTORBLOCKSIZE, ndbSize);
KHR_DFDSETVAL(nbdb, MODEL, KHR_DF_MODEL_UASTC);
KHR_DFDSETVAL(nbdb, PRIMARIES, KHR_DFDVAL(cbdb, PRIMARIES));
KHR_DFDSETVAL(nbdb, TRANSFER, KHR_DFDVAL(cbdb, TRANSFER));
KHR_DFDSETVAL(nbdb, FLAGS, KHR_DFDVAL(cbdb, FLAGS));
nbdb[KHR_DF_WORD_TEXELBLOCKDIMENSION0] =
3 | (3 << KHR_DF_SHIFT_TEXELBLOCKDIMENSION1);
nbdb[KHR_DF_WORD_BYTESPLANE0] = 16; /* bytesPlane0 = 16, bytesPlane3..1 = 0 */
nbdb[KHR_DF_WORD_BYTESPLANE4] = 0; /* bytesPlane7..5 = 0 */
// Set the data for our single sample
uint16_t channelId;
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 (!isLuminance && swizzle
&& swizzle[0] == swizzle[1] && swizzle[1] == swizzle[2]) {
channelId = KHR_DF_CHANNEL_UASTC_RRR;
} else {
channelId = KHR_DF_CHANNEL_UASTC_RGB;
}
KHR_DFDSETSVAL(nbdb, 0, CHANNELID, channelId);
KHR_DFDSETSVAL(nbdb, 0, QUALIFIERS, 0);
KHR_DFDSETSVAL(nbdb, 0, SAMPLEPOSITION_ALL, 0);
KHR_DFDSETSVAL(nbdb, 0, BITOFFSET, 0);
KHR_DFDSETSVAL(nbdb, 0, BITLENGTH, 127);
KHR_DFDSETSVAL(nbdb, 0, SAMPLELOWER, 0);
KHR_DFDSETSVAL(nbdb, 0, SAMPLEUPPER, UINT32_MAX);
This->pDfd = ndfd;
free(cdfd);
return KTX_SUCCESS;
}
static bool basisuEncoderInitialized = false;
/**
* @memberof ktxTexture2
* @ingroup writer
* @~English
* @brief Encode and possibly Supercompress a KTX2 texture with uncompressed images.
*
* The images are either encoded to ETC1S block-compressed format and supercompressed
* with Basis LZ or they are encoded to UASTC block-compressed format. UASTC format is
* selected by setting the @c uastc field of @a params to @c KTX_TRUE. The encoded images
* replace the original images and the texture's fields including the DFD are modified to reflect the new
* state.
*
* Such textures must be transcoded to a desired target block compressed format
* before they can be uploaded to a GPU via a graphics API.
*
* @sa ktxTexture2_TranscodeBasis().
*
* @param[in] This pointer to the ktxTexture2 object of interest.
* @param[in] params pointer to Basis 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
* @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.
* @exception KTX_INVALID_OPERATION
* This->generateMipmaps is set.
* @exception KTX_OUT_OF_MEMORY Not enough memory to carry out compression.
*/
extern "C" KTX_error_code
ktxTexture2_CompressBasisEx(ktxTexture2* This, ktxBasisParams* params)
{
KTX_error_code result;
if (!params)
return KTX_INVALID_VALUE;
if (params->structSize != sizeof(struct ktxBasisParams))
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 a Basis 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);
if (component_size != 1)
return KTX_INVALID_OPERATION; // Basis must have 8-bit components.
if (num_components == 1 && params->normalMap)
return KTX_INVALID_OPERATION; // Not enough components.
if (This->pData == NULL) {
result = ktxTexture2_LoadImageData(This, NULL, 0);
if (result != KTX_SUCCESS)
return result;
}
if (!basisuEncoderInitialized) {
// force_serialization uses a mutex to serialize when multiple command
// queues per thread are used. We shouldn't need to worry about this.
// How to decide whether to use OpenCL?
basisu_encoder_init((BASISU_SUPPORT_OPENCL ? true : false)/*use_opencl*/
/*opencl_force_serialization = false*/);
//atexit(basisu_encoder_deinit);
basisuEncoderInitialized = true;
}
basis_compressor_params cparams;
cparams.m_read_source_images = false; // Don't read from source files.
cparams.m_write_output_basis_files = false; // Don't write output files.
cparams.m_status_output = params->verbose;
//
// Calculate number of images
//
uint32_t layersFaces = This->numLayers * This->numFaces;
uint32_t num_images = 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.
num_images += layersFaces * MAX(This->baseDepth >> (level - 1), 1);
}
//
// Copy images into compressor parameters.
//
// Darn it! m_source_images is a vector of an internal image class which
// has its own array of RGBA-only pixels. Pending modifications to the
// basisu code we'll have to copy in the images.
cparams.m_source_images.resize(num_images);
basisu::vector<image>::iterator iit = cparams.m_source_images.begin();
// 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;
std::string swizzleString = params->inputSwizzle;
if (params->preSwizzle) {
if (swizzleString.size() > 0) {
return KTX_INVALID_OPERATION;
}
ktxHashListEntry* swizzleEntry;
result = ktxHashList_FindEntry(&This->kvDataHead, KTX_SWIZZLE_KEY,
&swizzleEntry);
if (result == KTX_SUCCESS) {
ktx_uint32_t swizzleLen = 0;
char* swizzleStr = nullptr;
ktxHashListEntry_GetValue(swizzleEntry,
&swizzleLen, (void**)&swizzleStr);
// Remove the swizzle as it is no longer needed.
ktxHashList_DeleteEntry(&This->kvDataHead, swizzleEntry);
// Do it this way in case there is no NUL terminator.
swizzleString.resize(swizzleLen);
swizzleString.assign(swizzleStr, swizzleLen);
}
}
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
|| (num_components == 3 && swizzleString.compare("rgb1"))
|| (num_components == 4 && swizzleString.compare("rgba"))) {
for (int i = 0; i < 4; i++) {
switch (swizzleString[i]) {
case 'r': meta_mapping[i] = R; break;
case 'g': meta_mapping[i] = G; break;
case 'b': meta_mapping[i] = B; break;
case 'a': meta_mapping[i] = A; break;
case '0': meta_mapping[i] = ZERO; break;
case '1': meta_mapping[i] = ONE; break;
}
}
comp_mapping = meta_mapping;
}
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;
}
}
PFNBUCOPYCB copycb = copy_rgba_to_rgba; // Initialization is just to keep
// compilers happy
if (comp_mapping) {
copycb = swizzle_to_rgba;
} else {
switch (num_components) {
case 4: copycb = copy_rgba_to_rgba; break;
case 3: copycb = copy_rgb_to_rgba; break;
default: assert(false);
}
}
// NOTA BENE: It is advantageous for Basis LZ compression to order
// mipmap levels from largest to smallest.
for (uint32_t level = 0; level < This->numLevels; 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 image_size = ktxTexture2_GetImageSize(This, level);
for (uint32_t layer = 0; layer < This->numLayers; layer++) {
uint32_t faceSlices = This->numFaces == 1 ? depth : This->numFaces;
for (ktx_uint32_t slice = 0; slice < faceSlices; slice++) {
ktx_size_t offset;
ktxTexture2_GetImageOffset(This, level, layer, slice, &offset);
iit->resize(width, height);
copycb((uint8_t*)iit->get_ptr(), This->pData + offset,
num_components, image_size,
comp_mapping);
++iit;
}
}
}
free(This->pData); // No longer needed. Reduce memory footprint.
This->pData = NULL;
This->dataSize = 0;
//
// Setup rest of compressor parameters
//
ktx_uint32_t threadCount = params->threadCount;
if (threadCount < 1)
threadCount = 1;
job_pool jpool(threadCount);
cparams.m_pJob_pool = &jpool;
#if BASISU_SUPPORT_SSE
bool prevSSESupport = g_cpu_supports_sse41;
if (params->noSSE)
g_cpu_supports_sse41 = false;
#endif
ktx_uint32_t transfer = KHR_DFDVAL(BDB, TRANSFER);
if (transfer == KHR_DF_TRANSFER_SRGB)
cparams.m_perceptual = true;
else
cparams.m_perceptual = false;
cparams.m_mip_gen = false; // We provide the mip levels.
cparams.m_uastc = params->uastc;
if (params->uastc) {
cparams.m_pack_uastc_flags = params->uastcFlags;
if (params->uastcRDO) {
cparams.m_rdo_uastc = true;
if (params->uastcRDOQualityScalar > 0.0f) {
cparams.m_rdo_uastc_quality_scalar =
params->uastcRDOQualityScalar;
}
if (params->uastcRDODictSize > 0) {
cparams.m_rdo_uastc_dict_size = params->uastcRDODictSize;
}
if (params->uastcRDOMaxSmoothBlockErrorScale > 0) {
cparams.m_rdo_uastc_max_smooth_block_error_scale =
params->uastcRDOMaxSmoothBlockErrorScale;
}
if (params->uastcRDOMaxSmoothBlockStdDev > 0) {
cparams.m_rdo_uastc_smooth_block_max_std_dev =
params->uastcRDOMaxSmoothBlockStdDev;
}
cparams.m_rdo_uastc_favor_simpler_modes_in_rdo_mode =
!params->uastcRDODontFavorSimplerModes;
cparams.m_rdo_uastc_favor_simpler_modes_in_rdo_mode =
!params->uastcRDONoMultithreading;
}
} else {
// ETC1S-related params.
// Explicit specification is required as 0 is a valid value
// in the basis_compressor leaving us without a good way to
// indicate the parameter has not been set by the caller. (If we
// leave m_compression_level unset it will default to 1. We don't
// want the default to differ from `basisu` so 0 can't be the default.
cparams.m_compression_level = params->compressionLevel;
// There's no default for m_quality_level. `basisu` tool overrides
// any explicit m_{endpoint,selector}_clusters settings with those
// calculated from m_quality_level, if the user set that option. On
// the other hand the basis_compressor overrides the values of
// m_{endpoint,selector}_rdo_thresh calculated from m_quality_level
// with explicit settings made by the user. Note that, unlike the
// first pair where both have to be set, each of the second pair
// independently override the value for it calculated from
// m_quality_level.
//
// This is confusing for the user and tricky to document clearly.
// Therefore we override qualityLevel if both of max{Endpoint,Selector}s
// have been set so both sets of parameters are treated the same,
// except that intentionally we require the caller to have set both
// of max{Endpoint,Selector}s
if (params->maxEndpoints && params->maxSelectors) {
cparams.m_max_endpoint_clusters = params->maxEndpoints;
cparams.m_max_selector_clusters = params->maxSelectors;
// cparams.m_quality_level = -1; // Default setting.
} else if (params->qualityLevel != 0) {
cparams.m_max_endpoint_clusters = 0;
cparams.m_max_selector_clusters = 0;
cparams.m_quality_level = params->qualityLevel;
} else {
cparams.m_max_endpoint_clusters = 0;
cparams.m_max_selector_clusters = 0;
cparams.m_quality_level = 128;
}
if (params->endpointRDOThreshold > 0)
cparams.m_endpoint_rdo_thresh = params->endpointRDOThreshold;
if (params->selectorRDOThreshold > 0)
cparams.m_selector_rdo_thresh = params->selectorRDOThreshold;
if (params->normalMap) {
cparams.m_no_endpoint_rdo = true;
cparams.m_no_selector_rdo = true;
} else {
cparams.m_no_endpoint_rdo = params->noEndpointRDO;
cparams.m_no_selector_rdo = params->noSelectorRDO;
}
}
// Flip images across Y axis
// cparams.m_y_flip = false; // Let tool, e.g. toktx do its own yflip so
// ktxTexture is consistent.
// Output debug information during compression
//cparams.m_debug = true;
// m_debug_images is pretty slow
//cparams.m_debug_images = true;
// Split the R channel to RGB and the G channel to alpha. We do the
// seperation in this func (see above) so leave this at its default, false.
//bool_param<false> m_seperate_rg_to_color_alpha;
// m_userdata0, m_userdata1 go directly into the .basis file header.
// No need to set.
if (This->isVideo) {
// Encoder uses this to decide whether to create p-frames.
cparams.m_tex_type = cBASISTexTypeVideoFrames;
// cparams.m_us_per_frame & m_framerate are not used by
// the encoder, except to write the values into the .basis file header,
// so no point in setting them.
} else {
// Set to cBASISTexType2D as any other setting is likely to cause
// validity checks that the encoder performs on its results, to
// fail. The checks only work properly when the encoder generates
// mipmaps tself and are oriented to ensuring the .basis file is
// sensible. Underlying compression works fine and we already know
// what level, layer and face/slice each image belongs too.
cparams.m_tex_type = cBASISTexType2D;
}
#define DUMP_BASIS_FILE 0
#if DUMP_BASIS_FILE
cparams.m_out_filename = "ktxtest.basis";
cparams.m_write_output_basis_files = true;
#endif
#define DEBUG_ENCODER 0
#if DEBUG_ENCODER
cparams.m_debug = true;
g_debug_printf = true;
#endif
basis_compressor c;
// As we don't use it, file reading support has been removed from the
// BasisU code. Ensure we don't accidentally try to use it.
assert(cparams.m_read_source_images == false
&& "m_read_source_images must be false");
// init() only returns false if told to read source image files and the
// list of files is empty.
(void)c.init(cparams);
//enable_debug_printf(true);
basis_compressor::error_code ec = c.process();
if (ec != basis_compressor::cECSuccess) {
// We should be sending valid 2d arrays, cubemaps or video ...
assert(ec != basis_compressor::cECFailedValidating);
// Do something sensible with other errors
return KTX_INVALID_OPERATION;
#if 0
switch (ec) {
case basis_compressor::cECFailedReadingSourceImages:
{
error_printf("Compressor failed reading a source image!\n");
if (opts.m_individual)
exit_flag = false;
break;
}
case basis_compressor::cECFailedFrontEnd:
error_printf("Compressor frontend stage failed!\n");
break;
case basis_compressor::cECFailedFontendExtract:
error_printf("Compressor frontend data extraction failed!\n");
break;
case basis_compressor::cECFailedBackend:
error_printf("Compressor backend stage failed!\n");
break;
case basis_compressor::cECFailedCreateBasisFile:
error_printf("Compressor failed creating Basis file data!\n");
break;
case basis_compressor::cECFailedWritingOutput:
error_printf("Compressor failed writing to output Basis file!\n");
break;
default:
error_printf("basis_compress::process() failed!\n");
break;
}
}
return KTX_WHAT_ERROR?;
#endif
}
#if DUMP_BASIS_FILE
return KTX_UNSUPPORTED_FEATURE;
#endif
//
// Compression successful. Now we have to unpick the basis output and
// copy the info and images to This texture.
//
#if BASISU_SUPPORT_SSE
g_cpu_supports_sse41 = prevSSESupport;
#endif
const uint8_vec& bf = c.get_output_basis_file();
const basis_file_header& bfh = *reinterpret_cast<const basis_file_header*>(bf.data());
assert(bfh.m_total_images == num_images);
uint8_t* bgd = nullptr;
size_t bgd_size;
uint32_t image_data_size = 0;
ktxTexture2_private& priv = *This->_private;
uint32_t base_offset = bfh.m_slice_desc_file_ofs;
const basis_slice_desc* slice
= reinterpret_cast<const basis_slice_desc*>(&bf[base_offset]);
std::vector<uint32_t> level_file_offsets(This->numLevels);
if (params->uastc) {
for (uint32_t level = 0; level < This->numLevels; level++) {
uint32_t depth = MAX(1, This->baseDepth >> level);
uint32_t levelByteLength = 0;
uint32_t levelImageCount = This->numLayers * This->numFaces * depth;
level_file_offsets[level] = slice->m_file_ofs;
for (uint32_t image = 0; image < levelImageCount; image++, slice++) {
image_data_size += slice->m_file_size;
levelByteLength += slice->m_file_size;
}
priv._levelIndex[level].byteLength = levelByteLength;
priv._levelIndex[level].uncompressedByteLength = levelByteLength;
}
} else {
//
// Allocate supercompression global data and write its header.
//
uint32_t image_desc_size = sizeof(ktxBasisLzEtc1sImageDesc);
bgd_size = sizeof(ktxBasisLzGlobalHeader)
+ image_desc_size * num_images
+ bfh.m_endpoint_cb_file_size + bfh.m_selector_cb_file_size
+ bfh.m_tables_file_size;
bgd = new ktx_uint8_t[bgd_size];
ktxBasisLzGlobalHeader& bgdh = *reinterpret_cast<ktxBasisLzGlobalHeader*>(bgd);
bgdh.endpointCount = (uint16_t)bfh.m_total_endpoints;
bgdh.endpointsByteLength = bfh.m_endpoint_cb_file_size;
bgdh.selectorCount = (uint16_t)bfh.m_total_selectors;
bgdh.selectorsByteLength = bfh.m_selector_cb_file_size;
bgdh.tablesByteLength = bfh.m_tables_file_size;
bgdh.extendedByteLength = 0;
//
// Write the index of slice descriptions to the global data.
//
ktxBasisLzEtc1sImageDesc* kimages = BGD_ETC1S_IMAGE_DESCS(bgd);
// 3 things to remember about offsets:
// 1. levelIndex offsets at this point are relative to This->pData;
// 2. In the ktx image descriptors, slice offsets are relative to the
// start of the mip level;
// 3. basis_slice_desc offsets are relative to the end of the basis
// header. Hence base_offset set above is used to rebase offsets
// relative to the start of the slice data.
// Assumption here is that slices produced by the compressor are in the
// same order as we passed them in above, i.e. ordered by mip level.
// Note also that slice->m_level_index is always 0, unless the compressor
// generated mip levels, so essentially useless. Alpha slices are always
// the odd numbered slices.
uint32_t image = 0;
for (uint32_t level = 0; level < This->numLevels; level++) {
uint32_t depth = MAX(1, This->baseDepth >> level);
uint32_t level_byte_length = 0;
assert(!(slice->m_flags & cSliceDescFlagsHasAlpha));
level_file_offsets[level] = slice->m_file_ofs;
for (uint32_t layer = 0; layer < This->numLayers; layer++) {
uint32_t faceSlices = This->numFaces == 1 ? depth
: This->numFaces;
for (uint32_t faceSlice = 0; faceSlice < faceSlices; faceSlice++) {
level_byte_length += slice->m_file_size;
kimages[image].rgbSliceByteOffset = slice->m_file_ofs
- level_file_offsets[level];
kimages[image].rgbSliceByteLength = slice->m_file_size;
if (bfh.m_flags & cBASISHeaderFlagHasAlphaSlices) {
slice++;
level_byte_length += slice->m_file_size;
kimages[image].alphaSliceByteOffset =
slice->m_file_ofs - level_file_offsets[level];
kimages[image].alphaSliceByteLength =
slice->m_file_size;
} else {
kimages[image].alphaSliceByteOffset = 0;
kimages[image].alphaSliceByteLength = 0;
}
// Set the PFrame flag, inverse of the .basis IFrame flag.
if (This->isVideo) {
// Extract FrameIsIFrame
kimages[image].imageFlags =
(slice->m_flags & ~cSliceDescFlagsHasAlpha);
// Set our flag to the inverse.
kimages[image].imageFlags ^=
cSliceDescFlagsFrameIsIFrame;
} else {
kimages[image].imageFlags = 0;
}
slice++;
image++;
}
}
priv._levelIndex[level].byteLength = level_byte_length;
priv._levelIndex[level].uncompressedByteLength = 0;
image_data_size += level_byte_length;
}
//
// Copy the global code books & huffman tables to global data.
//
// Slightly sleazy but as image is now the last valid index in the
// slice description array plus 1, &kimages[image] points at the first
// byte where the endpoints, etc. must be written.
uint8_t* dstptr = reinterpret_cast<uint8_t*>(&kimages[image]);
// Copy the endpoints ...
memcpy(dstptr,
&bf[bfh.m_endpoint_cb_file_ofs],
bfh.m_endpoint_cb_file_size);
dstptr += bgdh.endpointsByteLength;
// selectors ...
memcpy(dstptr,
&bf[bfh.m_selector_cb_file_ofs],
bfh.m_selector_cb_file_size);
dstptr += bgdh.selectorsByteLength;
// and the huffman tables.
memcpy(dstptr,
&bf[bfh.m_tables_file_ofs],
bfh.m_tables_file_size);
assert((size_t)(dstptr + bgdh.tablesByteLength - bgd) <= bgd_size);
//
// We have a complete global data package.
//
priv._supercompressionGlobalData = bgd;
priv._sgdByteLength = bgd_size;
}
//
// Update This texture and copy compressed image data to it.
//
// Declare here so can use goto cleanup.
uint8_t* new_data = 0;
ktxFormatSize& formatSize = This->_protected->_formatSize;
uint64_t level_offset = 0;
// Since we've left m_check_for_alpha set and m_force_alpha unset in
// the compressor parameters, the basis encoder will have removed an input
// alpha channel, if every alpha pixel in every image is 255 prior to
// encoding and supercompression. The DFD needs to reflect the encoded data
// not the input texture. Override the alphacontent setting, if this has
// happened.
if ((bfh.m_flags & cBASISHeaderFlagHasAlphaSlices) == 0) {
alphaContent = eNone;
}
new_data = (uint8_t*) malloc(image_data_size);
if (!new_data) {
result = KTX_OUT_OF_MEMORY;
goto cleanup;
}
// Delayed modifying texture until here so it's after points of
// possible failure.
if (params->uastc) {
result = ktxTexture2_rewriteDfd4Uastc(This, alphaContent,
isLuminance,
comp_mapping);
if (result != KTX_SUCCESS) goto cleanup;
// Reflect this in the formatSize
ktxFormatSize_initFromDfd(&formatSize, This->pDfd);
// and the requiredLevelAlignment.
priv._requiredLevelAlignment = 4 * 4;
} else {
result = ktxTexture2_rewriteDfd4BasisLzETC1S(This, alphaContent,
isLuminance,
comp_mapping);
if (result != KTX_SUCCESS) goto cleanup;
This->supercompressionScheme = KTX_SS_BASIS_LZ;
// Reflect this in the formatSize
ktxFormatSize_initFromDfd(&formatSize, This->pDfd);
// and the requiredLevelAlignment.
priv._requiredLevelAlignment = 1;
}
This->vkFormat = VK_FORMAT_UNDEFINED;
This->isCompressed = KTX_TRUE;
// Block-compressed textures never need byte swapping so typeSize is 1.
assert(This->_protected->_typeSize == 1);
// Copy in the compressed image data.
This->pData = new_data;
This->dataSize = image_data_size;
for (int32_t level = This->numLevels - 1; level >= 0; level--) {
priv._levelIndex[level].byteOffset = level_offset;
// byteLength was set in loop above
memcpy(This->pData + level_offset,
&bf[level_file_offsets[level]],
priv._levelIndex[level].byteLength);
level_offset += _KTX_PADN(priv._requiredLevelAlignment,
priv._levelIndex[level].byteLength);
}
return KTX_SUCCESS;
cleanup:
if (bgd) {
delete bgd;
priv._supercompressionGlobalData = 0;
priv._sgdByteLength = 0;
}
if (new_data) free(new_data);
return result;
}
extern "C" KTX_API const ktx_uint32_t KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL
= BASISU_DEFAULT_COMPRESSION_LEVEL;
/**
* @memberof ktxTexture2
* @ingroup writer
* @~English
* @brief Supercompress a KTX2 texture with uncompressed images.
*
* The images are encoded to ETC1S block-compressed format and supercompressed
* with Basis Universal. The encoded images replace the original images and the
* texture's fields including the DFD are modified to reflect the new state.
*
* Such textures must be transcoded to a desired target block compressed format
* before they can be uploaded to a GPU via a graphics API.
*
* @sa ktxTexture2_CompressBasisEx().
*
* @param[in] This pointer to the ktxTexture2 object of interest.
* @param[in] quality Compression quality, a value from 1 - 255. Default is
128 which is selected if @p quality is 0. Lower=better
compression/lower quality/faster. Higher=less
compression/higher quality/slower.
*
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
*
* @exception KTX_INVALID_OPERATION
* The texture is already supercompressed.
* @exception KTX_INVALID_OPERATION
* The texture's image are in a block compressed
* format.
* @exception KTX_OUT_OF_MEMORY Not enough memory to carry out supercompression.
*/
extern "C" KTX_error_code
ktxTexture2_CompressBasis(ktxTexture2* This, ktx_uint32_t quality)
{
ktxBasisParams params = {};
params.structSize = sizeof(params);
params.threadCount = 1;
params.compressionLevel = KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL;
params.qualityLevel = quality;
return ktxTexture2_CompressBasisEx(This, ¶ms);
}
|