Skip to content

Commit b6abc10

Browse files
authored
Add support for a few more ktx pixel formats (playcanvas#3059)
* add support for a few more ktx pixel formats
1 parent 096c356 commit b6abc10

File tree

2 files changed

+60
-53
lines changed

2 files changed

+60
-53
lines changed

src/graphics/graphics-device.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1748,7 +1748,7 @@ class GraphicsDevice extends EventHandler {
17481748
case PIXELFORMAT_111110F: // WebGL2 only
17491749
texture._glFormat = gl.RGB;
17501750
texture._glInternalFormat = gl.R11F_G11F_B10F;
1751-
texture._glPixelType = gl.FLOAT;
1751+
texture._glPixelType = gl.UNSIGNED_INT_10F_11F_11F_REV;
17521752
break;
17531753
case PIXELFORMAT_SRGB: // WebGL2 only
17541754
texture._glFormat = gl.RGB;

src/resources/parser/texture/ktx.js

Lines changed: 59 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ import {
55
PIXELFORMAT_DXT1, PIXELFORMAT_DXT3, PIXELFORMAT_DXT5,
66
PIXELFORMAT_ETC1, PIXELFORMAT_ETC2_RGB, PIXELFORMAT_ETC2_RGBA,
77
PIXELFORMAT_PVRTC_4BPP_RGB_1, PIXELFORMAT_PVRTC_2BPP_RGB_1, PIXELFORMAT_PVRTC_4BPP_RGBA_1, PIXELFORMAT_PVRTC_2BPP_RGBA_1,
8+
PIXELFORMAT_R8_G8_B8, PIXELFORMAT_R8_G8_B8_A8, PIXELFORMAT_SRGB, PIXELFORMAT_SRGBA,
9+
PIXELFORMAT_111110F, PIXELFORMAT_RGB16F, PIXELFORMAT_RGBA16F,
810
TEXHINT_ASSET
911
} from '../../../graphics/constants.js';
1012
import { Texture } from '../../../graphics/texture.js';
1113

1214
// Defined here: https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
1315
const IDENTIFIER = [0x58544BAB, 0xBB313120, 0x0A1A0A0D]; // «KTX 11»\r\n\x1A\n
16+
1417
const KNOWN_FORMATS = {
18+
// compressed formats
1519
0x83F0: PIXELFORMAT_DXT1,
1620
0x83F2: PIXELFORMAT_DXT3,
1721
0x83F3: PIXELFORMAT_DXT5,
@@ -21,9 +25,24 @@ const KNOWN_FORMATS = {
2125
0x8C00: PIXELFORMAT_PVRTC_4BPP_RGB_1,
2226
0x8C01: PIXELFORMAT_PVRTC_2BPP_RGB_1,
2327
0x8C02: PIXELFORMAT_PVRTC_4BPP_RGBA_1,
24-
0x8C03: PIXELFORMAT_PVRTC_2BPP_RGBA_1
28+
0x8C03: PIXELFORMAT_PVRTC_2BPP_RGBA_1,
29+
30+
// uncompressed formats
31+
0x8051: PIXELFORMAT_R8_G8_B8, // GL_RGB8
32+
0x8058: PIXELFORMAT_R8_G8_B8_A8, // GL_RGBA8
33+
0x8C41: PIXELFORMAT_SRGB, // GL_SRGB8
34+
0x8C43: PIXELFORMAT_SRGBA, // GL_SRGB8_ALPHA8
35+
0x8C3A: PIXELFORMAT_111110F, // GL_R11F_G11F_B10F
36+
0x881B: PIXELFORMAT_RGB16F, // GL_RGB16F
37+
0x881A: PIXELFORMAT_RGBA16F // GL_RGBA16F
2538
};
2639

40+
function createContainer(pixelFormat, buffer, byteOffset, byteSize) {
41+
return (pixelFormat === PIXELFORMAT_111110F) ?
42+
new Uint32Array(buffer, byteOffset, byteSize / 4) :
43+
new Uint8Array(buffer, byteOffset, byteSize);
44+
}
45+
2746
/**
2847
* @private
2948
* @class
@@ -73,99 +92,87 @@ class KtxParser {
7392
}
7493

7594
parse(data) {
76-
const headerU32 = new Uint32Array(data, 0, 16);
95+
const dataU32 = new Uint32Array(data);
7796

78-
if (IDENTIFIER[0] !== headerU32[0] || IDENTIFIER[1] !== headerU32[1] || IDENTIFIER[2] !== headerU32[2]) {
97+
// check magic bits
98+
if (IDENTIFIER[0] !== dataU32[0] ||
99+
IDENTIFIER[1] !== dataU32[1] ||
100+
IDENTIFIER[2] !== dataU32[2]) {
79101
// #if _DEBUG
80102
console.warn("Invalid definition header found in KTX file. Expected 0xAB4B5458, 0x203131BB, 0x0D0A1A0A");
81103
// #endif
82104
return null;
83105
}
84106

107+
// unpack header info
85108
const header = {
86-
endianness: headerU32[3], // todo: Use this information
87-
glType: headerU32[4],
88-
glTypeSize: headerU32[5],
89-
glFormat: headerU32[6],
90-
glInternalFormat: headerU32[7],
91-
glBaseInternalFormat: headerU32[8],
92-
pixelWidth: headerU32[9],
93-
pixelHeight: headerU32[10],
94-
pixelDepth: headerU32[11],
95-
numberOfArrayElements: headerU32[12],
96-
numberOfFaces: headerU32[13],
97-
numberOfMipmapLevels: headerU32[14],
98-
bytesOfKeyValueData: headerU32[15]
109+
endianness: dataU32[3], // todo: Use this information
110+
glType: dataU32[4],
111+
glTypeSize: dataU32[5],
112+
glFormat: dataU32[6],
113+
glInternalFormat: dataU32[7],
114+
glBaseInternalFormat: dataU32[8],
115+
pixelWidth: dataU32[9],
116+
pixelHeight: dataU32[10],
117+
pixelDepth: dataU32[11],
118+
numberOfArrayElements: dataU32[12],
119+
numberOfFaces: dataU32[13],
120+
numberOfMipmapLevels: dataU32[14],
121+
bytesOfKeyValueData: dataU32[15]
99122
};
100123

124+
// don't support volume textures
101125
if (header.pixelDepth > 1) {
102126
// #if _DEBUG
103127
console.warn("More than 1 pixel depth not supported!");
104128
// #endif
105129
return null;
106130
}
107131

108-
if (header.numberOfArrayElements > 1) {
132+
// don't support texture arrays
133+
if (header.numberOfArrayElements !== 0) {
109134
// #if _DEBUG
110135
console.warn("Array texture not supported!");
111136
// #endif
112137
return null;
113138
}
114139

115-
if (header.glFormat !== 0) {
116-
// #if _DEBUG
117-
console.warn("We only support compressed formats!");
118-
// #endif
119-
return null;
120-
}
140+
const format = KNOWN_FORMATS[header.glInternalFormat];
121141

122-
if (!KNOWN_FORMATS[header.glInternalFormat]) {
142+
// only support subset of pixel formats
143+
if (format === undefined) {
123144
// #if _DEBUG
124145
console.warn("Unknown glInternalFormat: " + header.glInternalFormat);
125146
// #endif
126147
return null;
127148
}
128149

129-
// Byte offset locating the first byte of texture level data
130-
let offset = (16 * 4) + header.bytesOfKeyValueData;
150+
// offset locating the first byte of texture level data
151+
let offset = 16 + header.bytesOfKeyValueData / 4;
131152

153+
const isCubemap = (header.numberOfFaces > 1);
132154
const levels = [];
133-
let isCubeMap = false;
134155
for (let mipmapLevel = 0; mipmapLevel < (header.numberOfMipmapLevels || 1); mipmapLevel++) {
135-
const imageSizeInBytes = new Uint32Array(data.slice(offset, offset + 4))[0];
136-
offset += 4;
137-
// Currently array textures not supported. Keeping this here for reference.
138-
// for (let arrayElement = 0; arrayElement < (header.numberOfArrayElements || 1); arrayElement++) {
139-
const faceSizeInBytes = imageSizeInBytes / (header.numberOfFaces || 1);
140-
// Create array for cubemaps
141-
if (header.numberOfFaces > 1) {
142-
isCubeMap = true;
156+
const imageSizeInBytes = dataU32[offset++];
157+
158+
if (isCubemap) {
143159
levels.push([]);
144160
}
145-
for (let face = 0; face < header.numberOfFaces; face++) {
146-
// Currently more than 1 pixel depth not supported. Keeping this here for reference.
147-
// for (let zSlice = 0; zSlice < (header.pixelDepth || 1); zSlice++) {
148-
const mipData = new Uint8Array(data, offset, faceSizeInBytes);
149-
// Handle cubemaps
150-
if (header.numberOfFaces > 1) {
151-
levels[mipmapLevel].push(mipData);
152-
} else {
153-
levels.push(mipData);
154-
}
155-
offset += faceSizeInBytes;
156-
// }
161+
162+
const target = isCubemap ? levels[mipmapLevel] : levels;
163+
164+
for (let face = 0; face < (isCubemap ? 6 : 1); ++face) {
165+
target.push(createContainer(format, data, offset * 4, imageSizeInBytes));
166+
offset += (imageSizeInBytes + 3) >> 2;
157167
}
158-
offset += 3 - ((offset + 3) % 4);
159-
// }
160-
// offset += 3 - ((offset + 3) % 4);
161168
}
162169

163170
return {
164-
format: KNOWN_FORMATS[header.glInternalFormat],
171+
format: format,
165172
width: header.pixelWidth,
166173
height: header.pixelHeight,
167174
levels: levels,
168-
cubemap: isCubeMap
175+
cubemap: isCubemap
169176
};
170177
}
171178
}

0 commit comments

Comments
 (0)