• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "image/loaders/image_loader_ktx.h"
17 
18 #include <algorithm>
19 #include <cstring>
20 
21 #include <base/containers/string_view.h>
22 #include <base/containers/unique_ptr.h>
23 #include <core/io/intf_file_manager.h>
24 #include <core/log.h>
25 #include <core/namespace.h>
26 
27 #include "image/image_loader_manager.h"
28 #include "image/loaders/gl_util.h"
29 
30 CORE_BEGIN_NAMESPACE()
31 namespace {
32 using BASE_NS::array_view;
33 using BASE_NS::CloneData;
34 using BASE_NS::Format;
35 using BASE_NS::make_unique;
36 using BASE_NS::move;
37 using BASE_NS::string_view;
38 using BASE_NS::unique_ptr;
39 using BASE_NS::vector;
40 
ReadU32(const uint8_t ** data)41 uint32_t ReadU32(const uint8_t** data)
42 {
43     CORE_ASSERT(data);
44     CORE_ASSERT(*data);
45 
46     uint32_t value;
47     value = *(*data)++;
48     value |= *(*data)++ << 8;
49     value |= *(*data)++ << 16;
50     value |= *(*data)++ << 24;
51     return value;
52 }
53 
ReadU32FlipEndian(const uint8_t ** data)54 uint32_t ReadU32FlipEndian(const uint8_t** data)
55 {
56     CORE_ASSERT(data);
57     CORE_ASSERT(*data);
58 
59     uint32_t value;
60     value = static_cast<uint32_t>(*(*data)++ << 24);
61     value |= static_cast<uint32_t>(*(*data)++ << 16);
62     value |= static_cast<uint32_t>(*(*data)++ << 8);
63     value |= static_cast<uint32_t>(*(*data)++);
64     return value;
65 }
66 
67 #ifdef CORE_READ_KTX_HEADER_STRING
68 // NOTE: Returns null if the value is not a valid null terminated string.
69 //       (i.e. maxBytes was reached before a null was found)
ReadStringZ(const uint8_t ** data,size_t maxBytes,size_t * bytesReadOut)70 string_view ReadStringZ(const uint8_t** data, size_t maxBytes, size_t* bytesReadOut)
71 {
72     CORE_ASSERT(data);
73     CORE_ASSERT(*data);
74     CORE_ASSERT(bytesReadOut);
75 
76     *bytesReadOut = 0;
77 
78     if (maxBytes == 0) {
79         return {};
80     }
81 
82     const auto start = *data;
83     const auto end = start + maxBytes;
84 
85     if (auto const pos = std::find(start, end, 0); pos != end) {
86         *data = pos + 1;
87         *bytesReadOut = static_cast<size_t>(std::distance(start, pos + 1));
88         return { reinterpret_cast<const char*>(start), *bytesReadOut };
89     }
90 
91     return {};
92 }
93 #endif
94 
95 // 12 byte ktx identifier.
96 constexpr const size_t KTX_IDENTIFIER_LENGTH = 12;
97 constexpr const char KTX_IDENTIFIER_REFERENCE[KTX_IDENTIFIER_LENGTH] = { '\xAB', 'K', 'T', 'X', ' ', '1', '1', '\xBB',
98     '\r', '\n', '\x1A', '\n' };
99 constexpr const uint32_t KTX_FILE_ENDIANNESS = 0x04030201;
100 constexpr const uint32_t KTX_FILE_ENDIANNESS_FLIPPED = 0x01020304;
101 
102 struct KtxHeader {
103     int8_t identifier[KTX_IDENTIFIER_LENGTH];
104     uint32_t endianness;
105     uint32_t glType;
106     uint32_t glTypeSize;
107     uint32_t glFormat;
108     uint32_t glInternalFormat;
109     uint32_t glBaseInternalFormat;
110     uint32_t pixelWidth;
111     uint32_t pixelHeight;
112     uint32_t pixelDepth;
113     uint32_t numberOfArrayElements;
114     uint32_t numberOfFaces;
115     uint32_t numberOfMipmapLevels;
116     uint32_t bytesOfKeyValueData;
117 };
118 
119 constexpr const size_t KTX_HEADER_LENGTH = sizeof(KtxHeader);
120 
GetImageType(const KtxHeader & header)121 IImageContainer::ImageType GetImageType(const KtxHeader& header)
122 {
123     if (header.pixelHeight == 0 && header.pixelDepth == 0) {
124         return IImageContainer::ImageType::TYPE_1D;
125     } else if (header.pixelDepth == 0) {
126         return IImageContainer::ImageType::TYPE_2D;
127     } else {
128         return IImageContainer::ImageType::TYPE_3D;
129     }
130 }
131 
GetImageViewType(const KtxHeader & header,IImageContainer::ImageType imageType)132 IImageContainer::ImageViewType GetImageViewType(const KtxHeader& header, IImageContainer::ImageType imageType)
133 {
134     const bool isArray = (header.numberOfArrayElements != 0);
135     const bool isCubeMap = (header.numberOfFaces == 6);
136 
137     if (isCubeMap) {
138         if (imageType == IImageContainer::ImageType::TYPE_3D || imageType == IImageContainer::ImageType::TYPE_1D) {
139             // Cubemaps must be 2d textures.
140             return IImageContainer::ImageViewType::VIEW_TYPE_MAX_ENUM;
141         }
142         return (isArray ? IImageContainer::ImageViewType::VIEW_TYPE_CUBE_ARRAY
143                         : IImageContainer::ImageViewType::VIEW_TYPE_CUBE);
144     } else if (isArray) {
145         switch (imageType) {
146             case IImageContainer::ImageType::TYPE_1D:
147                 return IImageContainer::ImageViewType::VIEW_TYPE_1D_ARRAY;
148             case IImageContainer::ImageType::TYPE_2D:
149                 return IImageContainer::ImageViewType::VIEW_TYPE_2D_ARRAY;
150             case IImageContainer::ImageType::TYPE_3D:
151                 // 3d arrays are not supported.
152                 return IImageContainer::ImageViewType::VIEW_TYPE_MAX_ENUM;
153             case IImageContainer::ImageType::TYPE_MAX_ENUM:
154                 return IImageContainer::ImageViewType::VIEW_TYPE_MAX_ENUM;
155         }
156     } else {
157         switch (imageType) {
158             case IImageContainer::ImageType::TYPE_1D:
159                 return IImageContainer::ImageViewType::VIEW_TYPE_1D;
160             case IImageContainer::ImageType::TYPE_2D:
161                 return IImageContainer::ImageViewType::VIEW_TYPE_2D;
162             case IImageContainer::ImageType::TYPE_3D:
163                 return IImageContainer::ImageViewType::VIEW_TYPE_3D;
164             case IImageContainer::ImageType::TYPE_MAX_ENUM:
165                 return IImageContainer::ImageViewType::VIEW_TYPE_MAX_ENUM;
166         }
167     }
168 
169     return IImageContainer::ImageViewType::VIEW_TYPE_MAX_ENUM;
170 }
171 
172 class KtxImage final : public IImageContainer {
173 public:
174     KtxImage() = default;
175 
KtxImage(unique_ptr<uint8_t[]> && fileBytes,size_t fileBytesLength)176     KtxImage(unique_ptr<uint8_t[]>&& fileBytes, size_t fileBytesLength)
177         : fileBytes_(CORE_NS::move(fileBytes)), fileBytesLength_(fileBytesLength)
178     {}
179 
180     ~KtxImage() noexcept override = default;
181 
182     using Ptr = BASE_NS::unique_ptr<KtxImage, Deleter>;
183 
GetImageDesc() const184     const ImageDesc& GetImageDesc() const override
185     {
186         return imageDesc_;
187     }
188 
GetData() const189     array_view<const uint8_t> GetData() const override
190     {
191         return array_view<const uint8_t>(imageBytes_, imageBytesLength_);
192     }
193 
GetBufferImageCopies() const194     array_view<const SubImageDesc> GetBufferImageCopies() const override
195     {
196         return imageBuffers_;
197     }
198 
ProcessMipmapLevel(KtxImage::Ptr & image,const size_t imageBufferIndex,const uint32_t currentImageElementOffset,const GlImageFormatInfo & formatInfo,const uint32_t elementWidth,const uint32_t elementHeight,const uint32_t mipmapLevel,const uint32_t faceCount,const uint32_t arrayElementCount,const uint32_t elementDepth,const size_t subelementLength)199     static void ProcessMipmapLevel(KtxImage::Ptr& image, const size_t imageBufferIndex,
200         const uint32_t currentImageElementOffset, const GlImageFormatInfo& formatInfo, const uint32_t elementWidth,
201         const uint32_t elementHeight, const uint32_t mipmapLevel, const uint32_t faceCount,
202         const uint32_t arrayElementCount, const uint32_t elementDepth, const size_t subelementLength)
203     {
204         image->imageBuffers_[imageBufferIndex].bufferOffset = currentImageElementOffset;
205 
206         // Vulkan requires the bufferRowLength and bufferImageHeight to be multiple of block width / height.
207         const auto blockWidth = formatInfo.blockWidth;
208         const auto blockHeight = formatInfo.blockHeight;
209         const auto widthBlockCount = (elementWidth + (blockWidth - 1)) / blockWidth;
210         const auto heightBlockCount = (elementHeight + (blockHeight - 1)) / blockHeight;
211         image->imageBuffers_[imageBufferIndex].bufferRowLength = widthBlockCount * blockWidth;
212         image->imageBuffers_[imageBufferIndex].bufferImageHeight = heightBlockCount * blockHeight;
213 
214         image->imageBuffers_[imageBufferIndex].mipLevel = mipmapLevel;
215 
216         image->imageBuffers_[imageBufferIndex].layerCount = faceCount * arrayElementCount;
217 
218         image->imageBuffers_[imageBufferIndex].width = elementWidth;
219         image->imageBuffers_[imageBufferIndex].height = elementHeight;
220         image->imageBuffers_[imageBufferIndex].depth = elementDepth;
221 
222         //
223         // Vulkan requires that: "If the calling command's VkImage parameter's
224         // format is not a depth/stencil format or a multi-planar format, then
225         // bufferOffset must be a multiple of the format's texel block size."
226         //
227         // This is a bit problematic as the ktx format requires padding only to
228         // 4 bytes and contains a 4 byte "lodsize" value between each data section.
229         // this causes all formats with bytesPerBlock > 4 to be misaligned.
230         //
231         // NOTE: try to figure out if there is a better way.
232         //
233         const uint32_t bytesPerBlock = formatInfo.bitsPerBlock / 8u;
234         if (mipmapLevel > 0 && bytesPerBlock > 4u) {
235             // We can assume that moving the data to the previous valid position
236             // is ok as it will only overwrite the now unnecessary "lodsize" value.
237             const uint32_t validOffset =
238                 static_cast<uint32_t>(currentImageElementOffset / bytesPerBlock * bytesPerBlock);
239             uint8_t* imageBytes = const_cast<uint8_t*>(image->imageBytes_);
240             if (imageBytes && (image->imageBytesLength_ - validOffset >= subelementLength)) {
241                 int ret = memmove_s(imageBytes + validOffset, subelementLength,
242                     imageBytes + currentImageElementOffset, subelementLength);
243                 if (ret != 0) {
244                     CORE_LOG_E("memmove failed.");
245                 }
246             } else {
247                 CORE_LOG_E("memmove failed.");
248             }
249             image->imageBuffers_[imageBufferIndex].bufferOffset = validOffset;
250         }
251     }
252 
ResolveGpuImageDesc(ImageDesc & desc,const KtxHeader & ktx,const CORE_NS::GlImageFormatInfo & formatInfo,const uint32_t loadFlags,const uint32_t inputMipCount,const uint32_t arrayElementCount,const uint32_t faceCount)253     static bool ResolveGpuImageDesc(ImageDesc& desc, const KtxHeader& ktx, const CORE_NS::GlImageFormatInfo& formatInfo,
254         const uint32_t loadFlags, const uint32_t inputMipCount, const uint32_t arrayElementCount,
255         const uint32_t faceCount)
256     {
257         if ((loadFlags & IImageLoaderManager::IMAGE_LOADER_FORCE_SRGB_BIT) != 0) {
258             desc.format = formatInfo.coreFormatForceSrgb;
259         } else if ((loadFlags & IImageLoaderManager::IMAGE_LOADER_FORCE_LINEAR_RGB_BIT) != 0) {
260             desc.format = formatInfo.coreFormatForceLinear;
261         } else {
262             desc.format = formatInfo.coreFormat;
263         }
264 
265         if ((desc.imageFlags & ImageFlags::FLAGS_CUBEMAP_BIT) != 0) {
266         }
267 
268         desc.imageType = GetImageType(ktx);
269         desc.imageViewType = GetImageViewType(ktx, desc.imageType);
270         if (desc.format == Format::BASE_FORMAT_UNDEFINED || desc.imageType == ImageType::TYPE_MAX_ENUM ||
271             desc.imageViewType == ImageViewType::VIEW_TYPE_MAX_ENUM) {
272             CORE_LOG_D(
273                 "glFormat=%u imageType=%u imageViewType=%u", ktx.glInternalFormat, desc.imageType, desc.imageViewType);
274             return false;
275         }
276 
277         desc.mipCount = inputMipCount;
278 
279         // NOTE: depth here means 3D textures, not color channels.
280         // In 1D and 2D textures the height and depth might be 0.
281         desc.width = ktx.pixelWidth;
282         desc.height = ((ktx.pixelHeight == 0) ? 1 : ktx.pixelHeight);
283         desc.depth = ((ktx.pixelDepth == 0) ? 1 : ktx.pixelDepth);
284         desc.layerCount = arrayElementCount * faceCount;
285 
286         const bool compressed = (desc.imageFlags & ImageFlags::FLAGS_COMPRESSED_BIT) != 0;
287         const bool imageRequestingMips = (desc.imageFlags & ImageFlags::FLAGS_REQUESTING_MIPMAPS_BIT) != 0;
288         const bool loaderRequestingMips = (loadFlags & IImageLoaderManager::IMAGE_LOADER_GENERATE_MIPS) != 0;
289         if (!compressed && (imageRequestingMips || loaderRequestingMips)) {
290             desc.imageFlags |= ImageFlags::FLAGS_REQUESTING_MIPMAPS_BIT;
291             uint32_t mipsize = (desc.width > desc.height) ? desc.width : desc.height;
292             desc.mipCount = 0;
293             while (mipsize > 0) {
294                 desc.mipCount++;
295                 mipsize >>= 1;
296             }
297         } else {
298             desc.imageFlags &= ~ImageFlags::FLAGS_REQUESTING_MIPMAPS_BIT;
299         }
300 
301         return true;
302     }
303 
ResolveImageDesc(const KtxHeader & ktx,const GlImageFormatInfo & formatInfo,uint32_t loadFlags,uint32_t inputMipCount,const uint32_t arrayElementCount,uint32_t faceCount,ImageDesc & outImageDesc)304     static bool ResolveImageDesc(const KtxHeader& ktx, const GlImageFormatInfo& formatInfo, uint32_t loadFlags,
305         uint32_t inputMipCount, const uint32_t arrayElementCount, uint32_t faceCount, ImageDesc& outImageDesc)
306     {
307         ImageDesc desc;
308 
309         desc.blockPixelWidth = formatInfo.blockWidth;
310         desc.blockPixelHeight = formatInfo.blockHeight;
311         desc.blockPixelDepth = formatInfo.blockDepth;
312         desc.bitsPerBlock = formatInfo.bitsPerBlock;
313 
314         // If there are six faces this is a cube.
315         if (faceCount == 6u) {
316             desc.imageFlags |= ImageFlags::FLAGS_CUBEMAP_BIT;
317         }
318 
319         // Is compressed?
320         if ((ktx.glType == 0) && (ktx.glFormat == 0)) {
321             desc.imageFlags |= ImageFlags::FLAGS_COMPRESSED_BIT;
322         } else {
323             // Mipmap generation works only if image is not using a compressed format.
324             // In ktx mip count of 0 (instead of 1) means requesting generating full chain of mipmaps.
325             if ((ktx.numberOfMipmapLevels == 0)) {
326                 desc.imageFlags |= ImageFlags::FLAGS_REQUESTING_MIPMAPS_BIT;
327             }
328         }
329 
330         if (!ResolveGpuImageDesc(desc, ktx, formatInfo, loadFlags, inputMipCount, arrayElementCount, faceCount)) {
331             return false;
332         }
333 
334         outImageDesc = desc;
335         return true;
336     }
337 
VerifyKtxInfo(const KtxHeader & ktx,const GlImageFormatInfo & formatInfo)338     static bool VerifyKtxInfo(const KtxHeader& ktx, const GlImageFormatInfo& formatInfo)
339     {
340         if (formatInfo.compressed) {
341             if (ktx.glTypeSize != 1) {
342                 CORE_LOG_D("Invalid typesize for a compressed image.");
343                 return false;
344             }
345             if (ktx.glFormat != 0) {
346                 CORE_LOG_D("Invalid glFormat for a compressed image.");
347                 return false;
348             }
349             if (ktx.glType != 0) {
350                 CORE_LOG_D("Invalid glType for a compressed image.");
351                 return false;
352             }
353         }
354 
355         if (ktx.pixelDepth != 0 && ktx.pixelHeight == 0) {
356             CORE_LOG_D("No pixelHeight defined for a 3d texture.");
357             return false;
358         }
359 
360         return true;
361     }
362 
CreateImage(KtxImage::Ptr image,const KtxHeader & ktx,uint32_t loadFlags,const uint8_t * data,bool isEndianFlipped)363     static ImageLoaderManager::LoadResult CreateImage(
364         KtxImage::Ptr image, const KtxHeader& ktx, uint32_t loadFlags, const uint8_t* data, bool isEndianFlipped)
365     {
366         // Mark this as the image data starting position.
367         const uint8_t* ktxDataSection = data;
368 
369         const uint32_t inputMipCount = ktx.numberOfMipmapLevels == 0 ? 1 : ktx.numberOfMipmapLevels;
370         const uint32_t arrayElementCount = ktx.numberOfArrayElements == 0 ? 1 : ktx.numberOfArrayElements;
371 
372         //
373         // Populate the image descriptor.
374         //
375         const GlImageFormatInfo formatInfo = GetFormatInfo(ktx.glInternalFormat);
376         if (!ResolveImageDesc(
377                 ktx, formatInfo, loadFlags, inputMipCount, arrayElementCount, ktx.numberOfFaces, image->imageDesc_)) {
378             return ImageLoaderManager::ResultFailure("Image not supported.");
379         }
380 
381         if (!VerifyKtxInfo(ktx, formatInfo)) {
382             return ImageLoaderManager::ResultFailure("Invalid ktx data.");
383         }
384 
385         if ((loadFlags & IImageLoaderManager::IMAGE_LOADER_METADATA_ONLY) == 0) {
386             uint32_t elementWidth = image->imageDesc_.width;
387             uint32_t elementHeight = image->imageDesc_.height;
388             uint32_t elementDepth = image->imageDesc_.depth;
389 
390             // Create buffer info for each mipmap level.
391             // NOTE: One BufferImageCopy can copy all the layers and faces in one step.
392             image->imageBuffers_.resize(static_cast<size_t>(inputMipCount));
393 
394             const auto myReadU32 = isEndianFlipped ? ReadU32FlipEndian : ReadU32;
395 
396             // for non-array cubemaps imageSize is the size of one face, but for other types the total size.
397             const auto iterations = (ktx.numberOfArrayElements == 0 && ktx.numberOfFaces == 6u) ? 6u : 1u;
398 
399             // Fill the image subelement buffer info.
400             size_t imageBufferIndex = 0;
401             for (uint32_t mipmapLevel = 0; mipmapLevel < inputMipCount; ++mipmapLevel) {
402                 if (data < ktxDataSection) {
403                     CORE_LOG_D("Trying to jump out of the parsed data.");
404                     return ImageLoaderManager::ResultFailure("Invalid ktx data.");
405                 }
406                 if (sizeof(uint32_t) >=
407                     image->fileBytesLength_ - static_cast<uintptr_t>(data - image->fileBytes_.get())) {
408                     CORE_LOG_D("Not enough data in the bytearray.");
409                     return ImageLoaderManager::ResultFailure("Invalid ktx data.");
410                 }
411 
412                 const size_t lodSize = myReadU32(&data);
413                 // Pad to to a multiple of 4.
414                 const size_t lodSizePadded = lodSize + ((~lodSize + 1) & (4u - 1u));
415 
416                 const uint64_t totalSizePadded = static_cast<uint64_t>(lodSizePadded) * iterations;
417                 if (totalSizePadded >= UINT32_MAX) {
418                     CORE_LOG_D("imageSize too big");
419                     return ImageLoaderManager::ResultFailure("Invalid ktx data.");
420                 }
421 
422                 const auto fileBytesLeft =
423                     image->fileBytesLength_ - static_cast<uintptr_t>(data - image->fileBytes_.get());
424                 if (totalSizePadded > fileBytesLeft) {
425                     CORE_LOG_D("Not enough data for the element");
426                     CORE_LOG_V(
427                         "  mips=%u faces=%u arrayElements=%u", inputMipCount, ktx.numberOfFaces, arrayElementCount);
428                     CORE_LOG_V("  mipmapLevel=%u lodsize=%zu end=%zu filesize=%zu.", mipmapLevel, lodSize,
429                         data - image->fileBytes_.get() + lodSize, image->fileBytesLength_);
430                     return ImageLoaderManager::ResultFailure("Invalid ktx data.");
431                 }
432 
433                 const uint32_t currentImageElementOffset = static_cast<uint32_t>(data - image->imageBytes_);
434                 CORE_ASSERT_MSG(currentImageElementOffset % 4u == 0, "Offset must be aligned to 4 bytes");
435                 ProcessMipmapLevel(image, imageBufferIndex, currentImageElementOffset, formatInfo, elementWidth,
436                     elementHeight, mipmapLevel, ktx.numberOfFaces, arrayElementCount, elementDepth,
437                     static_cast<uint32_t>(totalSizePadded));
438 
439                 // Move to the next buffer if any.
440                 imageBufferIndex++;
441 
442                 // Figure out the next mipmap level sizes. The dimentions of each level are half of the previous.
443                 elementWidth /= 2u;
444                 elementWidth = (elementWidth <= 1) ? 1 : elementWidth;
445 
446                 elementHeight /= 2u;
447                 elementHeight = (elementHeight <= 1) ? 1 : elementHeight;
448 
449                 elementDepth /= 2u;
450                 elementDepth = (elementDepth <= 1) ? 1 : elementDepth;
451 
452                 // Skip to the next lod level.
453                 // NOTE: in theory there could be packing here for each face. in that case we would need to
454                 // make a separate subelement of each face.
455                 data += totalSizePadded;
456             }
457 
458             if (data != (image->fileBytes_.get() + image->fileBytesLength_)) {
459                 CORE_LOG_D("File data left over.");
460                 return ImageLoaderManager::ResultFailure("Invalid ktx data.");
461             }
462         }
463         return ImageLoaderManager::ResultSuccess(CORE_NS::move(image));
464     }
465 
466     // Actual ktx loading implementation.
Load(unique_ptr<uint8_t[]> fileBytes,uint64_t fileBytesLength,uint32_t loadFlags)467     static ImageLoaderManager::LoadResult Load(
468         unique_ptr<uint8_t[]> fileBytes, uint64_t fileBytesLength, uint32_t loadFlags)
469     {
470         if (!fileBytes) {
471             return ImageLoaderManager::ResultFailure("Input data must not be null.");
472         } else if (fileBytesLength < KTX_HEADER_LENGTH) {
473             return ImageLoaderManager::ResultFailure("Not enough data for parsing ktx.");
474         }
475 
476         // Populate the image object.
477         auto image = KtxImage::Ptr(new KtxImage(move(fileBytes), static_cast<size_t>(fileBytesLength)));
478         if (!image) {
479             return ImageLoaderManager::ResultFailure("Loading image failed.");
480         }
481 
482         const uint8_t* data = image->fileBytes_.get();
483         const auto ktxHeader = ReadHeader(&data);
484         // Check that the header values make sense.
485         if (memcmp(ktxHeader.identifier, KTX_IDENTIFIER_REFERENCE, KTX_IDENTIFIER_LENGTH) != 0) {
486             CORE_LOG_D("Ktx invalid file identifier.");
487             return ImageLoaderManager::ResultFailure("Invalid ktx data.");
488         } else if (ktxHeader.endianness != KTX_FILE_ENDIANNESS && ktxHeader.endianness != KTX_FILE_ENDIANNESS_FLIPPED) {
489             CORE_LOG_D("Ktx invalid endian marker.");
490             return ImageLoaderManager::ResultFailure("Invalid ktx data.");
491         } else if (ktxHeader.numberOfFaces != 1 && ktxHeader.numberOfFaces != 6u) { // 1 for regular, 6 for cubemaps
492             CORE_LOG_D("Ktx invalid numberOfFaces.");
493             return ImageLoaderManager::ResultFailure("Invalid ktx data.");
494         } else if (ktxHeader.pixelWidth == 0) {
495             CORE_LOG_D("Ktx pixelWidth can't be 0.");
496             return ImageLoaderManager::ResultFailure("Invalid ktx data.");
497         }
498 
499         if ((loadFlags & IImageLoaderManager::IMAGE_LOADER_METADATA_ONLY) == 0) {
500             if (ktxHeader.bytesOfKeyValueData >
501                 image->fileBytesLength_ - static_cast<uintptr_t>(data - image->fileBytes_.get())) {
502                 CORE_LOG_D("Ktx bytesOfKeyValueData too large.");
503                 return ImageLoaderManager::ResultFailure("Invalid ktx data.");
504             }
505 
506             ReadKeyValueData(ktxHeader, &data);
507             // NOTE: Point to the start of the actual data of the first texture
508             // (Jump over the first lod offset uint32_t (4 bytes)
509             const size_t headerLength = static_cast<size_t>(data - image->fileBytes_.get()) + sizeof(uint32_t);
510             image->imageBytes_ = data + sizeof(uint32_t);
511             image->imageBytesLength_ = image->fileBytesLength_ - headerLength;
512         }
513         const bool isEndianFlipped = (ktxHeader.endianness == KTX_FILE_ENDIANNESS_FLIPPED);
514         if (isEndianFlipped && ktxHeader.glTypeSize > 1) {
515             CORE_ASSERT_MSG(true, "NOTE: must convert all data to correct endianness");
516             return ImageLoaderManager::ResultFailure("Image not supported.");
517         }
518 
519         return CreateImage(move(image), ktxHeader, loadFlags, data, isEndianFlipped);
520     }
521 
522 protected:
Destroy()523     void Destroy() override
524     {
525         delete this;
526     }
527 
528 private:
ReadHeader(const uint8_t ** data)529     static KtxHeader ReadHeader(const uint8_t** data)
530     {
531         // Read the identifier.
532         KtxHeader ktxHeader = {};
533 
534         CloneData(ktxHeader.identifier, sizeof(ktxHeader.identifier), *data, KTX_IDENTIFIER_LENGTH);
535         *data += KTX_IDENTIFIER_LENGTH;
536 
537         // Check file endianness.
538         ktxHeader.endianness = ReadU32(data);
539 
540         const bool isEndianFlipped = (ktxHeader.endianness == KTX_FILE_ENDIANNESS_FLIPPED);
541 
542         const auto myReadU32 = isEndianFlipped ? ReadU32FlipEndian : ReadU32;
543 
544         ktxHeader.glType = myReadU32(data);
545         ktxHeader.glTypeSize = myReadU32(data);
546         ktxHeader.glFormat = myReadU32(data);
547         ktxHeader.glInternalFormat = myReadU32(data);
548         ktxHeader.glBaseInternalFormat = myReadU32(data);
549         ktxHeader.pixelWidth = myReadU32(data);
550         ktxHeader.pixelHeight = myReadU32(data);
551         ktxHeader.pixelDepth = myReadU32(data);
552         ktxHeader.numberOfArrayElements = myReadU32(data);
553         ktxHeader.numberOfFaces = myReadU32(data);
554         ktxHeader.numberOfMipmapLevels = myReadU32(data);
555         ktxHeader.bytesOfKeyValueData = myReadU32(data);
556 
557         return ktxHeader;
558     }
559 
ReadKeyValueData(const KtxHeader & ktxHeader,const uint8_t ** data)560     static void ReadKeyValueData(const KtxHeader& ktxHeader, const uint8_t** data)
561     {
562 #ifndef CORE_READ_KTX_HEADER_STRING
563         // Skip reading the key-value data for now.
564         *data += ktxHeader.bytesOfKeyValueData;
565 #else
566         const bool isEndianFlipped = (ktxHeader.endianness == KTX_FILE_ENDIANNESS_FLIPPED);
567         const auto myReadU32 = isEndianFlipped ? ReadU32FlipEndian : ReadU32;
568 
569         // Read KTX key-value data.
570         size_t keyValueDataRead = 0;
571         while (keyValueDataRead < ktxHeader.bytesOfKeyValueData) {
572             uint32_t keyAndValueByteSize = myReadU32(data);
573             keyValueDataRead += sizeof(uint32_t);
574 
575             size_t keyBytesRead;
576             const auto key = ReadStringZ(data, keyAndValueByteSize, &keyBytesRead);
577             keyValueDataRead += keyBytesRead;
578 
579             size_t valueBytesRead;
580             const auto value = ReadStringZ(data, keyAndValueByteSize - keyBytesRead, &valueBytesRead);
581             keyValueDataRead += valueBytesRead;
582 
583             if (!key.empty() && !value.empty()) {
584                 // NOTE: The key-value data is not used for anything. Just printing to log.
585                 CORE_LOG_V("KTX metadata: '%s' : '%s'", key.data(), value.data());
586             }
587 
588             // Pad to a multiple of 4 bytes.
589             const size_t padding = (~keyAndValueByteSize + 1) & (4u - 1u);
590             keyValueDataRead += padding;
591             *data += padding;
592         }
593 #endif
594     }
595 
596     // Saving the whole image file data in one big chunk. This way we don't
597     // need to copy the data to a separate buffer after reading the file. We
598     // will be pointing to the file data anyway. Only downside is the wasted
599     // memory for the file header.
600     unique_ptr<uint8_t[]> fileBytes_;
601     size_t fileBytesLength_ { 0 };
602 
603     // The actual image data part of the file;
604     const uint8_t* imageBytes_ { nullptr };
605     size_t imageBytesLength_ { 0 };
606 
607     ImageDesc imageDesc_;
608     vector<SubImageDesc> imageBuffers_;
609 };
610 
611 class ImageLoaderKtx final : public IImageLoaderManager::IImageLoader {
612 public:
613     // Inherited via ImageManager::IImageLoader
Load(IFile & file,uint32_t loadFlags) const614     ImageLoaderManager::LoadResult Load(IFile& file, uint32_t loadFlags) const override
615     {
616         size_t byteLength = static_cast<size_t>(file.GetLength());
617 
618         if ((loadFlags & IImageLoaderManager::IMAGE_LOADER_METADATA_ONLY) != 0) {
619             // Only load header
620             byteLength = KTX_HEADER_LENGTH;
621         }
622 
623         // Read the file to a buffer.
624         unique_ptr<uint8_t[]> buffer = make_unique<uint8_t[]>(byteLength);
625         const uint64_t read = file.Read(buffer.get(), byteLength);
626         if (read != byteLength) {
627             CORE_LOG_D("Error loading image");
628             return ImageLoaderManager::ResultFailure("Reading file failed.");
629         }
630 
631         return KtxImage::Load(move(buffer), byteLength, loadFlags);
632     }
633 
Load(array_view<const uint8_t> imageFileBytes,uint32_t loadFlags) const634     ImageLoaderManager::LoadResult Load(array_view<const uint8_t> imageFileBytes, uint32_t loadFlags) const override
635     {
636         // NOTE: could reuse this and remove the extra copy here if the data would be given as a unique_ptr.
637         unique_ptr<uint8_t[]> buffer = make_unique<uint8_t[]>(static_cast<size_t>(imageFileBytes.size()));
638         if (buffer) {
639             std::copy(imageFileBytes.begin().ptr(), imageFileBytes.end().ptr(), buffer.get());
640         }
641 
642         return KtxImage::Load(move(buffer), imageFileBytes.size(), loadFlags);
643     }
644 
CanLoad(array_view<const uint8_t> imageFileBytes) const645     bool CanLoad(array_view<const uint8_t> imageFileBytes) const override
646     {
647         // Check for KTX
648         return (memcmp(imageFileBytes.data(), KTX_IDENTIFIER_REFERENCE, KTX_IDENTIFIER_LENGTH) == 0);
649     }
650 
651     // No animated KTX
LoadAnimatedImage(IFile & file,uint32_t loadFlags)652     ImageLoaderManager::LoadAnimatedResult LoadAnimatedImage(IFile& file, uint32_t loadFlags) override
653     {
654         return ImageLoaderManager::ResultFailureAnimated("Animated KTX not supported.");
655     }
656 
LoadAnimatedImage(array_view<const uint8_t> imageFileBytes,uint32_t loadFlags)657     ImageLoaderManager::LoadAnimatedResult LoadAnimatedImage(
658         array_view<const uint8_t> imageFileBytes, uint32_t loadFlags) override
659     {
660         return ImageLoaderManager::ResultFailureAnimated("Animated KTX not supported.");
661     }
662 
663 protected:
664     ~ImageLoaderKtx() = default;
Destroy()665     void Destroy() override
666     {
667         delete this;
668     }
669 };
670 } // namespace
671 
CreateImageLoaderKtx()672 IImageLoaderManager::IImageLoader::Ptr CreateImageLoaderKtx()
673 {
674     return ImageLoaderManager::IImageLoader::Ptr { new ImageLoaderKtx() };
675 }
676 CORE_END_NAMESPACE()
677