UNPKG

phaser

Version:

A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.

492 lines (396 loc) 16.9 kB
var PVRParser = require('../../../src/textures/parsers/PVRParser'); // Header layout (Uint32Array, 13 elements = 52 bytes): // [0] version (0x03525650 = v3) // [1] flags // [2] pixel format low (v3 uses this when version matches) // [3] pixel format high (v2 uses this when version does not match) // [4] colour space (0=linear, 1=sRGB) // [5] channel type // [6] height // [7] width // [8] depth // [9] num surfaces // [10] num faces // [11] mipmap count // [12] metadata size var V3_VERSION = 0x03525650; var HEADER_SIZE = 52; // Mirror the size functions from the source to compute required buffer sizes function calcPVRTC2bppSize (w, h) { return Math.max(w, 16) * Math.max(h, 8) / 4; } function calcPVRTC4bppSize (w, h) { return Math.max(w, 8) * Math.max(h, 8) / 2; } function calcBPTCSize (w, h) { return Math.ceil(w / 4) * Math.ceil(h / 4) * 16; } function calcDXTEtcSmallSize (w, h) { return Math.floor((w + 3) / 4) * Math.floor((h + 3) / 4) * 8; } function calcDXTEtcAstcBigSize (w, h) { return Math.floor((w + 3) / 4) * Math.floor((h + 3) / 4) * 16; } function calcASTCSize (bw, bh, w, h) { return Math.floor((w + bw - 1) / bw) * Math.floor((h + bh - 1) / bh) * 16; } var SIZE_FUNCS = { 0: calcPVRTC2bppSize, 1: calcPVRTC2bppSize, 2: calcPVRTC4bppSize, 3: calcPVRTC4bppSize, 6: calcDXTEtcSmallSize, 7: calcDXTEtcSmallSize, 8: calcDXTEtcAstcBigSize, 9: calcDXTEtcAstcBigSize, 11: calcDXTEtcAstcBigSize, 14: calcBPTCSize, 15: calcBPTCSize, 22: calcDXTEtcSmallSize, 23: calcDXTEtcAstcBigSize, 24: calcDXTEtcSmallSize, 25: calcDXTEtcSmallSize, 26: calcDXTEtcAstcBigSize, 27: calcDXTEtcAstcBigSize, 28: function (w, h) { return calcASTCSize(5, 4, w, h); }, 29: function (w, h) { return calcASTCSize(5, 5, w, h); }, 30: function (w, h) { return calcASTCSize(6, 5, w, h); }, 31: function (w, h) { return calcASTCSize(6, 6, w, h); }, 32: function (w, h) { return calcASTCSize(8, 5, w, h); }, 33: function (w, h) { return calcASTCSize(8, 6, w, h); }, 34: function (w, h) { return calcASTCSize(8, 8, w, h); }, 35: function (w, h) { return calcASTCSize(10, 5, w, h); }, 36: function (w, h) { return calcASTCSize(10, 6, w, h); }, 37: function (w, h) { return calcASTCSize(10, 8, w, h); }, 38: function (w, h) { return calcASTCSize(10, 10, w, h); }, 39: function (w, h) { return calcASTCSize(12, 10, w, h); }, 40: function (w, h) { return calcASTCSize(12, 12, w, h); } }; function calcTotalDataSize (pvrFormat, width, height, mipmapLevels) { var sizeFunc = SIZE_FUNCS[pvrFormat]; var total = 0; var lw = width; var lh = height; for (var i = 0; i < mipmapLevels; i++) { total += sizeFunc(lw, lh); lw = Math.max(1, lw >> 1); lh = Math.max(1, lh >> 1); } return total; } function createPVRBuffer (version, pvrFormat, colorSpace, width, height, mipmapLevels, metadataSize) { metadataSize = metadataSize || 0; var dataSize = calcTotalDataSize(pvrFormat, width, height, mipmapLevels); var bufferSize = HEADER_SIZE + metadataSize + dataSize; var buffer = new ArrayBuffer(bufferSize); var header = new Uint32Array(buffer, 0, 13); header[0] = version; if (version === V3_VERSION) { header[2] = pvrFormat; header[3] = 0; } else { header[2] = 0; header[3] = pvrFormat; } header[4] = colorSpace; header[6] = height; header[7] = width; header[11] = mipmapLevels; header[12] = metadataSize; return buffer; } describe('Phaser.Textures.Parsers.PVRParser', function () { describe('return value structure', function () { it('should return an object with mipmaps, width, height, internalFormat, compressed, generateMipmap', function () { var buffer = createPVRBuffer(V3_VERSION, 2, 0, 8, 8, 1, 0); var result = PVRParser(buffer); expect(result).toHaveProperty('mipmaps'); expect(result).toHaveProperty('width'); expect(result).toHaveProperty('height'); expect(result).toHaveProperty('internalFormat'); expect(result).toHaveProperty('compressed'); expect(result).toHaveProperty('generateMipmap'); }); it('should set compressed to true', function () { var buffer = createPVRBuffer(V3_VERSION, 2, 0, 8, 8, 1, 0); var result = PVRParser(buffer); expect(result.compressed).toBe(true); }); it('should set generateMipmap to false', function () { var buffer = createPVRBuffer(V3_VERSION, 2, 0, 8, 8, 1, 0); var result = PVRParser(buffer); expect(result.generateMipmap).toBe(false); }); }); describe('width and height parsing', function () { it('should correctly extract width from header[7]', function () { var buffer = createPVRBuffer(V3_VERSION, 6, 0, 64, 32, 1, 0); var result = PVRParser(buffer); expect(result.width).toBe(64); }); it('should correctly extract height from header[6]', function () { var buffer = createPVRBuffer(V3_VERSION, 6, 0, 64, 32, 1, 0); var result = PVRParser(buffer); expect(result.height).toBe(32); }); it('should handle a square texture', function () { var buffer = createPVRBuffer(V3_VERSION, 6, 0, 32, 32, 1, 0); var result = PVRParser(buffer); expect(result.width).toBe(32); expect(result.height).toBe(32); }); it('should return width and height independent of mipmap count', function () { var buffer = createPVRBuffer(V3_VERSION, 6, 0, 8, 8, 3, 0); var result = PVRParser(buffer); expect(result.width).toBe(8); expect(result.height).toBe(8); }); }); describe('version handling', function () { it('should use header[2] for pvrFormat when version matches 0x03525650 (v3)', function () { // Format 2 (PVRTC4bpp RGB) -> glFormat[0] = 0x8C00 var buffer = createPVRBuffer(V3_VERSION, 2, 0, 8, 8, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x8C00); }); it('should use header[3] for pvrFormat when version does not match (v2)', function () { // v2 file: header[2]=0, header[3]=2 -> format 2 -> glFormat[0] = 0x8C00 var buffer = createPVRBuffer(0x00000000, 2, 0, 8, 8, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x8C00); }); it('should produce different internalFormats for format 2 vs format 3 in v3 mode', function () { // Format 2 -> 0x8C00, Format 3 -> 0x8C02 var buffer2 = createPVRBuffer(V3_VERSION, 2, 0, 8, 8, 1, 0); var buffer3 = createPVRBuffer(V3_VERSION, 3, 0, 8, 8, 1, 0); var result2 = PVRParser(buffer2); var result3 = PVRParser(buffer3); expect(result2.internalFormat).not.toBe(result3.internalFormat); }); }); describe('internalFormat selection', function () { it('should select glFormat[0] when colorSpace is 0 (linear)', function () { // Format 7 glFormat = [0x83F0, 0x8C4C] var buffer = createPVRBuffer(V3_VERSION, 7, 0, 4, 4, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x83F0); }); it('should select glFormat[1] when colorSpace is 1 (sRGB)', function () { // Format 7 glFormat = [0x83F0, 0x8C4C] var buffer = createPVRBuffer(V3_VERSION, 7, 1, 4, 4, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x8C4C); }); it('should return correct internalFormat for PVRTC 2bpp RGB (format 0)', function () { // Format 0 glFormat = [0x8C01] var buffer = createPVRBuffer(V3_VERSION, 0, 0, 16, 8, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x8C01); }); it('should return correct internalFormat for PVRTC 2bpp RGBA (format 1)', function () { // Format 1 glFormat = [0x8C03] var buffer = createPVRBuffer(V3_VERSION, 1, 0, 16, 8, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x8C03); }); it('should return correct internalFormat for PVRTC 4bpp RGB (format 2)', function () { // Format 2 glFormat = [0x8C00] var buffer = createPVRBuffer(V3_VERSION, 2, 0, 8, 8, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x8C00); }); it('should return correct internalFormat for PVRTC 4bpp RGBA (format 3)', function () { // Format 3 glFormat = [0x8C02] var buffer = createPVRBuffer(V3_VERSION, 3, 0, 8, 8, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x8C02); }); it('should return correct internalFormat for ETC1 RGB (format 6)', function () { // Format 6 glFormat = [0x8D64] var buffer = createPVRBuffer(V3_VERSION, 6, 0, 4, 4, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x8D64); }); it('should return correct internalFormat for DXT1 linear (format 7, colorSpace 0)', function () { // Format 7 glFormat = [0x83F0, 0x8C4C] var buffer = createPVRBuffer(V3_VERSION, 7, 0, 4, 4, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x83F0); }); it('should return correct internalFormat for BPTC float (format 14, colorSpace 0)', function () { // Format 14 glFormat = [0x8E8E, 0x8E8F] var buffer = createPVRBuffer(V3_VERSION, 14, 0, 4, 4, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x8E8E); }); it('should return correct internalFormat for BPTC float (format 14, colorSpace 1)', function () { // Format 14 glFormat = [0x8E8E, 0x8E8F] var buffer = createPVRBuffer(V3_VERSION, 14, 1, 4, 4, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x8E8F); }); it('should return correct internalFormat for ASTC 4x4 linear (format 27, colorSpace 0)', function () { // Format 27 glFormat = [0x93B0, 0x93D0] var buffer = createPVRBuffer(V3_VERSION, 27, 0, 4, 4, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x93B0); }); it('should return correct internalFormat for ASTC 4x4 sRGB (format 27, colorSpace 1)', function () { // Format 27 glFormat = [0x93B0, 0x93D0] var buffer = createPVRBuffer(V3_VERSION, 27, 1, 4, 4, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x93D0); }); it('should return correct internalFormat for ASTC 8x8 (format 34)', function () { // Format 34 glFormat = [0x93B7, 0x93D7] var buffer = createPVRBuffer(V3_VERSION, 34, 0, 8, 8, 1, 0); var result = PVRParser(buffer); expect(result.internalFormat).toBe(0x93B7); }); }); describe('mipmap levels', function () { it('should return mipmaps array with length 1 for a single mipmap level', function () { var buffer = createPVRBuffer(V3_VERSION, 6, 0, 8, 8, 1, 0); var result = PVRParser(buffer); expect(result.mipmaps.length).toBe(1); }); it('should return mipmaps array with length 3 for three mipmap levels', function () { var buffer = createPVRBuffer(V3_VERSION, 6, 0, 8, 8, 3, 0); var result = PVRParser(buffer); expect(result.mipmaps.length).toBe(3); }); it('each mipmap should have data, width, and height properties', function () { var buffer = createPVRBuffer(V3_VERSION, 6, 0, 8, 8, 2, 0); var result = PVRParser(buffer); expect(result.mipmaps[0]).toHaveProperty('data'); expect(result.mipmaps[0]).toHaveProperty('width'); expect(result.mipmaps[0]).toHaveProperty('height'); expect(result.mipmaps[1]).toHaveProperty('data'); expect(result.mipmaps[1]).toHaveProperty('width'); expect(result.mipmaps[1]).toHaveProperty('height'); }); it('mipmap[0] should have base width and height', function () { var buffer = createPVRBuffer(V3_VERSION, 6, 0, 16, 8, 2, 0); var result = PVRParser(buffer); expect(result.mipmaps[0].width).toBe(16); expect(result.mipmaps[0].height).toBe(8); }); it('mipmap dimensions should halve at each level', function () { var buffer = createPVRBuffer(V3_VERSION, 6, 0, 8, 8, 3, 0); var result = PVRParser(buffer); expect(result.mipmaps[0].width).toBe(8); expect(result.mipmaps[0].height).toBe(8); expect(result.mipmaps[1].width).toBe(4); expect(result.mipmaps[1].height).toBe(4); expect(result.mipmaps[2].width).toBe(2); expect(result.mipmaps[2].height).toBe(2); }); it('mipmap dimensions should not go below 1', function () { var buffer = createPVRBuffer(V3_VERSION, 6, 0, 4, 4, 3, 0); var result = PVRParser(buffer); expect(result.mipmaps[0].width).toBe(4); expect(result.mipmaps[0].height).toBe(4); expect(result.mipmaps[1].width).toBe(2); expect(result.mipmaps[1].height).toBe(2); expect(result.mipmaps[2].width).toBe(1); expect(result.mipmaps[2].height).toBe(1); }); it('mipmap data should be a Uint8Array', function () { var buffer = createPVRBuffer(V3_VERSION, 6, 0, 4, 4, 1, 0); var result = PVRParser(buffer); expect(result.mipmaps[0].data).toBeInstanceOf(Uint8Array); }); it('mipmap data length should equal the computed compressed size for format 6', function () { // DXTEtcSmallSize(4, 4) = floor(7/4)*floor(7/4)*8 = 1*1*8 = 8 var buffer = createPVRBuffer(V3_VERSION, 6, 0, 4, 4, 1, 0); var result = PVRParser(buffer); expect(result.mipmaps[0].data.length).toBe(8); }); it('mipmap data length should equal the computed compressed size for format 2', function () { // PVRTC4bppSize(8, 8) = max(8,8)*max(8,8)/2 = 32 var buffer = createPVRBuffer(V3_VERSION, 2, 0, 8, 8, 1, 0); var result = PVRParser(buffer); expect(result.mipmaps[0].data.length).toBe(32); }); it('consecutive mipmap data views should not overlap', function () { var buffer = createPVRBuffer(V3_VERSION, 6, 0, 8, 8, 2, 0); var result = PVRParser(buffer); var m0 = result.mipmaps[0].data; var m1 = result.mipmaps[1].data; var end0 = m0.byteOffset + m0.byteLength; var start1 = m1.byteOffset; expect(end0).toBeLessThanOrEqual(start1); }); }); describe('data offset with metadata', function () { it('should account for metadata size when computing data start offset', function () { // The byteOffset of mipmaps[0].data should differ by the metadata size var bufferNoMeta = createPVRBuffer(V3_VERSION, 6, 0, 4, 4, 1, 0); var bufferWithMeta = createPVRBuffer(V3_VERSION, 6, 0, 4, 4, 1, 16); var resultNoMeta = PVRParser(bufferNoMeta); var resultWithMeta = PVRParser(bufferWithMeta); var diff = resultWithMeta.mipmaps[0].data.byteOffset - resultNoMeta.mipmaps[0].data.byteOffset; expect(diff).toBe(16); }); it('should handle zero metadata size', function () { var buffer = createPVRBuffer(V3_VERSION, 6, 0, 4, 4, 1, 0); var result = PVRParser(buffer); // dataOffset = 52 + 0 = 52 expect(result.mipmaps[0].data.byteOffset).toBe(52); }); }); });