| 1 | // tImageKTX.cpp  |
| 2 | //  |
| 3 | // This knows how to load/save KTX files. It knows the details of the ktx and ktx2 file format and loads the data into  |
| 4 | // multiple tPixel arrays, one for each frame (KTKs may be animated). These arrays may be 'stolen' by tPictures.  |
| 5 | //  |
| 6 | // Copyright (c) 2022-2024 Tristan Grimmer.  |
| 7 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
| 8 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
| 9 | //  |
| 10 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
| 11 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
| 12 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
| 13 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
| 14 | // PERFORMANCE OF THIS SOFTWARE.  |
| 15 |   |
| 16 | #include <Foundation/tString.h>  |
| 17 | #include <Foundation/tSmallFloat.h>  |
| 18 | #include <System/tMachine.h>  |
| 19 | #include "Image/tImageKTX.h"  |
| 20 | #include "Image/tPixelUtil.h"  |
| 21 | #include "Image/tPicture.h"  |
| 22 | #include "bcdec/bcdec.h"  |
| 23 | #include "etcdec/etcdec.h"  |
| 24 | #include "astcenc.h"  |
| 25 | #define KHRONOS_STATIC  |
| 26 | #include "LibKTX/include/ktx.h"  |
| 27 | #include "LibKTX/include/vulkan_core.h"  |
| 28 | #include "LibKTX/include/gl_format.h"  |
| 29 | namespace tImage  |
| 30 | {  |
| 31 |   |
| 32 |   |
| 33 | // Helper functions, enums, and types for parsing KTX files.  |
| 34 | namespace tKTX  |
| 35 | {  |
| 36 | // These figure out the pixel-format, colour-profile, alpha-mode, and channe-type. tPixelFormat does not specify  |
| 37 | // ancilllary properties of the data -- it specified the encoding of the data. The extra information, like the  |
| 38 | // colour-profile itcwas authored in, is stored in tColourProfile, tAlphaMode, and tChannelType. In many cases this  |
| 39 | // satellite information cannot be determined, in which case they get set the their 'unspecified' enumerants.  |
| 40 | void GetFormatInfo_FromGLFormat(tPixelFormat&, tColourProfile&, tAlphaMode&, tChannelType&, uint32 glType, uint32 glFormat, uint32 glInternalFormat);  |
| 41 | void GetFormatInfo_FromVKFormat(tPixelFormat&, tColourProfile&, tAlphaMode&, tChannelType&, uint32 vkFormat);  |
| 42 | }  |
| 43 |   |
| 44 |   |
| 45 | void tKTX::GetFormatInfo_FromGLFormat(tPixelFormat& format, tColourProfile& profile, tAlphaMode& alphaMode, tChannelType& chanType, uint32 glType, uint32 glFormat, uint32 glInternalFormat)  |
| 46 | {  |
| 47 | // For colour profile (the space of the data) we try to make an educated guess. In general only the asset author  |
| 48 | // knows the colour space/profile. For most (non-HDR) pixel formats for colours, we assume the data is sRGB.  |
| 49 | // Floating-point formats are likewise assumed to be in linear-space (and are usually used for HDR images). In  |
| 50 | // addition when the data is probably not colour data (like ATI1/2) we assume it's in linear.  |
| 51 | format = tPixelFormat::Invalid;  |
| 52 | profile = tColourProfile::sRGB;  |
| 53 | alphaMode = tAlphaMode::None;  |
| 54 | chanType = tChannelType::NONE;  |
| 55 |   |
| 56 | // First deal with compressed formats. For these the internal-format must be specified and can be used to determine  |
| 57 | // the format of the data in the ktx file. See https://registry.khronos.org/KTX/specs/1.0/ktxspec_v1.html  |
| 58 | // In cases where it's not a compressed format, the internal-format should not be queried to determine the format  |
| 59 | // of the ktx file data because it represents the desired format the data should be converted to when binding --  |
| 60 | // all we care about is the format of the actual data, and glType/glFormat can be used to determine that.  |
| 61 | // Exception: The internal format is used to determine some non-compressed formats.  |
| 62 | //  |
| 63 | // Regardiing colour profiles. In many cases there are two similar GL pixel formats that differ only in that one of  |
| 64 | // them specifies using sRGB for the RGB channels. For example:  |
| 65 | // SRGB_ALPHA_S3TC_DXT3_EXT : sRGB for RGB, linear for A.  |
| 66 | // RGBA_S3TC_DXT3_EXT : No-sRGB variant.  |
| 67 | // The implication is that RGBA_S3TC_DXT3_EXT should therefore be in linear RGBA space. Unfortunately most files in  |
| 68 | // the wild that have these non-sRGB variants are usually still authored in sRGB-space. Tacent keeps them by default  |
| 69 | // in sRGB-space but you will see a commented-out lRGB tag in the switch below (indicating how it 'should' be).  |
| 70 | #define C(c) case GL_##c  |
| 71 | #define CC(c) case GL_COMPRESSED_##c  |
| 72 | #define F(f) format = tPixelFormat::f;  |
| 73 | #define P(p) profile = tColourProfile::p;  |
| 74 | #define M(m) alphaMode = tAlphaMode::m;  |
| 75 | #define T(t) chanType = tChannelType::t;  |
| 76 | switch (glInternalFormat)  |
| 77 | {  |
| 78 | //  |
| 79 | // BC formats.  |
| 80 | //  |
| 81 | CC(RGB_S3TC_DXT1_EXT): F(BC1DXT1) /*P(lRGB)*/ M(None) T(NONE) break;  |
| 82 | CC(SRGB_S3TC_DXT1_EXT): F(BC1DXT1) break;  |
| 83 | CC(RGBA_S3TC_DXT1_EXT): F(BC1DXT1A) /*P(lRGB)*/ break;  |
| 84 | CC(SRGB_ALPHA_S3TC_DXT1_EXT): F(BC1DXT1A) break;  |
| 85 | CC(RGBA_S3TC_DXT3_EXT): F(BC2DXT2DXT3) /*P(lRGB)*/ break;  |
| 86 | CC(SRGB_ALPHA_S3TC_DXT3_EXT): F(BC2DXT2DXT3) break;  |
| 87 | CC(RGBA_S3TC_DXT5_EXT): F(BC3DXT4DXT5) /*P(lRGB)*/ break;  |
| 88 | CC(SRGB_ALPHA_S3TC_DXT5_EXT): F(BC3DXT4DXT5) break;  |
| 89 | CC(RED_RGTC1): F(BC4ATI1U) P(lRGB) T(UNORM) break;  |
| 90 | CC(SIGNED_RED_RGTC1): F(BC4ATI1S) P(lRGB) T(SNORM) break;  |
| 91 | CC(RG_RGTC2): F(BC5ATI2U) P(lRGB) T(UNORM) break;  |
| 92 | CC(SIGNED_RG_RGTC2): F(BC5ATI2S) P(lRGB) T(SNORM) break;  |
| 93 | CC(RGB_BPTC_UNSIGNED_FLOAT): F(BC6U) P(HDRa) T(UFLOAT) break;  |
| 94 | CC(RGB_BPTC_SIGNED_FLOAT): F(BC6S) P(HDRa) T(SFLOAT) break;  |
| 95 |   |
| 96 | // BPTC AKA BC7 is designed for UNORM data.  |
| 97 | CC(RGBA_BPTC_UNORM): F(BC7) /*P(lRGB)*/ T(UNORM) break;  |
| 98 | CC(SRGB_ALPHA_BPTC_UNORM): F(BC7) T(UNORM) break;  |
| 99 |   |
| 100 | //  |
| 101 | // ETC and EAC formats.  |
| 102 | //  |
| 103 | C(ETC1_RGB8_OES): F(ETC1) break;  |
| 104 | CC(RGB8_ETC2): F(ETC2RGB) /*P(lRGB)*/ break;  |
| 105 | CC(SRGB8_ETC2): F(ETC2RGB) break;  |
| 106 | CC(RGBA8_ETC2_EAC): F(ETC2RGBA) /*P(lRGB)*/ break;  |
| 107 | CC(SRGB8_ALPHA8_ETC2_EAC): F(ETC2RGBA) break;  |
| 108 | CC(RGB8_PUNCHTHROUGH_ALPHA1_ETC2): F(ETC2RGBA1) /*P(lRGB)*/ break;  |
| 109 | CC(SRGB8_PUNCHTHROUGH_ALPHA1_ETC2): F(ETC2RGBA1) break;  |
| 110 |   |
| 111 | // Leaving the R and RG formats in sRGB space.  |
| 112 | CC(R11_EAC): F(EACR11U) T(UINT) break;  |
| 113 | CC(SIGNED_R11_EAC): F(EACR11S) T(SINT) break;  |
| 114 | CC(RG11_EAC): F(EACRG11U) T(UINT) break;  |
| 115 | CC(SIGNED_RG11_EAC): F(EACRG11S) T(SINT) break;  |
| 116 |   |
| 117 | //  |
| 118 | // PVR formats.  |
| 119 | //  |
| 120 | // These are a bit badly named by OpenGL as there is no distinction between 3 and 4 component overall. It is a per-block  |
| 121 | // quantity that determines if it has alpha or not.  |
| 122 | CC(RGBA_PVRTC_4BPPV1_IMG): F(PVRBPP4) P(lRGB) T(UNORM) break; // 4-component PVRTC, 8x8 blocks, unsigned normalized.  |
| 123 | CC(SRGB_ALPHA_PVRTC_4BPPV1_EXT): F(PVRBPP4) P(sRGB) T(UINT) break; // 4-component PVRTC, 8x8 blocks, sRGB.  |
| 124 | CC(RGB_PVRTC_4BPPV1_IMG): F(PVRBPP4) P(lRGB) T(UNORM) break; // 3-component PVRTC, 8x8 blocks, unsigned normalized.  |
| 125 | CC(SRGB_PVRTC_4BPPV1_EXT): F(PVRBPP4) P(sRGB) T(UINT) break; // 3-component PVRTC, 8x8 blocks, sRGB.  |
| 126 | CC(RGBA_PVRTC_2BPPV1_IMG): F(PVRBPP2) P(lRGB) T(UNORM) break; // 4-component PVRTC, 16x8 blocks, unsigned normalized.  |
| 127 | CC(SRGB_ALPHA_PVRTC_2BPPV1_EXT): F(PVRBPP2) P(sRGB) T(UINT) break; // 4-component PVRTC, 16x8 blocks, sRGB.  |
| 128 | CC(RGB_PVRTC_2BPPV1_IMG): F(PVRBPP2) P(lRGB) T(UNORM) break; // 3-component PVRTC, 16x8 blocks, unsigned normalized.  |
| 129 | CC(SRGB_PVRTC_2BPPV1_EXT): F(PVRBPP2) P(sRGB) T(UINT) break; // 3-component PVRTC, 16x8 blocks, sRGB.  |
| 130 | //CC(RGBA_PVRTC_2BPPV2_IMG):  |
| 131 | //CC(SRGB_ALPHA_PVRTC_2BPPV2_IMG):  |
| 132 | //CC(RGBA_PVRTC_4BPPV2_IMG):  |
| 133 | //CC(SRGB_ALPHA_PVRTC_4BPPV2_IMG):  |
| 134 |   |
| 135 | //  |
| 136 | // For ASTC formats we assume HDR-linear space if SRGB not specified.  |
| 137 | //  |
| 138 | // We chose HDR as the default profile because it can load LDR blocks. The other way around doesn't work with  |
| 139 | // with the test images -- the LDR profile doesn't appear capable of loading HDR blocks (they become magenta).  |
| 140 | //  |
| 141 | CC(RGBA_ASTC_4x4_KHR): F(ASTC4X4) P(HDRa) break;  |
| 142 | CC(SRGB8_ALPHA8_ASTC_4x4_KHR): F(ASTC4X4) break;  |
| 143 | CC(RGBA_ASTC_5x4_KHR): F(ASTC5X4) P(HDRa) break;  |
| 144 | CC(SRGB8_ALPHA8_ASTC_5x4_KHR): F(ASTC5X4) break;  |
| 145 | CC(RGBA_ASTC_5x5_KHR): F(ASTC5X5) P(HDRa) break;  |
| 146 | CC(SRGB8_ALPHA8_ASTC_5x5_KHR): F(ASTC5X5) break;  |
| 147 | CC(RGBA_ASTC_6x5_KHR): F(ASTC6X5) P(HDRa) break;  |
| 148 | CC(SRGB8_ALPHA8_ASTC_6x5_KHR): F(ASTC6X5) break;  |
| 149 | CC(RGBA_ASTC_6x6_KHR): F(ASTC6X6) P(HDRa) break;  |
| 150 | CC(SRGB8_ALPHA8_ASTC_6x6_KHR): F(ASTC6X6) break;  |
| 151 | CC(RGBA_ASTC_8x5_KHR): F(ASTC8X5) P(HDRa) break;  |
| 152 | CC(SRGB8_ALPHA8_ASTC_8x5_KHR): F(ASTC8X5) break;  |
| 153 | CC(RGBA_ASTC_8x6_KHR): F(ASTC8X6) P(HDRa) break;  |
| 154 | CC(SRGB8_ALPHA8_ASTC_8x6_KHR): F(ASTC8X6) break;  |
| 155 | CC(RGBA_ASTC_8x8_KHR): F(ASTC8X8) P(HDRa) break;  |
| 156 | CC(SRGB8_ALPHA8_ASTC_8x8_KHR): F(ASTC8X8) break;  |
| 157 | CC(RGBA_ASTC_10x5_KHR): F(ASTC10X5) P(HDRa) break;  |
| 158 | CC(SRGB8_ALPHA8_ASTC_10x5_KHR): F(ASTC10X5) break;  |
| 159 | CC(RGBA_ASTC_10x6_KHR): F(ASTC10X6) P(HDRa) break;  |
| 160 | CC(SRGB8_ALPHA8_ASTC_10x6_KHR): F(ASTC10X6) break;  |
| 161 | CC(RGBA_ASTC_10x8_KHR): F(ASTC10X8) P(HDRa) break;  |
| 162 | CC(SRGB8_ALPHA8_ASTC_10x8_KHR): F(ASTC10X8) break;  |
| 163 | CC(RGBA_ASTC_10x10_KHR): F(ASTC10X10) P(HDRa) break;  |
| 164 | CC(SRGB8_ALPHA8_ASTC_10x10_KHR): F(ASTC10X10) break;  |
| 165 | CC(RGBA_ASTC_12x10_KHR): F(ASTC12X10) P(HDRa) break;  |
| 166 | CC(SRGB8_ALPHA8_ASTC_12x10_KHR): F(ASTC12X10) break;  |
| 167 | CC(RGBA_ASTC_12x12_KHR): F(ASTC12X12) P(HDRa) break;  |
| 168 | CC(SRGB8_ALPHA8_ASTC_12x12_KHR): F(ASTC12X12) break;  |
| 169 |   |
| 170 | // Decided to specify the channel-type as full floats rather than half. Ref implementations of  |
| 171 | // B10G11R11uf and E5B9G9R9uf all convert to float rather than half. They are both unsigned.  |
| 172 | C(R11F_G11F_B10F): F(B10G11R11uf) P(HDRa) T(UFLOAT) break;  |
| 173 | C(RGB9_E5): F(E5B9G9R9uf) P(HDRa) T(UFLOAT) break;  |
| 174 | C(RGBA4): F(B4A4R4G4) P(lRGB) T(UINT) break;  |
| 175 | }  |
| 176 |   |
| 177 | if (format != tPixelFormat::Invalid)  |
| 178 | return;  |
| 179 |   |
| 180 | // Not a compressed format. Look at glFormat.  |
| 181 | switch (glFormat)  |
| 182 | {  |
| 183 | C(LUMINANCE):  |
| 184 | switch (glType)  |
| 185 | {  |
| 186 | C(UNSIGNED_BYTE): F(L8) T(UINT) break;  |
| 187 | }  |
| 188 | break;  |
| 189 |   |
| 190 | C(ALPHA):  |
| 191 | switch (glType)  |
| 192 | {  |
| 193 | C(UNSIGNED_BYTE): F(A8) P(lRGB) T(UINT) break;  |
| 194 | }  |
| 195 | break;  |
| 196 |   |
| 197 | // It's ok to also include the 'INTEGER' versions here as it just restricts what the glType  |
| 198 | // may be. For example R32f would still be GL_RED with type GL_FLOAT, and R8 could be either  |
| 199 | // GL_RED/GL_UNSIGNED_BYTE or GL_RED_INTEGER/GL_UNSIGNED_BYTE.  |
| 200 | C(RED):  |
| 201 | C(RED_INTEGER):  |
| 202 | switch (glType)  |
| 203 | {  |
| 204 | C(UNSIGNED_BYTE): F(R8) T(UINT) break;  |
| 205 | C(HALF_FLOAT): F(R16f) P(HDRa) T(SFLOAT) break;  |
| 206 | C(FLOAT): F(R32f) P(HDRa) T(SFLOAT) break;  |
| 207 | }  |
| 208 | break;  |
| 209 |   |
| 210 | C(RG):  |
| 211 | C(RG_INTEGER):  |
| 212 | switch (glType)  |
| 213 | {  |
| 214 | C(UNSIGNED_BYTE): F(R8G8) T(UINT) break;  |
| 215 | C(HALF_FLOAT): F(R16G16f) P(HDRa) T(SFLOAT) break;  |
| 216 | C(FLOAT): F(R32G32f) P(HDRa) T(SFLOAT) break;  |
| 217 | }  |
| 218 | break;  |
| 219 |   |
| 220 | C(RGB):  |
| 221 | C(RGB_INTEGER):  |
| 222 | switch (glType)  |
| 223 | {  |
| 224 | // They are all T(UINT8). The short refers to total pixel size but each component would still  |
| 225 | // be accessed as a uint8 in a shader program. GL is a bit inconsistent here as the BYTE type  |
| 226 | // does not refer to the total pixel size like SHORT does.  |
| 227 | C(UNSIGNED_BYTE): F(R8G8B8) T(UINT) break;  |
| 228 | C(UNSIGNED_SHORT_5_6_5_REV): F(G3B5R5G3) T(UINT) break;  |
| 229 | C(HALF_FLOAT): F(R16G16B16f) P(HDRa) T(SFLOAT) break;  |
| 230 | C(FLOAT): F(R32G32B32f) P(HDRa) T(SFLOAT) break;  |
| 231 | }  |
| 232 | break;  |
| 233 |   |
| 234 | C(RGBA):  |
| 235 | C(RGBA_INTEGER):  |
| 236 | switch (glType)  |
| 237 | {  |
| 238 | C(UNSIGNED_SHORT_4_4_4_4): F(B4A4R4G4) T(UINT) break;  |
| 239 | C(UNSIGNED_BYTE): F(R8G8B8A8) T(UINT) break;  |
| 240 | C(HALF_FLOAT): F(R16G16B16A16f) P(HDRa) T(SFLOAT) break;  |
| 241 | C(FLOAT): F(R32G32B32A32f) P(HDRa) T(SFLOAT) break;  |
| 242 | }  |
| 243 | break;  |
| 244 |   |
| 245 | C(BGR):  |
| 246 | C(BGR_INTEGER):  |
| 247 | switch (glType)  |
| 248 | {  |
| 249 | // See comment above.  |
| 250 | C(UNSIGNED_BYTE): F(B8G8R8) T(UINT) break;  |
| 251 | C(UNSIGNED_SHORT_5_6_5): F(G3B5R5G3) T(UINT) break;  |
| 252 | }  |
| 253 | break;  |
| 254 |   |
| 255 | C(BGRA):  |
| 256 | C(BGRA_INTEGER):  |
| 257 | switch (glType)  |
| 258 | {  |
| 259 | // See comment above.  |
| 260 | C(UNSIGNED_BYTE): F(B8G8R8A8) T(UINT) break;  |
| 261 | C(UNSIGNED_SHORT_4_4_4_4): F(G4B4A4R4) T(UINT) break;  |
| 262 | C(UNSIGNED_SHORT_5_5_5_1): F(G3B5A1R5G2) T(UINT) break;  |
| 263 | }  |
| 264 | break;  |
| 265 | }  |
| 266 |   |
| 267 | if (format == tPixelFormat::Invalid)  |
| 268 | P(Unspecified)  |
| 269 |   |
| 270 | #undef C  |
| 271 | #undef CC  |
| 272 | #undef F  |
| 273 | #undef P  |
| 274 | #undef M  |
| 275 | #undef T  |
| 276 | }  |
| 277 |   |
| 278 |   |
| 279 | void tKTX::GetFormatInfo_FromVKFormat(tPixelFormat& format, tColourProfile& profile, tAlphaMode& alphaMode, tChannelType& chanType, uint32 vkFormat)  |
| 280 | {  |
| 281 | // For colour profile (the space of the data) we try to make an educated guess. In general only the asset author  |
| 282 | // knows the colour space/profile. For most (non-HDR) pixel formats for colours, we assume the data is sRGB.  |
| 283 | // Floating-point formats are likewise assumed to be in linear-space (and are usually used for HDR images). In  |
| 284 | // addition when the data is probably not colour data (like ATI1/2) we assume it's in linear.  |
| 285 | format = tPixelFormat::Invalid;  |
| 286 | profile = tColourProfile::sRGB;  |
| 287 | alphaMode = tAlphaMode::None;  |
| 288 | chanType = tChannelType::NONE;  |
| 289 |   |
| 290 | // The VK formats conflate the format with the data. The colour-space is not part of the format in tacent and is  |
| 291 | // returned in a separate variable. UNORM means E [0.0, 1.0].  |
| 292 | //  |
| 293 | // Regardiing colour profiles. In many cases there are two similar VK pixel formats that differ only in that one of  |
| 294 | // them specifies using sRGB for the RGB channels. For example:  |
| 295 | // VK_FORMAT_R8G8B8A8_SRGB : sRGB for RGB, linear for A.  |
| 296 | // VK_FORMAT_R8G8B8A8_UNORM : No-sRGB variant.  |
| 297 | // The implication is that VK_FORMAT_R8G8B8A8_UNORM should therefore be in linear RGBA space. Unfortunately most  |
| 298 | // files in the wild that have these non-sRGB variants are usually still authored in sRGB-space. Tacent keeps them  |
| 299 | // by default in sRGB-space but you will see a commented-out lRGB tag below (indicating how it 'should' be).  |
| 300 | #define C(c) case VK_FORMAT_##c  |
| 301 | #define F(f) format = tPixelFormat::f;  |
| 302 | #define P(p) profile = tColourProfile::p;  |
| 303 | #define M(m) alphaMode = tAlphaMode::m;  |
| 304 | #define T(t) chanType = tChannelType::t;  |
| 305 | switch (vkFormat)  |
| 306 | {  |
| 307 | //  |
| 308 | // Packed formats.  |
| 309 | //  |
| 310 | // NVTT can export ktx2 as A8. There is no A8 in VkFormat so it uses R8 instead.  |
| 311 | // There is no difference in storage between UNORM (unsigned normalized) and UINT. The only difference is  |
| 312 | // when the texture is bound, the UNORM textures get their component values converted to floats in [0.0, 1.0],  |
| 313 | // whereas the UINT textures would just have the int returned by a shader texture sampler. Commented-out  |
| 314 | // case statements are simply not implemented yet.  |
| 315 | //  |
| 316 | //C(R8_SNORM):  |
| 317 | //C(R8_USCALED):  |
| 318 | //C(R8_SSCALED):  |
| 319 | //C(R8_SINT):  |
| 320 | C(R8_UNORM): F(R8) /*P(lRGB)*/ M(None) T(UNORM) break;  |
| 321 | C(R8_UINT): F(R8) /*P(lRGB)*/ T(UINT) break;  |
| 322 | C(R8_SRGB): F(R8) break;  |
| 323 |   |
| 324 | //C(R8G8_SNORM):  |
| 325 | //C(R8G8_USCALED):  |
| 326 | //C(R8G8_SSCALED):  |
| 327 | //C(R8G8_SINT):  |
| 328 | C(R8G8_UNORM): F(R8G8) /*P(lRGB)*/ T(UNORM) break;  |
| 329 | C(R8G8_UINT): F(R8G8) /*P(lRGB)*/ T(UINT) break;  |
| 330 | C(R8G8_SRGB): F(R8G8) break;  |
| 331 |   |
| 332 | //C(R8G8B8_SNORM):  |
| 333 | //C(R8G8B8_USCALED):  |
| 334 | //C(R8G8B8_SSCALED):  |
| 335 | //C(R8G8B8_SINT):  |
| 336 | C(R8G8B8_UNORM): F(R8G8B8) /*P(lRGB)*/ T(UNORM) break;  |
| 337 | C(R8G8B8_UINT): F(R8G8B8) /*P(lRGB)*/ T(UINT) break;  |
| 338 | C(R8G8B8_SRGB): F(R8G8B8) break;  |
| 339 |   |
| 340 | //C(R8G8B8A8_SNORM):  |
| 341 | //C(R8G8B8A8_USCALED):  |
| 342 | //C(R8G8B8A8_SSCALED):  |
| 343 | //C(R8G8B8A8_SINT):  |
| 344 | C(R8G8B8A8_UNORM): F(R8G8B8A8) /*P(lRGB)*/ T(UNORM) break;  |
| 345 | C(R8G8B8A8_UINT): F(R8G8B8A8) /*P(lRGB)*/ T(UINT) break;  |
| 346 | C(R8G8B8A8_SRGB): F(R8G8B8A8) break;  |
| 347 |   |
| 348 | //C(B8G8R8_SNORM):  |
| 349 | //C(B8G8R8_USCALED):  |
| 350 | //C(B8G8R8_SSCALED):  |
| 351 | //C(B8G8R8_SINT):  |
| 352 | C(B8G8R8_UNORM): F(B8G8R8) /*P(lRGB)*/ T(UNORM) break;  |
| 353 | C(B8G8R8_UINT): F(B8G8R8) /*P(lRGB)*/ T(UINT) break;  |
| 354 | C(B8G8R8_SRGB): F(B8G8R8) break;  |
| 355 |   |
| 356 | //C(B8G8R8A8_SNORM):  |
| 357 | //C(B8G8R8A8_USCALED):  |
| 358 | //C(B8G8R8A8_SSCALED):  |
| 359 | //C(B8G8R8A8_SINT):  |
| 360 | C(B8G8R8A8_UNORM): F(B8G8R8A8) /*P(lRGB)*/ T(UNORM) break;  |
| 361 | C(B8G8R8A8_UINT): F(B8G8R8A8) /*P(lRGB)*/ T(UINT) break;  |
| 362 | C(B8G8R8A8_SRGB): F(B8G8R8A8) break;  |
| 363 |   |
| 364 | C(B5G6R5_UNORM_PACK16): F(G3B5R5G3) T(UNORM) break;  |
| 365 | C(B4G4R4A4_UNORM_PACK16): F(G4B4A4R4) T(UNORM) break;  |
| 366 | C(B5G5R5A1_UNORM_PACK16): F(G3B5A1R5G2) T(UNORM) break;  |
| 367 |   |
| 368 | C(R16_SFLOAT): F(R16f) P(HDRa) T(SFLOAT) break;  |
| 369 | C(R16G16_SFLOAT): F(R16G16f) P(HDRa) T(SFLOAT) break;  |
| 370 | C(R16G16B16_SFLOAT): F(R16G16B16f) P(HDRa) T(SFLOAT) break;  |
| 371 | C(R16G16B16A16_SFLOAT): F(R16G16B16A16f) P(HDRa) T(SFLOAT) break;  |
| 372 | C(R32_SFLOAT): F(R32f) P(HDRa) T(SFLOAT) break;  |
| 373 | C(R32G32_SFLOAT): F(R32G32f) P(HDRa) T(SFLOAT) break;  |
| 374 | C(R32G32B32_SFLOAT): F(R32G32B32f) P(HDRa) T(SFLOAT) break;  |
| 375 | C(R32G32B32A32_SFLOAT): F(R32G32B32A32f) P(HDRa) T(SFLOAT) break;  |
| 376 | C(B10G11R11_UFLOAT_PACK32): F(B10G11R11uf) P(HDRa) T(UFLOAT) break;   |
| 377 | C(E5B9G9R9_UFLOAT_PACK32): F(E5B9G9R9uf) P(HDRa) T(UFLOAT) break;  |
| 378 |   |
| 379 | //  |
| 380 | // BC Formats.  |
| 381 | //  |
| 382 | C(BC1_RGB_UNORM_BLOCK): F(BC1DXT1) /*P(lRGB)*/ T(UNORM) break;  |
| 383 | C(BC1_RGB_SRGB_BLOCK): F(BC1DXT1) break;  |
| 384 | C(BC1_RGBA_UNORM_BLOCK): F(BC1DXT1A) /*P(lRGB)*/ T(UNORM) break;  |
| 385 | C(BC1_RGBA_SRGB_BLOCK): F(BC1DXT1A) break;  |
| 386 | C(BC2_UNORM_BLOCK): F(BC2DXT2DXT3) /*P(lRGB)*/ T(UNORM) break;  |
| 387 | C(BC2_SRGB_BLOCK): F(BC2DXT2DXT3) break;  |
| 388 | C(BC3_UNORM_BLOCK): F(BC3DXT4DXT5) /*P(lRGB)*/ T(UNORM) break;  |
| 389 | C(BC3_SRGB_BLOCK): F(BC3DXT4DXT5) break;  |
| 390 |   |
| 391 | // Signed not supported yet for BC4 and BC5.  |
| 392 | C(BC4_UNORM_BLOCK): F(BC4ATI1U) /*P(lRGB)*/ T(UNORM) break;  |
| 393 | C(BC4_SNORM_BLOCK): F(BC4ATI1S) /*P(lRGB)*/ T(SNORM) break;  |
| 394 | C(BC5_UNORM_BLOCK): F(BC5ATI2U) /*P(lRGB)*/ T(UNORM) break;  |
| 395 | C(BC5_SNORM_BLOCK): F(BC5ATI2S) /*P(lRGB)*/ T(SNORM) break;  |
| 396 |   |
| 397 | C(BC6H_UFLOAT_BLOCK): F(BC6U) P(HDRa) T(UFLOAT) break;  |
| 398 | C(BC6H_SFLOAT_BLOCK): F(BC6S) P(HDRa) T(SFLOAT) break;  |
| 399 |   |
| 400 | C(BC7_UNORM_BLOCK): F(BC7) /*P(lRGB)*/ T(UNORM) break;  |
| 401 | C(BC7_SRGB_BLOCK): F(BC7) break;  |
| 402 |   |
| 403 | //  |
| 404 | // ETC2 and EAC Formats.  |
| 405 | //  |
| 406 | C(ETC2_R8G8B8_UNORM_BLOCK): F(ETC2RGB) /*P(lRGB)*/ T(UNORM) break;  |
| 407 | C(ETC2_R8G8B8_SRGB_BLOCK): F(ETC2RGB) break;  |
| 408 | C(ETC2_R8G8B8A8_UNORM_BLOCK): F(ETC2RGBA) /*P(lRGB)*/ T(UNORM) break;  |
| 409 | C(ETC2_R8G8B8A8_SRGB_BLOCK): F(ETC2RGBA) break;  |
| 410 | C(ETC2_R8G8B8A1_UNORM_BLOCK): F(ETC2RGBA1) /*P(lRGB)*/ T(UNORM) break;  |
| 411 | C(ETC2_R8G8B8A1_SRGB_BLOCK): F(ETC2RGBA1) break;  |
| 412 |   |
| 413 | C(EAC_R11_UNORM_BLOCK): F(EACR11U) T(UNORM) break;  |
| 414 | C(EAC_R11_SNORM_BLOCK): F(EACR11S) T(SFLOAT) break;  |
| 415 | C(EAC_R11G11_UNORM_BLOCK): F(EACRG11U) T(UNORM) break;  |
| 416 | C(EAC_R11G11_SNORM_BLOCK): F(EACRG11S) T(SFLOAT) break;  |
| 417 |   |
| 418 | //  |
| 419 | // PVR Formats.  |
| 420 | //  |
| 421 | C(PVRTC1_4BPP_UNORM_BLOCK_IMG): F(PVRBPP4) P(lRGB) T(UNORM) break;  |
| 422 | C(PVRTC1_4BPP_SRGB_BLOCK_IMG): F(PVRBPP4) P(sRGB) T(UINT) break;  |
| 423 | C(PVRTC1_2BPP_UNORM_BLOCK_IMG): F(PVRBPP2) P(lRGB) T(UNORM) break;  |
| 424 | C(PVRTC1_2BPP_SRGB_BLOCK_IMG): F(PVRBPP2) P(sRGB) T(UINT) break;  |
| 425 | //C(PVRTC2_4BPP_UNORM_BLOCK_IMG):  |
| 426 | //C(PVRTC2_4BPP_SRGB_BLOCK_IMG):  |
| 427 | //C(PVRTC2_2BPP_UNORM_BLOCK_IMG):  |
| 428 | //C(PVRTC2_2BPP_SRGB_BLOCK_IMG):  |
| 429 |   |
| 430 | //  |
| 431 | // ASTC  |
| 432 | //  |
| 433 | // From the Khronos description of ASTC:   |
| 434 | // "Whether floats larger than 1.0 are allowed is not a per-image property; it's a per-block property. An  |
| 435 | // HDR-compressed ASTC image is simply one where blocks can return values larger than 1.0." and "So the format  |
| 436 | // does not specify if floating point values are greater than 1.0." and "There are only two properties of an  |
| 437 | // ASTC compressed image that are per-image (and therefore part of the format) rather than being per-block.  |
| 438 | // These properties are block size and sRGB colorspace conversion."  |
| 439 | //  |
| 440 | // It seems to me for an HDR ASTC KTX2 image there are two possibilities for VK_FORMAT:  |
| 441 | //   |
| 442 | // 1) VK_FORMAT_ASTC_4x4_UNORM_BLOCK or VK_FORMAT_ASTC_4x4_SRGB_BLOCK, in which case blocks that return  |
| 443 | // component values > 1.0 are making a liar out of "UNORM" -- and  |
| 444 | // 2) VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT  |
| 445 | //  |
| 446 | // Which one is correct? AMD's compressonator, after converting an EXR to ASTC, it can't guarantee blocks won't  |
| 447 | // return values above 1.0 (i.e. an HDR image). It does not generate SFLOAT_BLOCK_EXT. I suspect compressionator  |
| 448 | // is incorrect.  |
| 449 | //  |
| 450 | // We chose HDR as the default profile because it can load LDR blocks. The other way around doesn't work with  |
| 451 | // with the tests images -- the LDR profile doesn't appear capable of loading HDR blocks (they become magenta).  |
| 452 | //  |
| 453 | C(ASTC_4x4_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 454 | C(ASTC_4x4_UNORM_BLOCK): F(ASTC4X4) P(HDRa) T(UNORM) break;  |
| 455 | C(ASTC_4x4_SRGB_BLOCK): F(ASTC4X4) break;  |
| 456 |   |
| 457 | C(ASTC_5x4_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 458 | C(ASTC_5x4_UNORM_BLOCK): F(ASTC5X4) P(HDRa) T(UNORM) break;  |
| 459 | C(ASTC_5x4_SRGB_BLOCK): F(ASTC5X4) break;  |
| 460 |   |
| 461 | C(ASTC_5x5_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 462 | C(ASTC_5x5_UNORM_BLOCK): F(ASTC5X5) P(HDRa) T(UNORM) break;  |
| 463 | C(ASTC_5x5_SRGB_BLOCK): F(ASTC5X5) break;  |
| 464 |   |
| 465 | C(ASTC_6x5_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 466 | C(ASTC_6x5_UNORM_BLOCK): F(ASTC6X5) P(HDRa) T(UNORM) break;  |
| 467 | C(ASTC_6x5_SRGB_BLOCK): F(ASTC6X5) break;  |
| 468 |   |
| 469 | C(ASTC_6x6_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 470 | C(ASTC_6x6_UNORM_BLOCK): F(ASTC6X6) P(HDRa) T(UNORM) break;  |
| 471 | C(ASTC_6x6_SRGB_BLOCK): F(ASTC6X6) break;  |
| 472 |   |
| 473 | C(ASTC_8x5_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 474 | C(ASTC_8x5_UNORM_BLOCK): F(ASTC8X5) P(HDRa) T(UNORM) break;  |
| 475 | C(ASTC_8x5_SRGB_BLOCK): F(ASTC8X5) break;  |
| 476 |   |
| 477 | C(ASTC_8x6_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 478 | C(ASTC_8x6_UNORM_BLOCK): F(ASTC8X6) P(HDRa) T(UNORM) break;  |
| 479 | C(ASTC_8x6_SRGB_BLOCK): F(ASTC8X6) break;  |
| 480 |   |
| 481 | C(ASTC_8x8_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 482 | C(ASTC_8x8_UNORM_BLOCK): F(ASTC8X8) P(HDRa) T(UNORM) break;  |
| 483 | C(ASTC_8x8_SRGB_BLOCK): F(ASTC8X8) break;  |
| 484 |   |
| 485 | C(ASTC_10x5_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 486 | C(ASTC_10x5_UNORM_BLOCK): F(ASTC10X5) P(HDRa) T(UNORM) break;  |
| 487 | C(ASTC_10x5_SRGB_BLOCK): F(ASTC10X5) break;  |
| 488 |   |
| 489 | C(ASTC_10x6_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 490 | C(ASTC_10x6_UNORM_BLOCK): F(ASTC10X6) P(HDRa) T(UNORM) break;  |
| 491 | C(ASTC_10x6_SRGB_BLOCK): F(ASTC10X6) break;  |
| 492 |   |
| 493 | C(ASTC_10x8_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 494 | C(ASTC_10x8_UNORM_BLOCK): F(ASTC10X8) P(HDRa) T(UNORM) break;  |
| 495 | C(ASTC_10x8_SRGB_BLOCK): F(ASTC10X8) break;  |
| 496 |   |
| 497 | C(ASTC_10x10_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 498 | C(ASTC_10x10_UNORM_BLOCK): F(ASTC10X10) P(HDRa) T(UNORM) break;  |
| 499 | C(ASTC_10x10_SRGB_BLOCK): F(ASTC10X10) break;  |
| 500 |   |
| 501 | C(ASTC_12x10_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 502 | C(ASTC_12x10_UNORM_BLOCK): F(ASTC12X10) P(HDRa) T(UNORM) break;  |
| 503 | C(ASTC_12x10_SRGB_BLOCK): F(ASTC12X10) break;  |
| 504 |   |
| 505 | C(ASTC_12x12_SFLOAT_BLOCK_EXT): F(ASTC4X4) P(HDRa) T(SFLOAT) break;  |
| 506 | C(ASTC_12x12_UNORM_BLOCK): F(ASTC12X12) P(HDRa) T(UNORM) break;  |
| 507 | C(ASTC_12x12_SRGB_BLOCK): F(ASTC12X12) break;  |
| 508 | }  |
| 509 |   |
| 510 | if (format == tPixelFormat::Invalid)  |
| 511 | P(Unspecified);  |
| 512 |   |
| 513 | #undef C  |
| 514 | #undef F  |
| 515 | #undef P  |
| 516 | #undef M  |
| 517 | #undef T  |
| 518 | }  |
| 519 |   |
| 520 |   |
| 521 | tImageKTX::tImageKTX()  |
| 522 | {  |
| 523 | tStd::tMemset(dest: Layers, val: 0, numBytes: sizeof(Layers));  |
| 524 | }  |
| 525 |   |
| 526 |   |
| 527 | tImageKTX::tImageKTX(const tString& ktxFile, const LoadParams& loadParams) :  |
| 528 | Filename(ktxFile)  |
| 529 | {  |
| 530 | tStd::tMemset(dest: Layers, val: 0, numBytes: sizeof(Layers));  |
| 531 | Load(ktxFile, loadParams);  |
| 532 | }  |
| 533 |   |
| 534 |   |
| 535 | tImageKTX::tImageKTX(const uint8* ktxFileInMemory, int numBytes, const LoadParams& loadParams)  |
| 536 | {  |
| 537 | tStd::tMemset(dest: Layers, val: 0, numBytes: sizeof(Layers));  |
| 538 | Load(ktxFileInMemory, numBytes, loadParams);  |
| 539 | }  |
| 540 |   |
| 541 |   |
| 542 | void tImageKTX::Clear()  |
| 543 | {  |
| 544 | for (int image = 0; image < NumImages; image++)  |
| 545 | {  |
| 546 | for (int layer = 0; layer < NumMipmapLayers; layer++)  |
| 547 | {  |
| 548 | delete Layers[layer][image];  |
| 549 | Layers[layer][image] = nullptr;  |
| 550 | }  |
| 551 | }  |
| 552 |   |
| 553 | States = 0; // Image will be invalid now since Valid state not set.  |
| 554 | AlphaMode = tAlphaMode::Unspecified;  |
| 555 | ChannelType = tChannelType::Unspecified;  |
| 556 | IsCubeMap = false;  |
| 557 | RowReversalOperationPerformed = false;  |
| 558 | NumImages = 0;  |
| 559 | NumMipmapLayers = 0;  |
| 560 |   |
| 561 | tBaseImage::Clear();  |
| 562 | }  |
| 563 |   |
| 564 |   |
| 565 | bool tImageKTX::Set(tPixel4b* pixels, int width, int height, bool steal)  |
| 566 | {  |
| 567 | Clear();  |
| 568 | if (!pixels || (width <= 0) || (height <= 0))  |
| 569 | return false;  |
| 570 |   |
| 571 | Layers[0][0] = new tLayer(tPixelFormat::R8G8B8A8, width, height, (uint8*)pixels, steal);  |
| 572 | AlphaMode = tAlphaMode::Normal;  |
| 573 | ChannelType = tChannelType::UNORM;  |
| 574 | NumImages = 1;  |
| 575 | NumMipmapLayers = 1;  |
| 576 |   |
| 577 | PixelFormatSrc = tPixelFormat::R8G8B8A8;  |
| 578 | PixelFormat = tPixelFormat::R8G8B8A8;  |
| 579 | ColourProfileSrc = tColourProfile::sRGB; // We assume pixels must be sRGB.  |
| 580 | ColourProfile = tColourProfile::sRGB;  |
| 581 |   |
| 582 | SetStateBit(StateBit::Valid);  |
| 583 | return true;  |
| 584 | }  |
| 585 |   |
| 586 |   |
| 587 | bool tImageKTX::Set(tFrame* frame, bool steal)  |
| 588 | {  |
| 589 | Clear();  |
| 590 | if (!frame || !frame->IsValid())  |
| 591 | return false;  |
| 592 |   |
| 593 | PixelFormatSrc = frame->PixelFormatSrc;  |
| 594 | PixelFormat = tPixelFormat::R8G8B8A8;  |
| 595 | ColourProfileSrc = tColourProfile::sRGB; // We assume frame must be sRGB.  |
| 596 | ColourProfile = tColourProfile::sRGB;  |
| 597 |   |
| 598 | tPixel4b* pixels = frame->GetPixels(steal);  |
| 599 | Set(pixels, width: frame->Width, height: frame->Height, steal);  |
| 600 | if (steal)  |
| 601 | delete frame;  |
| 602 |   |
| 603 | SetStateBit(StateBit::Valid);  |
| 604 | return true;  |
| 605 | }  |
| 606 |   |
| 607 |   |
| 608 | bool tImageKTX::Set(tPicture& picture, bool steal)  |
| 609 | {  |
| 610 | Clear();  |
| 611 | if (!picture.IsValid())  |
| 612 | return false;  |
| 613 |   |
| 614 | PixelFormatSrc = picture.PixelFormatSrc;  |
| 615 | PixelFormat = tPixelFormat::R8G8B8A8;  |
| 616 | // We don't know colour profile of tPicture.  |
| 617 |   |
| 618 | // This is worth some explanation. If steal is true the picture becomes invalid and the  |
| 619 | // 'set' call will steal the stolen pixels. If steal is false GetPixels is called and the  |
| 620 | // 'set' call will memcpy them out... which makes sure the picture is still valid after and  |
| 621 | // no-one is sharing the pixel buffer. We don't check the success of 'set' because it must  |
| 622 | // succeed if picture was valid.  |
| 623 | tPixel4b* pixels = steal ? picture.StealPixels() : picture.GetPixels();  |
| 624 | bool success = Set(pixels, width: picture.GetWidth(), height: picture.GetHeight(), steal);  |
| 625 | tAssert(success);  |
| 626 | return true;  |
| 627 | }  |
| 628 |   |
| 629 |   |
| 630 | tFrame* tImageKTX::GetFrame(bool steal)  |
| 631 | {  |
| 632 | // Data must be decoded for this to work.  |
| 633 | if (!IsValid() || (PixelFormat != tPixelFormat::R8G8B8A8) || (Layers[0][0] == nullptr))  |
| 634 | return nullptr;  |
| 635 |   |
| 636 | tFrame* frame = new tFrame();  |
| 637 | frame->Width = Layers[0][0]->Width;  |
| 638 | frame->Height = Layers[0][0]->Height;  |
| 639 | frame->PixelFormatSrc = PixelFormatSrc;  |
| 640 |   |
| 641 | if (steal)  |
| 642 | {  |
| 643 | frame->Pixels = (tPixel4b*)Layers[0][0]->StealData();  |
| 644 | delete Layers[0][0];  |
| 645 | Layers[0][0] = nullptr;  |
| 646 | }  |
| 647 | else  |
| 648 | {  |
| 649 | frame->Pixels = new tPixel4b[frame->Width * frame->Height];  |
| 650 | tStd::tMemcpy(dest: frame->Pixels, src: (tPixel4b*)Layers[0][0]->Data, numBytes: frame->Width * frame->Height * sizeof(tPixel4b));  |
| 651 | }  |
| 652 |   |
| 653 | return frame;  |
| 654 | }  |
| 655 |   |
| 656 |   |
| 657 | bool tImageKTX::IsOpaque() const  |
| 658 | {  |
| 659 | return tImage::tIsOpaqueFormat(format: PixelFormat);  |
| 660 | }  |
| 661 |   |
| 662 |   |
| 663 | bool tImageKTX::StealLayers(tList<tLayer>& layers)  |
| 664 | {  |
| 665 | if (!IsValid() || IsCubemap() || (NumImages <= 0))  |
| 666 | return false;  |
| 667 |   |
| 668 | for (int mip = 0; mip < NumMipmapLayers; mip++)  |
| 669 | {  |
| 670 | layers.Append(item: Layers[mip][0]);  |
| 671 | Layers[mip][0] = nullptr;  |
| 672 | }  |
| 673 |   |
| 674 | Clear();  |
| 675 | return true;  |
| 676 | }  |
| 677 |   |
| 678 |   |
| 679 | int tImageKTX::GetLayers(tList<tLayer>& layers) const  |
| 680 | {  |
| 681 | if (!IsValid() || IsCubemap() || (NumImages <= 0))  |
| 682 | return 0;  |
| 683 |   |
| 684 | for (int mip = 0; mip < NumMipmapLayers; mip++)  |
| 685 | layers.Append(item: Layers[mip][0]);  |
| 686 |   |
| 687 | return NumMipmapLayers;  |
| 688 | }  |
| 689 |   |
| 690 |   |
| 691 | int tImageKTX::StealCubemapLayers(tList<tLayer> layerLists[tFaceIndex_NumFaces], uint32 faceFlags)  |
| 692 | {  |
| 693 | if (!IsValid() || !IsCubemap() || !faceFlags)  |
| 694 | return 0;  |
| 695 |   |
| 696 | int faceCount = 0;  |
| 697 | for (int face = 0; face < tFaceIndex_NumFaces; face++)  |
| 698 | {  |
| 699 | uint32 faceFlag = 1 << face;  |
| 700 | if (!(faceFlag & faceFlags))  |
| 701 | continue;  |
| 702 |   |
| 703 | tList<tLayer>& layers = layerLists[face];  |
| 704 | for (int mip = 0; mip < NumMipmapLayers; mip++)  |
| 705 | {  |
| 706 | layers.Append( item: Layers[mip][face] );  |
| 707 | Layers[mip][face] = nullptr;  |
| 708 | }  |
| 709 | faceCount++;  |
| 710 | }  |
| 711 |   |
| 712 | Clear();  |
| 713 | return faceCount;  |
| 714 | }  |
| 715 |   |
| 716 |   |
| 717 | int tImageKTX::GetCubemapLayers(tList<tLayer> layerLists[tFaceIndex_NumFaces], uint32 faceFlags) const  |
| 718 | {  |
| 719 | if (!IsValid() || !IsCubemap() || !faceFlags)  |
| 720 | return 0;  |
| 721 |   |
| 722 | int faceCount = 0;  |
| 723 | for (int face = 0; face < tFaceIndex_NumFaces; face++)  |
| 724 | {  |
| 725 | uint32 faceFlag = 1 << face;  |
| 726 | if (!(faceFlag & faceFlags))  |
| 727 | continue;  |
| 728 |   |
| 729 | tList<tLayer>& layers = layerLists[face];  |
| 730 | for (int mip = 0; mip < NumMipmapLayers; mip++)  |
| 731 | layers.Append( item: Layers[mip][face] );  |
| 732 |   |
| 733 | faceCount++;  |
| 734 | }  |
| 735 |   |
| 736 | return faceCount;  |
| 737 | }  |
| 738 |   |
| 739 |   |
| 740 | bool tImageKTX::Load(const tString& ktxFile, const LoadParams& loadParams)  |
| 741 | {  |
| 742 | Clear();  |
| 743 | Filename = ktxFile;  |
| 744 | tSystem::tFileType fileType = tSystem::tGetFileType(file: ktxFile);  |
| 745 | if ((fileType != tSystem::tFileType::KTX) && (fileType != tSystem::tFileType::KTX2))  |
| 746 | {  |
| 747 | SetStateBit(StateBit::Fatal_IncorrectFileType);  |
| 748 | return false;  |
| 749 | }  |
| 750 |   |
| 751 | if (!tSystem::tFileExists(file: ktxFile))  |
| 752 | {  |
| 753 | SetStateBit(StateBit::Fatal_FileDoesNotExist);  |
| 754 | return false;  |
| 755 | }  |
| 756 |   |
| 757 | #if 0  |
| 758 | int ktxSizeBytes = 0;  |
| 759 | uint8* ktxData = (uint8*)tSystem::tLoadFile(ktxFile, 0, &ktxSizeBytes);  |
| 760 | bool success = Load(ktxData, ktxSizeBytes, loadParams);  |
| 761 | delete[] ktxData;  |
| 762 | return success;  |
| 763 | #endif  |
| 764 |   |
| 765 | ktx_error_code_e result = KTX_SUCCESS;  |
| 766 | ktxTexture* texture = nullptr;  |
| 767 | result = ktxTexture_CreateFromNamedFile(filename: ktxFile.Chr(), createFlags: KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, newTex: &texture);  |
| 768 | if (!texture || (result != KTX_SUCCESS))  |
| 769 | {  |
| 770 | SetStateBit(StateBit::Fatal_CouldNotParseFile);  |
| 771 | return false;  |
| 772 | }  |
| 773 |   |
| 774 | return LoadFromTexture(texture, paramsIn: loadParams);  |
| 775 | }  |
| 776 |   |
| 777 |   |
| 778 | bool tImageKTX::Load(const uint8* ktxData, int ktxSizeBytes, const LoadParams& paramsIn)  |
| 779 | {  |
| 780 | Clear();  |
| 781 |   |
| 782 | ktx_error_code_e result = KTX_SUCCESS;  |
| 783 | ktxTexture* texture = nullptr;  |
| 784 | result = ktxTexture_CreateFromMemory(bytes: ktxData, size: ktxSizeBytes, createFlags: KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, newTex: &texture);  |
| 785 | if (!texture || (result != KTX_SUCCESS))  |
| 786 | {  |
| 787 | SetStateBit(StateBit::Fatal_CouldNotParseFile);  |
| 788 | return false;  |
| 789 | }  |
| 790 |   |
| 791 | return LoadFromTexture(texture, paramsIn);  |
| 792 | }  |
| 793 |   |
| 794 |   |
| 795 | bool tImageKTX::LoadFromTexture(ktxTexture* texture, const LoadParams& paramsIn)  |
| 796 | {  |
| 797 | tAssert(texture);  |
| 798 | LoadParams params(paramsIn);  |
| 799 | ktx_error_code_e result = KTX_SUCCESS;  |
| 800 |   |
| 801 | NumImages = texture->numFaces; // Number of faces. 1 or 6 for cubemaps.  |
| 802 | int numLayers = texture->numLayers; // Number of array layers. I believe this will be > 1 for 3D textures that are made of an array of layers.  |
| 803 | NumMipmapLayers = texture->numLevels; // Mipmap levels.  |
| 804 | int numDims = texture->numDimensions; // 1D, 2D, or 3D.  |
| 805 | int mainWidth = texture->baseWidth;  |
| 806 | int mainHeight = texture->baseHeight;  |
| 807 | bool isArray = texture->isArray;  |
| 808 | bool isCompressed = texture->isCompressed;  |
| 809 |   |
| 810 | if ((NumMipmapLayers <= 0) || (numDims != 2) || (mainWidth <= 0) || (mainHeight <= 0))  |
| 811 | {  |
| 812 | ktxTexture_Destroy(texture);  |
| 813 | SetStateBit(StateBit::Fatal_InvalidDimensions);  |
| 814 | return false;  |
| 815 | }  |
| 816 |   |
| 817 | if (NumMipmapLayers > MaxMipmapLayers)  |
| 818 | {  |
| 819 | ktxTexture_Destroy(texture);  |
| 820 | SetStateBit(StateBit::Fatal_MaxNumMipmapLevelsExceeded);  |
| 821 | return false;  |
| 822 | }  |
| 823 |   |
| 824 | IsCubeMap = (NumImages == 6);  |
| 825 |   |
| 826 | // We need to determine the pixel-format of the data. To do this we first need to know if we are dealing with  |
| 827 | // a ktx1 (OpenGL-style) or ktx2 (Vulkan-style) file.  |
| 828 | ktxTexture1* ktx1 = (texture->classId == ktxTexture1_c) ? (ktxTexture1*)texture : nullptr;  |
| 829 | ktxTexture2* ktx2 = (texture->classId == ktxTexture2_c) ? (ktxTexture2*)texture : nullptr;  |
| 830 | if (!ktx1 && !ktx2)  |
| 831 | {  |
| 832 | ktxTexture_Destroy(texture);  |
| 833 | SetStateBit(StateBit::Fatal_CorruptedFile);  |
| 834 | return false;  |
| 835 | }  |
| 836 |   |
| 837 | tSystem::tFileType fileType = tSystem::tGetFileType(file: Filename);  |
| 838 | if (ktx1)  |
| 839 | {  |
| 840 | tKTX::GetFormatInfo_FromGLFormat(format&: PixelFormatSrc, profile&: ColourProfileSrc, alphaMode&: AlphaMode, chanType&: ChannelType, glType: ktx1->glType, glFormat: ktx1->glFormat, glInternalFormat: ktx1->glInternalformat);  |
| 841 | if (fileType == tSystem::tFileType::KTX2)  |
| 842 | SetStateBit(StateBit::Conditional_ExtVersionMismatch);  |
| 843 | }  |
| 844 | else if (ktx2)  |
| 845 | {  |
| 846 | tKTX::GetFormatInfo_FromVKFormat(format&: PixelFormatSrc, profile&: ColourProfileSrc, alphaMode&: AlphaMode, chanType&: ChannelType, vkFormat: ktx2->vkFormat);  |
| 847 | if (fileType == tSystem::tFileType::KTX)  |
| 848 | SetStateBit(StateBit::Conditional_ExtVersionMismatch);  |
| 849 | }  |
| 850 | PixelFormat = PixelFormatSrc;  |
| 851 | ColourProfile = ColourProfileSrc;  |
| 852 |   |
| 853 | // From now on we should just be using the PixelFormat to decide what to do next.  |
| 854 | if (PixelFormat == tPixelFormat::Invalid)  |
| 855 | {  |
| 856 | ktxTexture_Destroy(texture);  |
| 857 | SetStateBit(StateBit::Fatal_PixelFormatNotSupported);  |
| 858 | return false;  |
| 859 | }  |
| 860 |   |
| 861 | if (tIsBCFormat(format: PixelFormat))  |
| 862 | {  |
| 863 | if ((params.Flags & LoadFlag_CondMultFourDim) && ((mainWidth%4) || (mainHeight%4)))  |
| 864 | SetStateBit(StateBit::Conditional_DimNotMultFourBC);  |
| 865 | if ((params.Flags & LoadFlag_CondPowerTwoDim) && (!tMath::tIsPower2(v: mainWidth) || !tMath::tIsPower2(v: mainHeight)))  |
| 866 | SetStateBit(StateBit::Conditional_DimNotMultFourBC);  |
| 867 | }  |
| 868 |   |
| 869 | bool reverseRowOrderRequested = params.Flags & LoadFlag_ReverseRowOrder;  |
| 870 | RowReversalOperationPerformed = false;  |
| 871 |   |
| 872 | // For images whose height is not a multiple of the block size it makes it tricky when deoompressing to do  |
| 873 | // the more efficient row reversal here, so we defer it. Packed formats have a block height of 1. Only BC  |
| 874 | // and astc have non-unity block dimensins.  |
| 875 | bool doRowReversalBeforeDecode = false;  |
| 876 | if (reverseRowOrderRequested)  |
| 877 | {  |
| 878 | bool canDo = true;  |
| 879 | for (int image = 0; image < NumImages; image++)  |
| 880 | {  |
| 881 | int height = mainHeight;  |
| 882 | for (int layer = 0; layer < NumMipmapLayers; layer++)  |
| 883 | {  |
| 884 | if (!CanReverseRowData(PixelFormat, height))  |
| 885 | {  |
| 886 | canDo = false;  |
| 887 | break;  |
| 888 | }  |
| 889 | height /= 2; if (height < 1) height = 1;  |
| 890 | }  |
| 891 | if (!canDo)  |
| 892 | break;  |
| 893 | }  |
| 894 | doRowReversalBeforeDecode = canDo;  |
| 895 | }  |
| 896 |   |
| 897 | for (int image = 0; image < NumImages; image++)  |
| 898 | {  |
| 899 | int width = mainWidth;  |
| 900 | int height = mainHeight;  |
| 901 | for (int layer = 0; layer < NumMipmapLayers; layer++)  |
| 902 | {  |
| 903 | size_t offset;  |
| 904 | result = ktxTexture_GetImageOffset(texture, layer, 0, image, &offset);  |
| 905 | if (result != KTX_SUCCESS)  |
| 906 | {  |
| 907 | ktxTexture_Destroy(texture);  |
| 908 | SetStateBit(StateBit::Fatal_InvalidDataOffset);  |
| 909 | return false;  |
| 910 | }  |
| 911 |   |
| 912 | uint8* currPixelData = ktxTexture_GetData(This: texture) + offset;  |
| 913 | int numBytes = 0;  |
| 914 | if (tImage::tIsBCFormat(format: PixelFormat) || tImage::tIsASTCFormat(format: PixelFormat) || tImage::tIsPackedFormat(format: PixelFormat) || tImage::tIsPVRFormat(format: PixelFormat))  |
| 915 | {  |
| 916 | // It's a block format (BC/DXTn or ASTC). Each block encodes a 4x4 up to 12x12 square of pixels. DXT2,3,4,5 and BC 6,7 use 128  |
| 917 | // bits per block. DXT1 and DXT1A (BC1) use 64bits per block. ASTC always uses 128 bits per block but it's not always 4x4.  |
| 918 | // Packed formats are considered to have a block width and height of 1.  |
| 919 | int blockW = tGetBlockWidth(PixelFormat);  |
| 920 | int blockH = tGetBlockHeight(PixelFormat);  |
| 921 | int bytesPerBlock = tImage::tGetBytesPerBlock(PixelFormat);  |
| 922 | tAssert(bytesPerBlock > 0);  |
| 923 | int numBlocksW = tGetNumBlocks(blockWH: blockW, imageWH: width);  |
| 924 | int numBlocksH = tGetNumBlocks(blockWH: blockH, imageWH: height);  |
| 925 | int numBlocks = numBlocksW*numBlocksH;  |
| 926 | numBytes = numBlocks * bytesPerBlock;  |
| 927 |   |
| 928 | // Here's where we possibly modify the opaque DXT1 texture to be DXT1A if there are blocks with binary  |
| 929 | // transparency. We only bother checking the main layer. If it's opaque we assume all the others are too.  |
| 930 | if ((layer == 0) && (PixelFormat == tPixelFormat::BC1DXT1) && tImage::DoBC1BlocksHaveBinaryAlpha(blocks: (tImage::BC1Block*)currPixelData, numBlocks))  |
| 931 | PixelFormat = PixelFormatSrc = tPixelFormat::BC1DXT1A;  |
| 932 |   |
| 933 | // KTX files store textures upside down. In the OpenGL RH coord system, the lower left of the texture  |
| 934 | // is the origin and consecutive rows go up. For this reason we need to read each row of blocks from  |
| 935 | // the top to the bottom row. We also need to flip the rows within the 4x4 block by flipping the lookup  |
| 936 | // tables. This should be fairly fast as there is no encoding or encoding going on. Width and height  |
| 937 | // will go down to 1x1, which will still use a 4x4 DXT pixel-block.  |
| 938 | if (doRowReversalBeforeDecode)  |
| 939 | {  |
| 940 | uint8* reversedPixelData = tImage::CreateReversedRowData(pixelData: currPixelData, pixelDataFormat: PixelFormat, numBlocksW, numBlocksH);  |
| 941 | tAssert(reversedPixelData);  |
| 942 |   |
| 943 | // We can simply get the layer to steal the memory (the last true arg).  |
| 944 | Layers[layer][image] = new tLayer(PixelFormat, width, height, reversedPixelData, true);  |
| 945 | }  |
| 946 | else  |
| 947 | {  |
| 948 | // Not reversing. Use the currPixelData.  |
| 949 | Layers[layer][image] = new tLayer(PixelFormat, width, height, (uint8*)currPixelData);  |
| 950 | }  |
| 951 |   |
| 952 | tAssert(Layers[layer][image]->GetDataSize() == numBytes);  |
| 953 | }  |
| 954 | else  |
| 955 | {  |
| 956 | // Unsupported pixel format.  |
| 957 | Clear();  |
| 958 | SetStateBit(StateBit::Fatal_PixelFormatNotSupported);  |
| 959 | return false;  |
| 960 | }  |
| 961 |   |
| 962 | currPixelData += numBytes;  |
| 963 | width /= 2; tMath::tiClampMin(val&: width, min: 1);  |
| 964 | height /= 2; tMath::tiClampMin(val&: height, min: 1);  |
| 965 | }  |
| 966 | }  |
| 967 |   |
| 968 | if (doRowReversalBeforeDecode)  |
| 969 | RowReversalOperationPerformed = true;  |
| 970 |   |
| 971 | // Not asked to decode. We're basically done.  |
| 972 | if (!(params.Flags & LoadFlag_Decode))  |
| 973 | {  |
| 974 | if (reverseRowOrderRequested && !RowReversalOperationPerformed)  |
| 975 | SetStateBit(StateBit::Conditional_CouldNotFlipRows);  |
| 976 |   |
| 977 | SetStateBit(StateBit::Valid);  |
| 978 | tAssert(IsValid());  |
| 979 | return true;  |
| 980 | }  |
| 981 |   |
| 982 | // Decode to 32-bit RGBA.  |
| 983 | // Spread only applies to single-channel (R-only or L-only) formats.  |
| 984 | bool spread = params.Flags & LoadFlag_SpreadLuminance;  |
| 985 |   |
| 986 | // The gamma-compression load flags only apply when decoding. If the gamma mode is auto, we determine here  |
| 987 | // whether to apply sRGB compression. If the space is linear and a format that often encodes colours, we apply it.  |
| 988 | if (params.Flags & LoadFlag_AutoGamma)  |
| 989 | {  |
| 990 | // Clear all related flags.  |
| 991 | params.Flags &= ~(LoadFlag_AutoGamma | LoadFlag_SRGBCompression | LoadFlag_GammaCompression);  |
| 992 | if (tMath::tIsProfileLinearInRGB(profile: ColourProfileSrc))  |
| 993 | {  |
| 994 | // Just cuz it's linear doesn't mean we want to gamma transform. Some formats should be kept linear.  |
| 995 | if  |
| 996 | (  |
| 997 | (PixelFormatSrc != tPixelFormat::A8) && (PixelFormatSrc != tPixelFormat::A8L8) &&  |
| 998 | (PixelFormatSrc != tPixelFormat::BC4ATI1U) && (PixelFormatSrc != tPixelFormat::BC4ATI1S) &&  |
| 999 | (PixelFormatSrc != tPixelFormat::BC5ATI2U) && (PixelFormatSrc != tPixelFormat::BC5ATI2S)  |
| 1000 | )  |
| 1001 | {  |
| 1002 | params.Flags |= LoadFlag_SRGBCompression;  |
| 1003 | }  |
| 1004 | }  |
| 1005 | }  |
| 1006 |   |
| 1007 | bool didRowReversalAfterDecode = false;  |
| 1008 | for (int image = 0; image < NumImages; image++)  |
| 1009 | {  |
| 1010 | for (int layerNum = 0; layerNum < NumMipmapLayers; layerNum++)  |
| 1011 | {  |
| 1012 | tLayer* layer = Layers[layerNum][image];  |
| 1013 | int w = layer->Width;  |
| 1014 | int h = layer->Height;  |
| 1015 |   |
| 1016 | // At the end of decoding _either_ decoded4b _or_ decoded4f will be valid, not both.  |
| 1017 | // The decoded4b format used for LDR images.  |
| 1018 | // The decoded4f format used for HDR images.  |
| 1019 | tColour4b* decoded4b = nullptr;  |
| 1020 | tColour4f* decoded4f = nullptr;  |
| 1021 | DecodeResult result = DecodePixelData  |
| 1022 | (  |
| 1023 | layer->PixelFormat, data: layer->Data, dataSize: layer->GetDataSize(),  |
| 1024 | width: w, height: h, dstLDR&: decoded4b, dstHDR&: decoded4f  |
| 1025 | );  |
| 1026 |   |
| 1027 | if (result != DecodeResult::Success)  |
| 1028 | {  |
| 1029 | Clear();  |
| 1030 | switch (result)  |
| 1031 | {  |
| 1032 | case DecodeResult::PackedDecodeError: SetStateBit(StateBit::Fatal_PackedDecodeError); break;  |
| 1033 | case DecodeResult::BlockDecodeError: SetStateBit(StateBit::Fatal_BCDecodeError); break;  |
| 1034 | case DecodeResult::ASTCDecodeError: SetStateBit(StateBit::Fatal_ASTCDecodeError); break;  |
| 1035 | default: SetStateBit(StateBit::Fatal_PixelFormatNotSupported); break;  |
| 1036 | }  |
| 1037 | return false;  |
| 1038 | }  |
| 1039 |   |
| 1040 | // Apply any decode flags.  |
| 1041 | tAssert(decoded4b || decoded4f);  |
| 1042 | bool flagTone = (params.Flags & tImageKTX::LoadFlag_ToneMapExposure) ? true : false;  |
| 1043 | bool flagSRGB = (params.Flags & tImageKTX::LoadFlag_SRGBCompression) ? true : false;  |
| 1044 | bool flagGama = (params.Flags & tImageKTX::LoadFlag_GammaCompression)? true : false;  |
| 1045 | if (decoded4f && (flagTone || flagSRGB || flagGama))  |
| 1046 | {  |
| 1047 | for (int p = 0; p < w*h; p++)  |
| 1048 | {  |
| 1049 | tColour4f& colour = decoded4f[p];  |
| 1050 | if (flagTone)  |
| 1051 | colour.TonemapExposure(exposure: params.Exposure, chans: tCompBit_RGB);  |
| 1052 | if (flagSRGB)  |
| 1053 | colour.LinearToSRGB(chans: tCompBit_RGB);  |
| 1054 | if (flagGama)  |
| 1055 | colour.LinearToGamma(gamma: params.Gamma, chans: tCompBit_RGB);  |
| 1056 | }  |
| 1057 | }  |
| 1058 | if (decoded4b && (flagSRGB || flagGama))  |
| 1059 | {  |
| 1060 | for (int p = 0; p < w*h; p++)  |
| 1061 | {  |
| 1062 | tColour4f colour(decoded4b[p]);  |
| 1063 | if (flagSRGB)  |
| 1064 | colour.LinearToSRGB(chans: tCompBit_RGB);  |
| 1065 | if (flagGama)  |
| 1066 | colour.LinearToGamma(gamma: params.Gamma, chans: tCompBit_RGB);  |
| 1067 | decoded4b[p].SetR(colour.R);  |
| 1068 | decoded4b[p].SetG(colour.G);  |
| 1069 | decoded4b[p].SetB(colour.B);  |
| 1070 | }  |
| 1071 | }  |
| 1072 |   |
| 1073 | // Update the layer with the 32-bit RGBA decoded data. If the data was HDR (float)  |
| 1074 | // convert it to 32 bit. Start by getting ride of the existing layer pixel data.  |
| 1075 | delete[] layer->Data;  |
| 1076 | if (decoded4f)  |
| 1077 | {  |
| 1078 | tAssert(!decoded4b);  |
| 1079 | decoded4b = new tColour4b[w*h];  |
| 1080 | for (int p = 0; p < w*h; p++)  |
| 1081 | decoded4b[p].Set(decoded4f[p]);  |
| 1082 | delete[] decoded4f;  |
| 1083 | }  |
| 1084 |   |
| 1085 | // Possibly spread the L/Red channel.  |
| 1086 | if (spread && tIsLuminanceFormat(format: layer->PixelFormat))  |
| 1087 | {  |
| 1088 | for (int p = 0; p < w*h; p++)  |
| 1089 | {  |
| 1090 | decoded4b[p].G = decoded4b[p].R;  |
| 1091 | decoded4b[p].B = decoded4b[p].R;  |
| 1092 | }  |
| 1093 | }  |
| 1094 |   |
| 1095 | layer->Data = (uint8*)decoded4b;  |
| 1096 | layer->PixelFormat = tPixelFormat::R8G8B8A8;  |
| 1097 |   |
| 1098 | // We've got one more chance to reverse the rows here (if we still need to) because we were asked to decode.  |
| 1099 | if (reverseRowOrderRequested && !RowReversalOperationPerformed && (layer->PixelFormat == tPixelFormat::R8G8B8A8))  |
| 1100 | {  |
| 1101 | // This shouldn't ever fail. Too easy to reverse RGBA 32-bit.  |
| 1102 | uint8* reversedRowData = tImage::CreateReversedRowData(pixelData: layer->Data, pixelDataFormat: layer->PixelFormat, numBlocksW: w, numBlocksH: h);  |
| 1103 | tAssert(reversedRowData);  |
| 1104 | delete[] layer->Data;  |
| 1105 | layer->Data = reversedRowData;  |
| 1106 | didRowReversalAfterDecode = true;  |
| 1107 | }  |
| 1108 |   |
| 1109 | if ((params.Flags & LoadFlag_SwizzleBGR2RGB) && (layer->PixelFormat == tPixelFormat::R8G8B8A8))  |
| 1110 | {  |
| 1111 | for (int xy = 0; xy < w*h; xy++)  |
| 1112 | {  |
| 1113 | tColour4b& col = ((tColour4b*)layer->Data)[xy];  |
| 1114 | tStd::tSwap(a&: col.R, b&: col.B);  |
| 1115 | }  |
| 1116 | }  |
| 1117 | }  |
| 1118 | }  |
| 1119 |   |
| 1120 | if (reverseRowOrderRequested && !RowReversalOperationPerformed && didRowReversalAfterDecode)  |
| 1121 | RowReversalOperationPerformed = true;  |
| 1122 |   |
| 1123 | if (params.Flags & LoadFlag_SRGBCompression) ColourProfile = tColourProfile::LDRsRGB_LDRlA;  |
| 1124 | if (params.Flags & LoadFlag_GammaCompression) ColourProfile = tColourProfile::LDRgRGB_LDRlA;  |
| 1125 |   |
| 1126 | // All images decoded. Can now set the object's pixel format. We do _not_ set the PixelFormatSrc here!  |
| 1127 | PixelFormat = tPixelFormat::R8G8B8A8;  |
| 1128 |   |
| 1129 | if (reverseRowOrderRequested && !RowReversalOperationPerformed)  |
| 1130 | SetStateBit(StateBit::Conditional_CouldNotFlipRows);  |
| 1131 |   |
| 1132 | ktxTexture_Destroy(texture);  |
| 1133 | SetStateBit(StateBit::Valid);  |
| 1134 | tAssert(IsValid());  |
| 1135 | return true;  |
| 1136 | }  |
| 1137 |   |
| 1138 |   |
| 1139 | const char* tImageKTX::GetStateDesc(StateBit state)  |
| 1140 | {  |
| 1141 | return StateDescriptions[int(state)];  |
| 1142 | }  |
| 1143 |   |
| 1144 |   |
| 1145 | const char* tImageKTX::StateDescriptions[] =  |
| 1146 | {  |
| 1147 | "Valid" ,  |
| 1148 | "Conditional Valid. Image rows could not be flipped." ,  |
| 1149 | "Conditional Valid. Image has dimension not multiple of four." ,  |
| 1150 | "Conditional Valid. Image has dimension not power of two." ,  |
| 1151 | "Conditional Valid. KTX extension doesn't match file version." ,  |
| 1152 | "Fatal Error. File does not exist." ,  |
| 1153 | "Fatal Error. Incorrect file type. Must be a KTX or KTX2 file." ,  |
| 1154 | "Fatal Error. LibKTX could not parse file." ,  |
| 1155 | "Fatal Error. KTX file corrupted." ,  |
| 1156 | "Fatal Error. Incorrect Dimensions." ,  |
| 1157 | "Fatal Error. KTX volume textures not supported." ,  |
| 1158 | "Fatal Error. Unsupported pixel format." ,  |
| 1159 | "Fatal Error. Invalid pixel data offset." ,  |
| 1160 | "Fatal Error. Maximum number of mipmap levels exceeded." ,  |
| 1161 | "Fatal Error. Unable to decode packed pixels." ,  |
| 1162 | "Fatal Error. Unable to decode BC pixels." ,  |
| 1163 | "Fatal Error. Unable to decode ASTC pixels."   |
| 1164 | };  |
| 1165 | tStaticAssert(tNumElements(tImageKTX::StateDescriptions) == int(tImageKTX::StateBit::NumStateBits));  |
| 1166 | tStaticAssert(int(tImageKTX::StateBit::NumStateBits) <= int(tImageKTX::StateBit::MaxStateBits));  |
| 1167 |   |
| 1168 |   |
| 1169 | }  |
| 1170 | |