diff options
Diffstat (limited to 'thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp')
-rw-r--r-- | thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp | 561 |
1 files changed, 530 insertions, 31 deletions
diff --git a/thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp b/thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp index dec769d5ac..342446b8fd 100644 --- a/thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp +++ b/thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp @@ -1,5 +1,5 @@ // basisu_gpu_texture.cpp -// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved. +// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,13 +15,15 @@ #include "basisu_gpu_texture.h" #include "basisu_enc.h" #include "basisu_pvrtc1_4.h" -#if BASISU_USE_ASTC_DECOMPRESS -#include "basisu_astc_decomp.h" -#endif +#include "3rdparty/android_astc_decomp.h" #include "basisu_bc7enc.h" +#include "../transcoder/basisu_astc_hdr_core.h" namespace basisu { + //------------------------------------------------------------------------------------------------ + // ETC2 EAC + void unpack_etc2_eac(const void *pBlock_bits, color_rgba *pPixels) { static_assert(sizeof(eac_a8_block) == 8, "sizeof(eac_a8_block) == 8"); @@ -56,6 +58,8 @@ namespace basisu pPixels[15].a = clamp255(base + pTable[pBlock->get_selector(3, 3, selector_bits)] * mul); } + //------------------------------------------------------------------------------------------------ + // BC1 struct bc1_block { enum { cTotalEndpointBytes = 2, cTotalSelectorBytes = 4 }; @@ -274,6 +278,9 @@ namespace basisu return used_punchthrough; } + //------------------------------------------------------------------------------------------------ + // BC3-5 + struct bc4_block { enum { cBC4SelectorBits = 3, cTotalSelectorBytes = 6, cMaxSelectorValues = 8 }; @@ -372,7 +379,8 @@ namespace basisu unpack_bc4(pBlock_bits, &pPixels[0].r, sizeof(color_rgba)); unpack_bc4((const uint8_t *)pBlock_bits + sizeof(bc4_block), &pPixels[0].g, sizeof(color_rgba)); } - + + //------------------------------------------------------------------------------------------------ // ATC isn't officially documented, so I'm assuming these references: // http://www.guildsoftware.com/papers/2012.Converting.DXTC.to.ATC.pdf // https://github.com/Triang3l/S3TConv/blob/master/s3tconv_atitc.c @@ -426,6 +434,7 @@ namespace basisu } } + //------------------------------------------------------------------------------------------------ // BC7 mode 0-7 decompression. // Instead of one monster routine to unpack all the BC7 modes, we're lumping the 3 subset, 2 subset, 1 subset, and dual plane modes together into simple shared routines. @@ -742,6 +751,255 @@ namespace basisu return false; } + static inline int bc6h_sign_extend(int val, int bits) + { + assert((bits >= 1) && (bits < 32)); + assert((val >= 0) && (val < (1 << bits))); + return (val << (32 - bits)) >> (32 - bits); + } + + static inline int bc6h_apply_delta(int base, int delta, int num_bits, int is_signed) + { + int bitmask = ((1 << num_bits) - 1); + int v = (base + delta) & bitmask; + return is_signed ? bc6h_sign_extend(v, num_bits) : v; + } + + static int bc6h_dequantize(int val, int bits, int is_signed) + { + int result; + if (is_signed) + { + if (bits >= 16) + result = val; + else + { + int s_flag = 0; + if (val < 0) + { + s_flag = 1; + val = -val; + } + + if (val == 0) + result = 0; + else if (val >= ((1 << (bits - 1)) - 1)) + result = 0x7FFF; + else + result = ((val << 15) + 0x4000) >> (bits - 1); + + if (s_flag) + result = -result; + } + } + else + { + if (bits >= 15) + result = val; + else if (!val) + result = 0; + else if (val == ((1 << bits) - 1)) + result = 0xFFFF; + else + result = ((val << 16) + 0x8000) >> bits; + } + return result; + } + + static inline int bc6h_interpolate(int a, int b, const uint8_t* pWeights, int index) + { + return (a * (64 - (int)pWeights[index]) + b * (int)pWeights[index] + 32) >> 6; + } + + static inline basist::half_float bc6h_convert_to_half(int val, int is_signed) + { + if (!is_signed) + { + // scale by 31/64 + return (basist::half_float)((val * 31) >> 6); + } + + // scale by 31/32 + val = (val < 0) ? -(((-val) * 31) >> 5) : (val * 31) >> 5; + + int s = 0; + if (val < 0) + { + s = 0x8000; + val = -val; + } + + return (basist::half_float)(s | val); + } + + static inline uint32_t bc6h_get_bits(uint32_t num_bits, uint64_t& l, uint64_t& h, uint32_t& total_bits) + { + assert((num_bits) && (num_bits <= 63)); + + uint32_t v = (uint32_t)(l & ((1U << num_bits) - 1U)); + + l >>= num_bits; + l |= (h << (64U - num_bits)); + h >>= num_bits; + + total_bits += num_bits; + assert(total_bits <= 128); + + return v; + } + + static inline uint32_t bc6h_reverse_bits(uint32_t v, uint32_t num_bits) + { + uint32_t res = 0; + for (uint32_t i = 0; i < num_bits; i++) + { + uint32_t bit = (v & (1u << i)) != 0u; + res |= (bit << (num_bits - 1u - i)); + } + return res; + } + + static inline uint64_t bc6h_read_le_qword(const void* p) + { + const uint8_t* pSrc = static_cast<const uint8_t*>(p); + return ((uint64_t)read_le_dword(pSrc)) | (((uint64_t)read_le_dword(pSrc + sizeof(uint32_t))) << 32U); + } + + bool unpack_bc6h(const void* pSrc_block, void* pDst_block, bool is_signed, uint32_t dest_pitch_in_halfs) + { + assert(dest_pitch_in_halfs >= 4 * 3); + + const uint32_t MAX_SUBSETS = 2, MAX_COMPS = 3; + + const uint8_t* pSrc = static_cast<const uint8_t*>(pSrc_block); + basist::half_float* pDst = static_cast<basist::half_float*>(pDst_block); + + uint64_t blo = bc6h_read_le_qword(pSrc), bhi = bc6h_read_le_qword(pSrc + sizeof(uint64_t)); + + // Unpack mode + const int mode = basist::g_bc6h_mode_lookup[blo & 31]; + if (mode < 0) + { + for (int y = 0; y < 4; y++) + { + memset(pDst, 0, sizeof(basist::half_float) * 4); + pDst += dest_pitch_in_halfs; + } + return false; + } + + // Skip mode bits + uint32_t total_bits_read = 0; + bc6h_get_bits((mode < 2) ? 2 : 5, blo, bhi, total_bits_read); + + assert(mode < (int)basist::NUM_BC6H_MODES); + + const uint32_t num_subsets = (mode >= 10) ? 1 : 2; + const bool is_mode_9_or_10 = (mode == 9) || (mode == 10); + + // Unpack endpoint components + int comps[MAX_SUBSETS][MAX_COMPS][2] = { { { 0 } } }; // [subset][comp][l/h] + int part_index = 0; + + uint32_t layout_index = 0; + while (layout_index < basist::MAX_BC6H_LAYOUT_INDEX) + { + const basist::bc6h_bit_layout& layout = basist::g_bc6h_bit_layouts[mode][layout_index]; + + if (layout.m_comp < 0) + break; + + const int subset = layout.m_index >> 1, lh_index = layout.m_index & 1; + assert((layout.m_comp == 3) || ((subset >= 0) && (subset < (int)MAX_SUBSETS))); + + const int last_bit = layout.m_last_bit, first_bit = layout.m_first_bit; + assert(last_bit >= 0); + + int& res = (layout.m_comp == 3) ? part_index : comps[subset][layout.m_comp][lh_index]; + + if (first_bit < 0) + { + res |= (bc6h_get_bits(1, blo, bhi, total_bits_read) << last_bit); + } + else + { + const int total_bits = iabs(last_bit - first_bit) + 1; + const int bit_shift = basisu::minimum(first_bit, last_bit); + + int b = bc6h_get_bits(total_bits, blo, bhi, total_bits_read); + + if (last_bit < first_bit) + b = bc6h_reverse_bits(b, total_bits); + + res |= (b << bit_shift); + } + + layout_index++; + } + assert(layout_index != basist::MAX_BC6H_LAYOUT_INDEX); + + // Sign extend/dequantize endpoints + const int num_sig_bits = basist::g_bc6h_mode_sig_bits[mode][0]; + if (is_signed) + { + for (uint32_t comp = 0; comp < 3; comp++) + comps[0][comp][0] = bc6h_sign_extend(comps[0][comp][0], num_sig_bits); + } + + if (is_signed || !is_mode_9_or_10) + { + for (uint32_t subset = 0; subset < num_subsets; subset++) + for (uint32_t comp = 0; comp < 3; comp++) + for (uint32_t lh = (subset ? 0 : 1); lh < 2; lh++) + comps[subset][comp][lh] = bc6h_sign_extend(comps[subset][comp][lh], basist::g_bc6h_mode_sig_bits[mode][1 + comp]); + } + + if (!is_mode_9_or_10) + { + for (uint32_t subset = 0; subset < num_subsets; subset++) + for (uint32_t comp = 0; comp < 3; comp++) + for (uint32_t lh = (subset ? 0 : 1); lh < 2; lh++) + comps[subset][comp][lh] = bc6h_apply_delta(comps[0][comp][0], comps[subset][comp][lh], num_sig_bits, is_signed); + } + + for (uint32_t subset = 0; subset < num_subsets; subset++) + for (uint32_t comp = 0; comp < 3; comp++) + for (uint32_t lh = 0; lh < 2; lh++) + comps[subset][comp][lh] = bc6h_dequantize(comps[subset][comp][lh], num_sig_bits, is_signed); + + // Now unpack weights and output texels + const int weight_bits = (mode >= 10) ? 4 : 3; + const uint8_t* pWeights = (mode >= 10) ? basist::g_bc6h_weight4 : basist::g_bc6h_weight3; + + dest_pitch_in_halfs -= 4 * 3; + + for (uint32_t y = 0; y < 4; y++) + { + for (uint32_t x = 0; x < 4; x++) + { + int subset = (num_subsets == 1) ? ((x | y) ? 0 : 0x80) : basist::g_bc6h_2subset_patterns[part_index][y][x]; + const int num_bits = weight_bits + ((subset & 0x80) ? -1 : 0); + + subset &= 1; + + const int weight_index = bc6h_get_bits(num_bits, blo, bhi, total_bits_read); + + pDst[0] = bc6h_convert_to_half(bc6h_interpolate(comps[subset][0][0], comps[subset][0][1], pWeights, weight_index), is_signed); + pDst[1] = bc6h_convert_to_half(bc6h_interpolate(comps[subset][1][0], comps[subset][1][1], pWeights, weight_index), is_signed); + pDst[2] = bc6h_convert_to_half(bc6h_interpolate(comps[subset][2][0], comps[subset][2][1], pWeights, weight_index), is_signed); + + pDst += 3; + } + + pDst += dest_pitch_in_halfs; + } + + assert(total_bits_read == 128); + return true; + } + //------------------------------------------------------------------------------------------------ + // FXT1 (for fun, and because some modern Intel parts support it, and because a subset is like BC1) + struct fxt1_block { union @@ -901,6 +1159,9 @@ namespace basisu return true; } + //------------------------------------------------------------------------------------------------ + // PVRTC2 (non-interpolated, hard_flag=1 modulation=0 subset only!) + struct pvrtc2_block { uint8_t m_modulation[4]; @@ -1015,6 +1276,9 @@ namespace basisu return true; } + //------------------------------------------------------------------------------------------------ + // ETC2 EAC R11 or RG11 + struct etc2_eac_r11 { uint64_t m_base : 8; @@ -1085,13 +1349,16 @@ namespace basisu unpack_etc2_eac_r(pBlock, pPixels, c); } } - + + //------------------------------------------------------------------------------------------------ + // UASTC + void unpack_uastc(const void* p, color_rgba* pPixels) { basist::unpack_uastc(*static_cast<const basist::uastc_block*>(p), (basist::color32 *)pPixels, false); } - - // Unpacks to RGBA, R, RG, or A + + // Unpacks to RGBA, R, RG, or A. LDR GPU texture formats only. bool unpack_block(texture_format fmt, const void* pBlock, color_rgba* pPixels) { switch (fmt) @@ -1150,14 +1417,24 @@ namespace basisu unpack_etc2_eac(pBlock, pPixels); break; } - case texture_format::cASTC4x4: + case texture_format::cBC6HSigned: + case texture_format::cBC6HUnsigned: + case texture_format::cASTC_HDR_4x4: + case texture_format::cUASTC_HDR_4x4: + { + // Can't unpack HDR blocks in unpack_block() because it returns 32bpp pixel data. + assert(0); + return false; + } + case texture_format::cASTC_LDR_4x4: { -#if BASISU_USE_ASTC_DECOMPRESS const bool astc_srgb = false; - basisu_astc::astc::decompress(reinterpret_cast<uint8_t*>(pPixels), static_cast<const uint8_t*>(pBlock), astc_srgb, 4, 4); -#else - memset(pPixels, 255, 16 * sizeof(color_rgba)); -#endif + bool status = basisu_astc::astc::decompress_ldr(reinterpret_cast<uint8_t*>(pPixels), static_cast<const uint8_t*>(pBlock), astc_srgb, 4, 4); + assert(status); + + if (!status) + return false; + break; } case texture_format::cATC_RGB: @@ -1206,6 +1483,66 @@ namespace basisu return true; } + bool unpack_block_hdr(texture_format fmt, const void* pBlock, vec4F* pPixels) + { + switch (fmt) + { + case texture_format::cASTC_HDR_4x4: + case texture_format::cUASTC_HDR_4x4: + { +#if 1 + bool status = basisu_astc::astc::decompress_hdr(&pPixels[0][0], (uint8_t*)pBlock, 4, 4); + assert(status); + if (!status) + return false; +#else + basist::half_float half_block[16][4]; + + astc_helpers::log_astc_block log_blk; + if (!astc_helpers::unpack_block(pBlock, log_blk, 4, 4)) + return false; + if (!astc_helpers::decode_block(log_blk, half_block, 4, 4, astc_helpers::cDecodeModeHDR16)) + return false; + + for (uint32_t p = 0; p < 16; p++) + { + pPixels[p][0] = basist::half_to_float(half_block[p][0]); + pPixels[p][1] = basist::half_to_float(half_block[p][1]); + pPixels[p][2] = basist::half_to_float(half_block[p][2]); + pPixels[p][3] = basist::half_to_float(half_block[p][3]); + } + + //memset(pPixels, 0, sizeof(vec4F) * 16); +#endif + return true; + } + case texture_format::cBC6HSigned: + case texture_format::cBC6HUnsigned: + { + basist::half_float half_block[16][3]; + + unpack_bc6h(pBlock, half_block, fmt == texture_format::cBC6HSigned); + + for (uint32_t p = 0; p < 16; p++) + { + pPixels[p][0] = basist::half_to_float(half_block[p][0]); + pPixels[p][1] = basist::half_to_float(half_block[p][1]); + pPixels[p][2] = basist::half_to_float(half_block[p][2]); + pPixels[p][3] = 1.0f; + } + + return true; + } + default: + { + break; + } + } + + assert(0); + return false; + } + bool gpu_image::unpack(image& img) const { img.resize(get_pixel_width(), get_pixel_height()); @@ -1252,7 +1589,48 @@ namespace basisu return success; } + + bool gpu_image::unpack_hdr(imagef& img) const + { + if ((m_fmt != texture_format::cASTC_HDR_4x4) && + (m_fmt != texture_format::cUASTC_HDR_4x4) && + (m_fmt != texture_format::cBC6HUnsigned) && + (m_fmt != texture_format::cBC6HSigned)) + { + // Can't call on LDR images, at least currently. (Could unpack the LDR data and convert to float.) + assert(0); + return false; + } + + img.resize(get_pixel_width(), get_pixel_height()); + img.set_all(vec4F(0.0f)); + + if (!img.get_width() || !img.get_height()) + return true; + + assert((m_block_width <= cMaxBlockSize) && (m_block_height <= cMaxBlockSize)); + vec4F pixels[cMaxBlockSize * cMaxBlockSize]; + clear_obj(pixels); + + bool success = true; + + for (uint32_t by = 0; by < m_blocks_y; by++) + { + for (uint32_t bx = 0; bx < m_blocks_x; bx++) + { + const void* pBlock = get_block_ptr(bx, by); + + if (!unpack_block_hdr(m_fmt, pBlock, pixels)) + success = false; + + img.set_block_clipped(pixels, bx * m_block_width, by * m_block_height, m_block_width, m_block_height); + } // bx + } // by + + return success; + } + // KTX1 texture file writing static const uint8_t g_ktx_file_id[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; // KTX/GL enums @@ -1273,6 +1651,8 @@ namespace basisu KTX_COMPRESSED_RGBA8_ETC2_EAC = 0x9278, KTX_COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C, KTX_COMPRESSED_SRGB_ALPHA_BPTC_UNORM = 0x8E8D, + KTX_COMPRESSED_RGB_BPTC_SIGNED_FLOAT = 0x8E8E, + KTX_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT = 0x8E8F, KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00, KTX_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02, KTX_COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0, @@ -1319,6 +1699,7 @@ namespace basisu uint32_t width = 0, height = 0, total_levels = 0; basisu::texture_format fmt = texture_format::cInvalidTextureFormat; + // Sanity check the input if (cubemap_flag) { if ((gpu_images.size() % 6) != 0) @@ -1327,7 +1708,7 @@ namespace basisu return false; } } - + for (uint32_t array_index = 0; array_index < gpu_images.size(); array_index++) { const gpu_image_vec &levels = gpu_images[array_index]; @@ -1426,6 +1807,18 @@ namespace basisu base_internal_fmt = KTX_RGBA; break; } + case texture_format::cBC6HSigned: + { + internal_fmt = KTX_COMPRESSED_RGB_BPTC_SIGNED_FLOAT; + base_internal_fmt = KTX_RGBA; + break; + } + case texture_format::cBC6HUnsigned: + { + internal_fmt = KTX_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + base_internal_fmt = KTX_RGBA; + break; + } case texture_format::cBC7: { internal_fmt = KTX_COMPRESSED_RGBA_BPTC_UNORM; @@ -1443,7 +1836,10 @@ namespace basisu base_internal_fmt = KTX_RGBA; break; } - case texture_format::cASTC4x4: + // We use different enums for HDR vs. LDR ASTC, but internally they are both just ASTC. + case texture_format::cASTC_LDR_4x4: + case texture_format::cASTC_HDR_4x4: + case texture_format::cUASTC_HDR_4x4: // UASTC_HDR is just HDR-only ASTC { internal_fmt = KTX_COMPRESSED_RGBA_ASTC_4x4_KHR; base_internal_fmt = KTX_RGBA; @@ -1496,17 +1892,17 @@ namespace basisu return false; } } - + ktx_header header; header.clear(); memcpy(&header.m_identifier, g_ktx_file_id, sizeof(g_ktx_file_id)); header.m_endianness = KTX_ENDIAN; - + header.m_pixelWidth = width; header.m_pixelHeight = height; - + header.m_glTypeSize = 1; - + header.m_glInternalFormat = internal_fmt; header.m_glBaseInternalFormat = base_internal_fmt; @@ -1517,12 +1913,12 @@ namespace basisu header.m_numberOfMipmapLevels = total_levels; header.m_numberOfFaces = cubemap_flag ? 6 : 1; - append_vector(ktx_data, (uint8_t *)&header, sizeof(header)); + append_vector(ktx_data, (uint8_t*)&header, sizeof(header)); for (uint32_t level_index = 0; level_index < total_levels; level_index++) { uint32_t img_size = gpu_images[0][level_index].get_size_in_bytes(); - + if ((header.m_numberOfFaces == 1) || (header.m_numberOfArrayElements > 1)) { img_size = img_size * header.m_numberOfFaces * maximum<uint32_t>(1, header.m_numberOfArrayElements); @@ -1531,9 +1927,10 @@ namespace basisu assert(img_size && ((img_size & 3) == 0)); packed_uint<4> packed_img_size(img_size); - append_vector(ktx_data, (uint8_t *)&packed_img_size, sizeof(packed_img_size)); + append_vector(ktx_data, (uint8_t*)&packed_img_size, sizeof(packed_img_size)); uint32_t bytes_written = 0; + (void)bytes_written; for (uint32_t array_index = 0; array_index < maximum<uint32_t>(1, header.m_numberOfArrayElements); array_index++) { @@ -1541,11 +1938,11 @@ namespace basisu { const gpu_image& img = gpu_images[cubemap_flag ? (array_index * 6 + face_index) : array_index][level_index]; - append_vector(ktx_data, (uint8_t *)img.get_ptr(), img.get_size_in_bytes()); - + append_vector(ktx_data, (uint8_t*)img.get_ptr(), img.get_size_in_bytes()); + bytes_written += img.get_size_in_bytes(); } - + } // array_index } // level_index @@ -1553,7 +1950,58 @@ namespace basisu return true; } - bool write_compressed_texture_file(const char* pFilename, const basisu::vector<gpu_image_vec>& g, bool cubemap_flag) + bool does_dds_support_format(texture_format fmt) + { + switch (fmt) + { + case texture_format::cBC1_NV: + case texture_format::cBC1_AMD: + case texture_format::cBC1: + case texture_format::cBC3: + case texture_format::cBC4: + case texture_format::cBC5: + case texture_format::cBC6HSigned: + case texture_format::cBC6HUnsigned: + case texture_format::cBC7: + return true; + default: + break; + } + return false; + } + + // Only supports the basic DirectX BC texture formats. + // gpu_images array is: [face/layer][mipmap level] + // For cubemap arrays, # of face/layers must be a multiple of 6. + // Accepts 2D, 2D mipmapped, 2D array, 2D array mipmapped + // and cubemap, cubemap mipmapped, and cubemap array mipmapped. + bool write_dds_file(uint8_vec &dds_data, const basisu::vector<gpu_image_vec>& gpu_images, bool cubemap_flag, bool use_srgb_format) + { + return false; + } + + bool write_dds_file(const char* pFilename, const basisu::vector<gpu_image_vec>& gpu_images, bool cubemap_flag, bool use_srgb_format) + { + uint8_vec dds_data; + + if (!write_dds_file(dds_data, gpu_images, cubemap_flag, use_srgb_format)) + return false; + + if (!write_vec_to_file(pFilename, dds_data)) + { + fprintf(stderr, "write_dds_file: Failed writing DDS file data\n"); + return false; + } + + return true; + } + + bool read_uncompressed_dds_file(const char* pFilename, basisu::vector<image> &ldr_mips, basisu::vector<imagef>& hdr_mips) + { + return false; + } + + bool write_compressed_texture_file(const char* pFilename, const basisu::vector<gpu_image_vec>& g, bool cubemap_flag, bool use_srgb_format) { std::string extension(string_tolower(string_get_extension(pFilename))); @@ -1570,8 +2018,8 @@ namespace basisu } else if (extension == "dds") { - // TODO - return false; + if (!write_dds_file(filedata, g, cubemap_flag, use_srgb_format)) + return false; } else { @@ -1583,11 +2031,18 @@ namespace basisu return basisu::write_vec_to_file(pFilename, filedata); } - bool write_compressed_texture_file(const char* pFilename, const gpu_image& g) + bool write_compressed_texture_file(const char* pFilename, const gpu_image_vec& g, bool use_srgb_format) + { + basisu::vector<gpu_image_vec> a; + a.push_back(g); + return write_compressed_texture_file(pFilename, a, false, use_srgb_format); + } + + bool write_compressed_texture_file(const char* pFilename, const gpu_image& g, bool use_srgb_format) { basisu::vector<gpu_image_vec> v; enlarge_vector(v, 1)->push_back(g); - return write_compressed_texture_file(pFilename, v, false); + return write_compressed_texture_file(pFilename, v, false, use_srgb_format); } //const uint32_t OUT_FILE_MAGIC = 'TEXC'; @@ -1626,5 +2081,49 @@ namespace basisu return fclose(pFile) != EOF; } + + // The .astc texture format is readable using ARM's astcenc, AMD Compressonator, and other engines/tools. It oddly doesn't support mipmaps, limiting + // its usefulness/relevance. + // https://github.com/ARM-software/astc-encoder/blob/main/Docs/FileFormat.md + bool write_astc_file(const char* pFilename, const void* pBlocks, uint32_t block_width, uint32_t block_height, uint32_t dim_x, uint32_t dim_y) + { + assert(pBlocks && (block_width >= 4) && (block_height >= 4) && (dim_x > 0) && (dim_y > 0)); + + uint8_vec file_data; + file_data.push_back(0x13); + file_data.push_back(0xAB); + file_data.push_back(0xA1); + file_data.push_back(0x5C); + + file_data.push_back((uint8_t)block_width); + file_data.push_back((uint8_t)block_height); + file_data.push_back(1); + + file_data.push_back((uint8_t)dim_x); + file_data.push_back((uint8_t)(dim_x >> 8)); + file_data.push_back((uint8_t)(dim_x >> 16)); + + file_data.push_back((uint8_t)dim_y); + file_data.push_back((uint8_t)(dim_y >> 8)); + file_data.push_back((uint8_t)(dim_y >> 16)); + + file_data.push_back((uint8_t)1); + file_data.push_back((uint8_t)0); + file_data.push_back((uint8_t)0); + + const uint32_t num_blocks_x = (dim_x + block_width - 1) / block_width; + const uint32_t num_blocks_y = (dim_y + block_height - 1) / block_height; + + const uint32_t total_bytes = num_blocks_x * num_blocks_y * 16; + + const size_t cur_size = file_data.size(); + + file_data.resize(cur_size + total_bytes); + + memcpy(&file_data[cur_size], pBlocks, total_bytes); + + return write_vec_to_file(pFilename, file_data); + } + } // basisu |