// basisu_gpu_texture.cpp // Copyright (C) 2017-2019 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. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "basisu_gpu_texture.h" #include "detex/decompress_bc.h" #include "detex/decompress_bc7.h" #include "detex/decompress_eac.h" #include "basisu_enc.h" #include "basisu_pvrtc1_4.h" namespace basisu { // Unpacks to RGBA, R, RG, or A void unpack_block(texture_format fmt, const void* pBlock, color_rgba* pPixels) { switch (fmt) { case cBC1: { if (detexGetModeBC1((uint8_t*)pBlock)) detexDecompressBlockBC1A((uint8_t*)pBlock, 0, (uint8_t*)pPixels); else detexDecompressBlockBC1((uint8_t*)pBlock, 0, (uint8_t*)pPixels); break; } case cBC3: { detexDecompressBlockBC3((uint8_t*)pBlock, 0, (uint8_t*)pPixels); break; } case cBC4: { // Unpack to R detexDecompressBlockBC4((uint8_t*)pBlock, 0, (uint8_t*)pPixels, sizeof(color_rgba)); break; } case cBC5: { // Unpack to RG detexDecompressBlockBC4((uint8_t*)pBlock, 0, (uint8_t*)pPixels, sizeof(color_rgba)); detexDecompressBlockBC4((uint8_t*)pBlock + sizeof(uint64_t), 0, (uint8_t*)pPixels + 1, sizeof(color_rgba)); break; } case cBC7: { detexDecompressBlockBPTC((const uint8_t*)pBlock, UINT32_MAX, 0, (uint8_t*)pPixels); break; } // Full ETC2 color blocks (planar/T/H modes) is currently unsupported in basisu, but we do support ETC2 with alpha (using ETC1 for color) case cETC2_RGB: case cETC1: case cETC1S: { unpack_etc1(*static_cast(pBlock), pPixels); break; } case cETC2_RGBA: { unpack_etc1(static_cast(pBlock)[1], pPixels); detexDecompressBlockETC2_EAC((const uint8_t*)pBlock, (uint8_t*)pPixels + 3, sizeof(color_rgba)); break; } case cETC2_ALPHA: { // Unpack to A detexDecompressBlockETC2_EAC((const uint8_t*)pBlock, (uint8_t*)pPixels + 3, sizeof(color_rgba)); break; } default: { assert(0); // TODO break; } } } bool gpu_image::unpack(image& img, bool pvrtc_wrap_addressing) const { img.resize(get_width(), get_height()); img.set_all(g_black_color); if (!img.get_width() || !img.get_height()) return true; if ((m_fmt == cPVRTC1_4_RGB) || (m_fmt == cPVRTC1_4_RGBA)) { if (!is_pow2(m_width) || !is_pow2(m_height)) { // PVRTC1 images must use power of 2 dimensions return false; } pvrtc4_image pi(m_width, m_height, pvrtc_wrap_addressing); if (get_total_blocks() != pi.get_total_blocks()) return false; memcpy(&pi.get_blocks()[0], get_ptr(), get_size_in_bytes()); pi.deswizzle(); pi.unpack_all_pixels(img); return true; } color_rgba pixels[cMaxBlockSize * cMaxBlockSize]; for (uint32_t i = 0; i < cMaxBlockSize * cMaxBlockSize; i++) pixels[i] = g_black_color; 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); unpack_block(m_fmt, pBlock, pixels); img.set_block_clipped(pixels, bx * m_block_width, by * m_block_height, m_block_width, m_block_height); } // bx } // by return true; } static const uint8_t g_ktx_file_id[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; // KTX/GL enums enum { KTX_ENDIAN = 0x04030201, KTX_OPPOSITE_ENDIAN = 0x01020304, KTX_ETC1_RGB8_OES = 0x8D64, KTX_RED = 0x1903, KTX_RG = 0x8227, KTX_RGB = 0x1907, KTX_RGBA = 0x1908, KTX_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0, KTX_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3, KTX_COMPRESSED_RED_RGTC1_EXT = 0x8DBB, KTX_COMPRESSED_RED_GREEN_RGTC2_EXT = 0x8DBD, KTX_COMPRESSED_RGB8_ETC2 = 0x9274, KTX_COMPRESSED_RGBA8_ETC2_EAC = 0x9278, KTX_COMPRESSED_RGBA_BPTC_UNORM_ARB, KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00, KTX_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, }; struct ktx_header { uint8_t m_identifier[12]; packed_uint<4> m_endianness; packed_uint<4> m_glType; packed_uint<4> m_glTypeSize; packed_uint<4> m_glFormat; packed_uint<4> m_glInternalFormat; packed_uint<4> m_glBaseInternalFormat; packed_uint<4> m_pixelWidth; packed_uint<4> m_pixelHeight; packed_uint<4> m_pixelDepth; packed_uint<4> m_numberOfArrayElements; packed_uint<4> m_numberOfFaces; packed_uint<4> m_numberOfMipmapLevels; packed_uint<4> m_bytesOfKeyValueData; void clear() { clear_obj(*this); } }; bool create_ktx_texture_file(uint8_vec &ktx_data, const gpu_image_vec& g) { if (!g.size()) { assert(0); return false; } uint32_t internal_fmt = KTX_ETC1_RGB8_OES, base_internal_fmt = KTX_RGB; switch (g[0].get_format()) { case cBC1: { internal_fmt = KTX_COMPRESSED_RGB_S3TC_DXT1_EXT; break; } case cBC3: { internal_fmt = KTX_COMPRESSED_RGBA_S3TC_DXT5_EXT; base_internal_fmt = KTX_RGBA; break; } case cBC4: { internal_fmt = KTX_COMPRESSED_RED_RGTC1_EXT;// KTX_COMPRESSED_LUMINANCE_LATC1_EXT; base_internal_fmt = KTX_RED; break; } case cBC5: { internal_fmt = KTX_COMPRESSED_RED_GREEN_RGTC2_EXT; base_internal_fmt = KTX_RG; break; } case cETC1: case cETC1S: { internal_fmt = KTX_ETC1_RGB8_OES; break; } case cETC2_RGB: { internal_fmt = KTX_COMPRESSED_RGB8_ETC2; break; } case cETC2_RGBA: { internal_fmt = KTX_COMPRESSED_RGBA8_ETC2_EAC; base_internal_fmt = KTX_RGBA; break; } case cBC7: { internal_fmt = KTX_COMPRESSED_RGBA_BPTC_UNORM_ARB; base_internal_fmt = KTX_RGBA; break; } case cPVRTC1_4_RGB: { internal_fmt = KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; } case cPVRTC1_4_RGBA: { internal_fmt = KTX_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; base_internal_fmt = KTX_RGBA; break; } default: { // TODO assert(0); 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 = g[0].get_width(); header.m_pixelHeight = g[0].get_height(); header.m_glInternalFormat = internal_fmt; header.m_glBaseInternalFormat = base_internal_fmt; header.m_numberOfMipmapLevels = (uint32_t)g.size(); header.m_numberOfFaces = 1; append_vector(ktx_data, (uint8_t *)&header, sizeof(header)); for (uint32_t level = 0; level < g.size(); level++) { const gpu_image& img = g[level]; if (level) { if ( (img.get_format() != g[0].get_format()) || (img.get_width() != maximum(1, g[0].get_width() >> level)) || (img.get_height() != maximum(1, g[0].get_height() >> level)) ) { // Bad input assert(0); return false; } } packed_uint<4> img_size = (uint32_t)img.get_size_in_bytes(); assert(img_size && ((img_size & 3) == 0)); append_vector(ktx_data, (uint8_t *)&img_size, sizeof(img_size)); append_vector(ktx_data, (uint8_t *)img.get_ptr(), img.get_size_in_bytes()); } return true; } bool write_compressed_texture_file(const char* pFilename, const gpu_image_vec& g) { std::string extension(string_tolower(string_get_extension(pFilename))); uint8_vec filedata; if (extension == "ktx") { if (!create_ktx_texture_file(filedata, g)) return false; } else if (extension == "pvr") { // TODO return false; } else if (extension == "dds") { // TODO return false; } else { // unsupported texture format assert(0); return false; } return basisu::write_vec_to_file(pFilename, filedata); } bool write_compressed_texture_file(const char* pFilename, const gpu_image& g) { gpu_image_vec v; v.push_back(g); return write_compressed_texture_file(pFilename, v); } } // basisu