bool ktx_texture::check_header() const { if (((get_num_faces() != 1) && (get_num_faces() != 6)) || (!m_header.m_pixelWidth)) return false; if ((!m_header.m_pixelHeight) && (m_header.m_pixelDepth)) return false; if ((get_num_faces() == 6) && ((m_header.m_pixelDepth) || (!m_header.m_pixelHeight))) return false; #if 0 if (m_header.m_numberOfMipmapLevels) { const uint32_t max_mipmap_dimension = 1U << (m_header.m_numberOfMipmapLevels - 1U); if (max_mipmap_dimension > (VOGL_MAX(VOGL_MAX(m_header.m_pixelWidth, m_header.m_pixelHeight), m_header.m_pixelDepth))) return false; } #endif return true; }
bool file_utils::remove_extension(dynamic_string &filename) { int sep = -1; #if defined(PLATFORM_WINDOWS) int rightmost_backslash = filename.find_right('\\'); int rightmost_forwardslash = filename.find_right('/'); sep = VOGL_MAX(rightmost_backslash, rightmost_forwardslash); #endif if (sep < 0) sep = filename.find_right('/'); int dot = filename.find_right('.'); if (dot < sep) return false; filename.left(dot); return true; }
bool ktx_texture::read_from_stream(data_stream_serializer &serializer) { clear(); // Read header if (serializer.read(&m_header, 1, sizeof(m_header)) != sizeof(ktx_header)) return false; // Check header if (memcmp(s_ktx_file_id, m_header.m_identifier, sizeof(m_header.m_identifier))) return false; if ((m_header.m_endianness != KTX_OPPOSITE_ENDIAN) && (m_header.m_endianness != KTX_ENDIAN)) return false; m_opposite_endianness = (m_header.m_endianness == KTX_OPPOSITE_ENDIAN); if (m_opposite_endianness) { m_header.endian_swap(); if ((m_header.m_glTypeSize != sizeof(uint8_t)) && (m_header.m_glTypeSize != sizeof(uint16_t)) && (m_header.m_glTypeSize != sizeof(uint32_t))) return false; } if (!check_header()) return false; if (!compute_pixel_info()) { #if VOGL_KTX_PVRTEX_WORKAROUNDS // rg [9/10/13] - moved this check into here, instead of in compute_pixel_info(), but need to retest it. if ((!m_header.m_glInternalFormat) && (!m_header.m_glType) && (!m_header.m_glTypeSize) && (!m_header.m_glBaseInternalFormat)) { // PVRTexTool writes bogus headers when outputting ETC1. console::warning("ktx_texture::compute_pixel_info: Header doesn't specify any format, assuming ETC1 and hoping for the best\n"); m_header.m_glBaseInternalFormat = KTX_RGB; m_header.m_glInternalFormat = KTX_ETC1_RGB8_OES; m_header.m_glTypeSize = 1; m_block_dim = 4; m_bytes_per_block = 8; } else #endif return false; } uint8_t pad_bytes[3]; // Read the key value entries uint32_t num_key_value_bytes_remaining = m_header.m_bytesOfKeyValueData; while (num_key_value_bytes_remaining) { if (num_key_value_bytes_remaining < sizeof(uint32_t)) return false; uint32_t key_value_byte_size; if (serializer.read(&key_value_byte_size, 1, sizeof(uint32_t)) != sizeof(uint32_t)) return false; num_key_value_bytes_remaining -= sizeof(uint32_t); if (m_opposite_endianness) key_value_byte_size = utils::swap32(key_value_byte_size); if (key_value_byte_size > num_key_value_bytes_remaining) return false; uint8_vec key_value_data; if (key_value_byte_size) { key_value_data.resize(key_value_byte_size); if (serializer.read(&key_value_data[0], 1, key_value_byte_size) != key_value_byte_size) return false; } m_key_values.push_back(key_value_data); uint32_t padding = 3 - ((key_value_byte_size + 3) % 4); if (padding) { if (serializer.read(pad_bytes, 1, padding) != padding) return false; } num_key_value_bytes_remaining -= key_value_byte_size; if (num_key_value_bytes_remaining < padding) return false; num_key_value_bytes_remaining -= padding; } // Now read the mip levels uint32_t total_faces = get_num_mips() * get_array_size() * get_num_faces() * get_depth(); if ((!total_faces) || (total_faces > 65535)) return false; // See Section 2.8 of KTX file format: No rounding to block sizes should be applied for block compressed textures. // OK, I'm going to break that rule otherwise KTX can only store a subset of textures that DDS can handle for no good reason. #if 0 const uint32_t mip0_row_blocks = m_header.m_pixelWidth / m_block_dim; const uint32_t mip0_col_blocks = VOGL_MAX(1, m_header.m_pixelHeight) / m_block_dim; #else const uint32_t mip0_row_blocks = (m_header.m_pixelWidth + m_block_dim - 1) / m_block_dim; const uint32_t mip0_col_blocks = (VOGL_MAX(1, m_header.m_pixelHeight) + m_block_dim - 1) / m_block_dim; #endif if ((!mip0_row_blocks) || (!mip0_col_blocks)) return false; const uint32_t mip0_depth = VOGL_MAX(1, m_header.m_pixelDepth); VOGL_NOTE_UNUSED(mip0_depth); bool has_valid_image_size_fields = true; bool disable_mip_and_cubemap_padding = false; #if VOGL_KTX_PVRTEX_WORKAROUNDS { // PVRTexTool has a bogus KTX writer that doesn't write any imageSize fields. Nice. size_t expected_bytes_remaining = 0; for (uint32_t mip_level = 0; mip_level < get_num_mips(); mip_level++) { uint32_t mip_width, mip_height, mip_depth; get_mip_dim(mip_level, mip_width, mip_height, mip_depth); const uint32_t mip_row_blocks = (mip_width + m_block_dim - 1) / m_block_dim; const uint32_t mip_col_blocks = (mip_height + m_block_dim - 1) / m_block_dim; if ((!mip_row_blocks) || (!mip_col_blocks)) return false; expected_bytes_remaining += sizeof(uint32_t); if ((!m_header.m_numberOfArrayElements) && (get_num_faces() == 6)) { for (uint32_t face = 0; face < get_num_faces(); face++) { uint32_t slice_size = mip_row_blocks * mip_col_blocks * m_bytes_per_block; expected_bytes_remaining += slice_size; uint32_t num_cube_pad_bytes = 3 - ((slice_size + 3) % 4); expected_bytes_remaining += num_cube_pad_bytes; } } else { uint32_t total_mip_size = 0; for (uint32_t array_element = 0; array_element < get_array_size(); array_element++) { for (uint32_t face = 0; face < get_num_faces(); face++) { for (uint32_t zslice = 0; zslice < mip_depth; zslice++) { uint32_t slice_size = mip_row_blocks * mip_col_blocks * m_bytes_per_block; total_mip_size += slice_size; } } } expected_bytes_remaining += total_mip_size; uint32_t num_mip_pad_bytes = 3 - ((total_mip_size + 3) % 4); expected_bytes_remaining += num_mip_pad_bytes; } } if (serializer.get_stream()->get_remaining() < expected_bytes_remaining) { has_valid_image_size_fields = false; disable_mip_and_cubemap_padding = true; console::warning("ktx_texture::read_from_stream: KTX file size is smaller than expected - trying to read anyway without imageSize fields\n"); } } #endif for (uint32_t mip_level = 0; mip_level < get_num_mips(); mip_level++) { uint32_t mip_width, mip_height, mip_depth; get_mip_dim(mip_level, mip_width, mip_height, mip_depth); const uint32_t mip_row_blocks = (mip_width + m_block_dim - 1) / m_block_dim; const uint32_t mip_col_blocks = (mip_height + m_block_dim - 1) / m_block_dim; if ((!mip_row_blocks) || (!mip_col_blocks)) return false; uint32_t image_size = 0; if (!has_valid_image_size_fields) { if ((!m_header.m_numberOfArrayElements) && (get_num_faces() == 6)) { // The KTX file format has an exception for plain cubemap textures, argh. image_size = mip_row_blocks * mip_col_blocks * m_bytes_per_block; } else { image_size = mip_depth * mip_row_blocks * mip_col_blocks * m_bytes_per_block * get_array_size() * get_num_faces(); } } else { if (serializer.read(&image_size, 1, sizeof(image_size)) != sizeof(image_size)) return false; if (m_opposite_endianness) image_size = utils::swap32(image_size); } if (!image_size) return false; uint32_t total_mip_size = 0; // The KTX file format has an exception for plain cubemap textures, argh. if ((!m_header.m_numberOfArrayElements) && (get_num_faces() == 6)) { // plain non-array cubemap for (uint32_t face = 0; face < get_num_faces(); face++) { VOGL_ASSERT(m_image_data.size() == get_image_index(mip_level, 0, face, 0)); m_image_data.push_back(uint8_vec()); uint8_vec &image_data = m_image_data.back(); image_data.resize(image_size); if (serializer.read(&image_data[0], 1, image_size) != image_size) return false; if (m_opposite_endianness) utils::endian_swap_mem(&image_data[0], image_size, m_header.m_glTypeSize); uint32_t num_cube_pad_bytes = disable_mip_and_cubemap_padding ? 0 : (3 - ((image_size + 3) % 4)); if (serializer.read(pad_bytes, 1, num_cube_pad_bytes) != num_cube_pad_bytes) return false; total_mip_size += image_size + num_cube_pad_bytes; } } else { uint32_t num_image_bytes_remaining = image_size; // 1D, 2D, 3D (normal or array texture), or array cubemap for (uint32_t array_element = 0; array_element < get_array_size(); array_element++) { for (uint32_t face = 0; face < get_num_faces(); face++) { for (uint32_t zslice = 0; zslice < mip_depth; zslice++) { uint32_t slice_size = mip_row_blocks * mip_col_blocks * m_bytes_per_block; if ((!slice_size) || (slice_size > num_image_bytes_remaining)) return false; uint32_t image_index = get_image_index(mip_level, array_element, face, zslice); m_image_data.ensure_element_is_valid(image_index); uint8_vec &image_data = m_image_data[image_index]; image_data.resize(slice_size); if (serializer.read(&image_data[0], 1, slice_size) != slice_size) return false; if (m_opposite_endianness) utils::endian_swap_mem(&image_data[0], slice_size, m_header.m_glTypeSize); num_image_bytes_remaining -= slice_size; total_mip_size += slice_size; } } } if (num_image_bytes_remaining) { VOGL_ASSERT_ALWAYS; return false; } } uint32_t num_mip_pad_bytes = disable_mip_and_cubemap_padding ? 0 : (3 - ((total_mip_size + 3) % 4)); if (serializer.read(pad_bytes, 1, num_mip_pad_bytes) != num_mip_pad_bytes) return false; } return true; }