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