Skip to content

Commit

Permalink
Add KTXLoader support for loading key-value data from texture (pixijs…
Browse files Browse the repository at this point in the history
  • Loading branch information
ShukantPal committed May 17, 2022
1 parent f07d003 commit 9e3adf0
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 9 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,8 @@ packages/**/index.d.ts
.vscode
pixi.code-workspace

# IntelliJ
*.iml

# webdoc api
dist/docs/pixi.api.json
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions packages/compressed-textures/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
declare namespace GlobalMixins
{
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface BaseTexture
{
ktxKeyValueData: Map<string, DataView>;
}
}
1 change: 1 addition & 0 deletions packages/compressed-textures/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@pixi/constants": "6.3.2",
"@pixi/core": "6.3.2",
"@pixi/loaders": "6.3.2",
"@pixi/settings": "6.3.2",
"@pixi/utils": "6.3.2"
}
}
97 changes: 90 additions & 7 deletions packages/compressed-textures/src/loaders/KTXLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,29 @@ export const TYPES_TO_BYTES_PER_PIXEL: { [id: number]: number } = {
* This KTX loader does not currently support the following features:
* * cube textures
* * 3D textures
* * vendor-specific key/value data parsing
* * endianness conversion for big-endian machines
* * embedded *.basis files
*
* It does supports the following features:
* * multiple textures per file
* * mipmapping (only for compressed formats)
* * vendor-specific key/value data parsing (enable {@link PIXI.KTXLoader.loadKeyValueData})
*
* @class
* @memberof PIXI
* @implements PIXI.ILoaderPlugin
*/
export class KTXLoader
{
/**
* If set to `true`, {@link PIXI.KTXLoader} will parse key-value data in KTX textures. This feature relies
* on the [Encoding Standard]{@link https://encoding.spec.whatwg.org}.
*
* The key-value data will be available on the base-textures as {@code PIXI.BaseTexture.ktxKeyValueData}. They
* will hold a reference to the texture data buffer, so make sure to delete key-value data once you are done
* using it.
*/
static loadKeyValueData = false;

/**
* Called after a KTX file is loaded.
*
Expand All @@ -130,15 +139,25 @@ export class KTXLoader
try
{
const url = resource.name || resource.url;
const { compressed, uncompressed } = KTXLoader.parse(url, resource.data);
const { compressed, uncompressed, kvData } = KTXLoader.parse(url, resource.data);

if (compressed)
{
Object.assign(resource, registerCompressedTextures(
const result = registerCompressedTextures(
url,
compressed,
resource.metadata,
));
);

if (kvData && result.textures)
{
for (const textureId in result.textures)
{
result.textures[textureId].baseTexture.ktxKeyValueData = kvData;
}
}

Object.assign(resource, result);
}
else if (uncompressed)
{
Expand All @@ -157,6 +176,8 @@ export class KTXLoader
));
const cacheID = `${url}-${i + 1}`;

if (kvData) texture.baseTexture.ktxKeyValueData = kvData;

BaseTexture.addToCache(texture.baseTexture, cacheID);
Texture.addToCache(texture, cacheID);

Expand Down Expand Up @@ -188,6 +209,7 @@ export class KTXLoader
private static parse(url: string, arrayBuffer: ArrayBuffer): {
compressed?: CompressedTextureResource[]
uncompressed?: { resource: BufferResource, type: TYPES, format: FORMATS }[]
kvData: Map<string, DataView> | null
}
{
const dataView = new DataView(arrayBuffer);
Expand Down Expand Up @@ -268,6 +290,10 @@ export class KTXLoader
throw new Error('Unable to resolve the pixel format stored in the *.ktx file!');
}

const kvData: Map<string, DataView> | null = KTXLoader.loadKeyValueData
? KTXLoader.parseKvData(dataView, bytesOfKeyValueData, littleEndian)
: null;

const imageByteSize = imagePixels * imagePixelByteSize;
let mipByteSize = imageByteSize;
let mipWidth = pixelWidth;
Expand Down Expand Up @@ -362,7 +388,8 @@ export class KTXLoader
type: glType,
format: convertToInt ? KTXLoader.convertFormatToInteger(glFormat) : glFormat,
};
})
}),
kvData
};
}

Expand All @@ -373,7 +400,8 @@ export class KTXLoader
height: pixelHeight,
levels: numberOfMipmapLevels,
levelBuffers,
}))
})),
kvData
};
}

Expand Down Expand Up @@ -408,4 +436,59 @@ export class KTXLoader
default: return format;
}
}

private static parseKvData(dataView: DataView, bytesOfKeyValueData: number, littleEndian: boolean): Map<string, DataView>
{
const kvData = new Map<string, DataView>();
let bytesIntoKeyValueData = 0;

while (bytesIntoKeyValueData < bytesOfKeyValueData)
{
const keyAndValueByteSize = dataView.getUint32(FILE_HEADER_SIZE + bytesIntoKeyValueData, littleEndian);
const keyAndValueByteOffset = FILE_HEADER_SIZE + bytesIntoKeyValueData + 4;
const valuePadding = 3 - ((keyAndValueByteSize + 3) % 4);

// Bounds check
if (keyAndValueByteSize === 0 || keyAndValueByteSize > bytesOfKeyValueData - bytesIntoKeyValueData)
{
console.error('KTXLoader: keyAndValueByteSize out of bounds');
break;
}

// Note: keyNulByte can't be 0 otherwise the key is an empty string.
let keyNulByte = 0;

for (; keyNulByte < keyAndValueByteSize; keyNulByte++)
{
if (dataView.getUint8(keyAndValueByteOffset + keyNulByte) === 0x00)
{
break;
}
}

if (keyNulByte === -1)
{
console.error('KTXLoader: Failed to find null byte terminating kvData key');
break;
}

const key = new TextDecoder().decode(
new Uint8Array(dataView.buffer, keyAndValueByteOffset, keyNulByte)
);
const value = new DataView(
dataView.buffer,
keyAndValueByteOffset + keyNulByte + 1,
keyAndValueByteSize - keyNulByte - 1,
);

kvData.set(key, value);

// 4 = the keyAndValueByteSize field itself
// keyAndValueByteSize = the bytes taken by the key and value
// valuePadding = extra padding to align with 4 bytes
bytesIntoKeyValueData += 4 + keyAndValueByteSize + valuePadding;
}

return kvData;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ type CompressedTexturesResult = Pick<LoaderResource, 'textures' | 'texture'>;
* @param resources - the resources backing texture data
* @ignore
*/
export function registerCompressedTextures(url: string,
export function registerCompressedTextures(
url: string,
resources: CompressedTextureResource[],
metadata: IResourceMetadata): CompressedTexturesResult
metadata: IResourceMetadata
): CompressedTexturesResult
{
const result: CompressedTexturesResult = {
textures: {},
Expand Down

0 comments on commit 9e3adf0

Please sign in to comment.