@loaders.gl/textures
Version:
Framework-independent loaders for compressed and super compressed (basis) textures
242 lines (207 loc) • 8.69 kB
text/typescript
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
/* eslint-disable camelcase */
// Forked from PicoGL: https://github.com/tsherif/picogl.js/blob/master/examples/utils/utils.js
// Copyright (c) 2017 Tarek Sherif, The MIT License (MIT)
import type {TextureFormat, TextureLevel} from '@loaders.gl/schema';
import {extractMipmapImages} from '../utils/extract-mipmap-images';
const PVR_CONSTANTS: Record<string, number> = {
MAGIC_NUMBER: 0x03525650,
MAGIC_NUMBER_EXTRA: 0x50565203,
HEADER_LENGTH: 13,
HEADER_SIZE: 52,
MAGIC_NUMBER_INDEX: 0,
PIXEL_FORMAT_INDEX: 2,
COLOUR_SPACE_INDEX: 4,
HEIGHT_INDEX: 6,
WIDTH_INDEX: 7,
MIPMAPCOUNT_INDEX: 11,
METADATA_SIZE_INDEX: 12
};
const PVR_TEXTURE_FORMATS: Record<number, TextureFormat[]> = {
0: ['pvrtc-rgb2unorm-webgl'],
1: ['pvrtc-rgba2unorm-webgl'],
2: ['pvrtc-rgb4unorm-webgl'],
3: ['pvrtc-rgba4unorm-webgl'],
6: ['etc1-rgb-unorm-webgl'],
7: ['bc1-rgb-unorm-webgl'],
9: ['bc2-rgba-unorm'],
11: ['bc3-rgba-unorm'],
22: ['etc2-rgb8unorm'],
23: ['etc2-rgba8unorm'],
24: ['etc2-rgb8a1unorm'],
25: ['eac-r11unorm'],
26: ['eac-rg11unorm'],
27: ['astc-4x4-unorm', 'astc-4x4-unorm-srgb'],
28: ['astc-5x4-unorm', 'astc-5x4-unorm-srgb'],
29: ['astc-5x5-unorm', 'astc-5x5-unorm-srgb'],
30: ['astc-6x5-unorm', 'astc-6x5-unorm-srgb'],
31: ['astc-6x6-unorm', 'astc-6x6-unorm-srgb'],
32: ['astc-8x5-unorm', 'astc-8x5-unorm-srgb'],
33: ['astc-8x6-unorm', 'astc-8x6-unorm-srgb'],
34: ['astc-8x8-unorm', 'astc-8x8-unorm-srgb'],
35: ['astc-10x5-unorm', 'astc-10x5-unorm-srgb'],
36: ['astc-10x6-unorm', 'astc-10x6-unorm-srgb'],
37: ['astc-10x8-unorm', 'astc-10x8-unorm-srgb'],
38: ['astc-10x10-unorm', 'astc-10x10-unorm-srgb'],
39: ['astc-12x10-unorm', 'astc-12x10-unorm-srgb'],
40: ['astc-12x12-unorm', 'astc-12x12-unorm-srgb']
};
const PVR_SIZE_FUNCTIONS: Record<number, (width: number, height: number) => number> = {
0: pvrtc2bppSize,
1: pvrtc2bppSize,
2: pvrtc4bppSize,
3: pvrtc4bppSize,
6: dxtEtcSmallSize,
7: dxtEtcSmallSize,
9: dxtEtcAstcBigSize,
11: dxtEtcAstcBigSize,
22: dxtEtcSmallSize,
23: dxtEtcAstcBigSize,
24: dxtEtcSmallSize,
25: dxtEtcSmallSize,
26: dxtEtcAstcBigSize,
27: dxtEtcAstcBigSize,
28: atc5x4Size,
29: atc5x5Size,
30: atc6x5Size,
31: atc6x6Size,
32: atc8x5Size,
33: atc8x6Size,
34: atc8x8Size,
35: atc10x5Size,
36: atc10x6Size,
37: atc10x8Size,
38: atc10x10Size,
39: atc12x10Size,
40: atc12x12Size
};
/**
* Check if data is in "PVR" format by its magic number
* @param data - binary data of compressed texture
* @returns true - data in "PVR" format, else - false
*/
export function isPVR(data: ArrayBuffer): boolean {
const header = new Uint32Array(data, 0, PVR_CONSTANTS.HEADER_LENGTH);
const version = header[PVR_CONSTANTS.MAGIC_NUMBER_INDEX];
return version === PVR_CONSTANTS.MAGIC_NUMBER || version === PVR_CONSTANTS.MAGIC_NUMBER_EXTRA;
}
/**
* Parse texture data as "PVR" format
* @param data - binary data of compressed texture
* @returns Array of the texture levels
* @see http://cdn.imgtec.com/sdk-documentation/PVR+File+Format.Specification.pdf
*/
export function parsePVR(data: ArrayBuffer): TextureLevel[] {
const header = new Uint32Array(data, 0, PVR_CONSTANTS.HEADER_LENGTH);
const pvrFormat = header[PVR_CONSTANTS.PIXEL_FORMAT_INDEX];
const colourSpace = header[PVR_CONSTANTS.COLOUR_SPACE_INDEX];
const textureFormats = PVR_TEXTURE_FORMATS[pvrFormat] || [];
const textureFormat =
textureFormats.length > 1 && colourSpace ? textureFormats[1] : textureFormats[0];
const sizeFunction = PVR_SIZE_FUNCTIONS[pvrFormat];
const mipMapLevels = header[PVR_CONSTANTS.MIPMAPCOUNT_INDEX];
const width = header[PVR_CONSTANTS.WIDTH_INDEX];
const height = header[PVR_CONSTANTS.HEIGHT_INDEX];
const dataOffset = PVR_CONSTANTS.HEADER_SIZE + header[PVR_CONSTANTS.METADATA_SIZE_INDEX];
const image = new Uint8Array(data, dataOffset);
return extractMipmapImages(image, {
mipMapLevels,
width,
height,
sizeFunction,
textureFormat
});
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/
function pvrtc2bppSize(width: number, height: number): number {
width = Math.max(width, 16);
height = Math.max(height, 8);
return (width * height) / 4;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/
function pvrtc4bppSize(width: number, height: number): number {
width = Math.max(width, 8);
height = Math.max(height, 8);
return (width * height) / 2;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc/
// Size for:
// COMPRESSED_RGB_S3TC_DXT1_EXT
// COMPRESSED_R11_EAC
// COMPRESSED_SIGNED_R11_EAC
// COMPRESSED_RGB8_ETC2
// COMPRESSED_SRGB8_ETC2
// COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2
// COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2
function dxtEtcSmallSize(width: number, height: number): number {
return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 8;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc/
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
// Size for:
// COMPRESSED_RGBA_S3TC_DXT3_EXT
// COMPRESSED_RGBA_S3TC_DXT5_EXT
// COMPRESSED_RG11_EAC
// COMPRESSED_SIGNED_RG11_EAC
// COMPRESSED_RGBA8_ETC2_EAC
// COMPRESSED_SRGB8_ALPHA8_ETC2_EAC
// COMPRESSED_RGBA_ASTC_4x4_KHR
function dxtEtcAstcBigSize(width: number, height: number): number {
return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 16;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc5x4Size(width: number, height: number): number {
return Math.floor((width + 4) / 5) * Math.floor((height + 3) / 4) * 16;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc5x5Size(width: number, height: number): number {
return Math.floor((width + 4) / 5) * Math.floor((height + 4) / 5) * 16;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc6x5Size(width: number, height: number): number {
return Math.floor((width + 5) / 6) * Math.floor((height + 4) / 5) * 16;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc6x6Size(width: number, height: number): number {
return Math.floor((width + 5) / 6) * Math.floor((height + 5) / 6) * 16;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc8x5Size(width: number, height: number): number {
return Math.floor((width + 7) / 8) * Math.floor((height + 4) / 5) * 16;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc8x6Size(width: number, height: number): number {
return Math.floor((width + 7) / 8) * Math.floor((height + 5) / 6) * 16;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc8x8Size(width: number, height: number): number {
return Math.floor((width + 7) / 8) * Math.floor((height + 7) / 8) * 16;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc10x5Size(width: number, height: number): number {
return Math.floor((width + 9) / 10) * Math.floor((height + 4) / 5) * 16;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc10x6Size(width: number, height: number): number {
return Math.floor((width + 9) / 10) * Math.floor((height + 5) / 6) * 16;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc10x8Size(width: number, height: number): number {
return Math.floor((width + 9) / 10) * Math.floor((height + 7) / 8) * 16;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc10x10Size(width: number, height: number): number {
return Math.floor((width + 9) / 10) * Math.floor((height + 9) / 10) * 16;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc12x10Size(width: number, height: number): number {
return Math.floor((width + 11) / 12) * Math.floor((height + 9) / 10) * 16;
}
// https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
function atc12x12Size(width: number, height: number): number {
return Math.floor((width + 11) / 12) * Math.floor((height + 11) / 12) * 16;
}