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