/* Copyright (c) 2019 The Khronos Group Inc. Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt file. */ "use strict"; let CompressedTextureUtils = (function() { let formatToString = function(ext, format) { for (let p in ext) { if (ext[p] == format) { return p; } } return "0x" + format.toString(16); }; /** * Make an image element from Uint8Array bitmap data. * @param {number} imageHeight Height of the data in pixels. * @param {number} imageWidth Width of the data in pixels. * @param {number} dataWidth Width of each row in the data buffer, in pixels. * @param {Uint8Array} data Image data buffer to display. Each pixel takes up 4 bytes in the array regardless of the alpha parameter. * @param {boolean} alpha True if alpha data should be taken from data. Otherwise alpha channel is set to 255. * @return {HTMLImageElement} The image element. */ let makeScaledImage = function(imageWidth, imageHeight, dataWidth, data, alpha, opt_scale) { let scale = opt_scale ? opt_scale : 8; let c = document.createElement("canvas"); c.width = imageWidth * scale; c.height = imageHeight * scale; let ctx = c.getContext("2d"); for (let yy = 0; yy < imageHeight; ++yy) { for (let xx = 0; xx < imageWidth; ++xx) { let offset = (yy * dataWidth + xx) * 4; ctx.fillStyle = "rgba(" + data[offset + 0] + "," + data[offset + 1] + "," + data[offset + 2] + "," + (alpha ? data[offset + 3] / 255 : 1) + ")"; ctx.fillRect(xx * scale, yy * scale, scale, scale); } } return wtu.makeImageFromCanvas(c); }; let insertCaptionedImg = function(parent, caption, img) { let div = document.createElement("div"); div.appendChild(img); let label = document.createElement("div"); label.appendChild(document.createTextNode(caption)); div.appendChild(label); parent.appendChild(div); }; /** * @param {WebGLRenderingContextBase} gl * @param {Object} compressedFormats Mapping from format names to format enum values. * @param expectedByteLength A function that takes in width, height and format and returns the expected buffer size in bytes. */ let testCompressedFormatsUnavailableWhenExtensionDisabled = function(gl, compressedFormats, expectedByteLength, testSize) { let tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); for (let name in compressedFormats) { if (compressedFormats.hasOwnProperty(name)) { gl.compressedTexImage2D(gl.TEXTURE_2D, 0, compressedFormats[name], testSize, testSize, 0, new Uint8Array(expectedByteLength(testSize, testSize, compressedFormats[name]))); wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "Trying to use format " + name + " with extension disabled."); } } gl.bindTexture(gl.TEXTURE_2D, null); gl.deleteTexture(tex); }; /** * @param {WebGLRenderingContextBase} gl * @param {Object} expectedFormats Mapping from format names to format enum values. */ let testCompressedFormatsListed = function(gl, expectedFormats) { debug(""); debug("Testing that every format is listed by the compressed texture formats query"); let supportedFormats = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS); let failed; let count = 0; for (let name in expectedFormats) { if (expectedFormats.hasOwnProperty(name)) { ++count; let format = expectedFormats[name]; failed = true; for (let ii = 0; ii < supportedFormats.length; ++ii) { if (format == supportedFormats[ii]) { testPassed("supported format " + name + " exists"); failed = false; break; } } if (failed) { testFailed("supported format " + name + " does not exist"); } } } if (supportedFormats.length != count) { testFailed("Incorrect number of supported formats, was " + supportedFormats.length + " should be " + count); } }; /** * @param {Object} ext Compressed texture extension object. * @param {Object} expectedFormats Mapping from format names to format enum values. */ let testCorrectEnumValuesInExt = function(ext, expectedFormats) { debug(""); debug("Testing that format enum values in the extension object are correct"); for (name in expectedFormats) { if (expectedFormats.hasOwnProperty(name)) { if (isResultCorrect(ext[name], expectedFormats[name])) { testPassed("Enum value for " + name + " matches 0x" + ext[name].toString(16)); } else { testFailed("Enum value for " + name + " mismatch: 0x" + ext[name].toString(16) + " should be 0x" + expectedFormats[name].toString(16)); } } } }; /** * @param {WebGLRenderingContextBase} gl * @param {Object} validFormats Mapping from format names to format enum values. * @param expectedByteLength A function that takes in width, height and format and returns the expected buffer size in bytes. * @param getBlockDimensions A function that takes in a format and returns block size in pixels. */ let testFormatRestrictionsOnBufferSize = function(gl, validFormats, expectedByteLength, getBlockDimensions) { debug(""); debug("Testing format restrictions on texture upload buffer size"); let tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); for (let formatId in validFormats) { if (validFormats.hasOwnProperty(formatId)) { let format = validFormats[formatId]; let blockSize = getBlockDimensions(format); let expectedSize = expectedByteLength(blockSize.width * 4, blockSize.height * 4, format); let data = new Uint8Array(expectedSize); gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, blockSize.width * 3, blockSize.height * 4, 0, data); wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, formatId + " data size does not match dimensions (too small width)"); gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, blockSize.width * 5, blockSize.height * 4, 0, data); wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, formatId + " data size does not match dimensions (too large width)"); gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, blockSize.width * 4, blockSize.height * 3, 0, data); wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, formatId + " data size does not match dimensions (too small height)"); gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, blockSize.width * 4, blockSize.height * 5, 0, data); wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, formatId + " data size does not match dimensions (too large height)"); } } }; /** * @param {WebGLRenderingContextBase} gl * @param {Object} validFormats Mapping from format names to format enum values. * @param expectedByteLength A function that takes in width, height and format and returns the expected buffer size in bytes. * @param getBlockDimensions A function that takes in a format and returns block size in pixels. * @param {number} width Width of the image in pixels. * @param {number} height Height of the image in pixels. * @param {Object} subImageConfigs configs for compressedTexSubImage calls */ let testTexSubImageDimensions = function(gl, ext, validFormats, expectedByteLength, getBlockDimensions, width, height, subImageConfigs) { let tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); for (let formatId in validFormats) { if (validFormats.hasOwnProperty(formatId)) { let format = validFormats[formatId]; let blockSize = getBlockDimensions(format); debug("testing " + ctu.formatToString(ext, format)); let expectedSize = expectedByteLength(width, height, format); let data = new Uint8Array(expectedSize); gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, data); wtu.glErrorShouldBe(gl, gl.NO_ERROR, "setting up compressed texture"); for (let i = 0, len = subImageConfigs.length; i < len; ++i) { let c = subImageConfigs[i]; let subData = new Uint8Array(expectedByteLength(c.width, c.height, format)); gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, c.xoffset, c.yoffset, c.width, c.height, format, subData); wtu.glErrorShouldBe(gl, c.expectation, c.message); } } } gl.bindTexture(gl.TEXTURE_2D, null); gl.deleteTexture(tex); }; let testTexImageLevelDimensions = function(gl, ext, validFormats, expectedByteLength, getBlockDimensions, imageConfigs) { let tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); for (let formatId in validFormats) { if (validFormats.hasOwnProperty(formatId)) { let format = validFormats[formatId]; let blockSize = getBlockDimensions(format); debug("testing " + ctu.formatToString(ext, format)); for (let i = 0, len = imageConfigs.length; i < len; ++i) { let c = imageConfigs[i]; let data = new Uint8Array(expectedByteLength(c.width, c.height, format)); gl.compressedTexImage2D(gl.TEXTURE_2D, c.level, format, c.width, c.height, 0, data); wtu.glErrorShouldBe(gl, c.expectation, c.message); } } } gl.bindTexture(gl.TEXTURE_2D, null); gl.deleteTexture(tex); } let testTexStorageLevelDimensions = function(gl, ext, validFormats, expectedByteLength, getBlockDimensions, imageConfigs) { for (let formatId in validFormats) { let tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); if (validFormats.hasOwnProperty(formatId)) { let format = validFormats[formatId]; let blockSize = getBlockDimensions(format); debug("testing " + ctu.formatToString(ext, format)); for (let i = 0, len = imageConfigs.length; i < len; ++i) { let c = imageConfigs[i]; let data = new Uint8Array(expectedByteLength(c.width, c.height, format)); if (i == 0) { gl.texStorage2D(gl.TEXTURE_2D, imageConfigs.length, format, c.width, c.height); wtu.glErrorShouldBe(gl, c.expectation, c.message); } gl.compressedTexSubImage2D(gl.TEXTURE_2D, i, 0, 0, c.width, c.height, format, data); wtu.glErrorShouldBe(gl, c.expectation, c.message); } } gl.bindTexture(gl.TEXTURE_2D, null); gl.deleteTexture(tex); } } return { formatToString: formatToString, insertCaptionedImg: insertCaptionedImg, makeScaledImage: makeScaledImage, testCompressedFormatsListed: testCompressedFormatsListed, testCompressedFormatsUnavailableWhenExtensionDisabled: testCompressedFormatsUnavailableWhenExtensionDisabled, testCorrectEnumValuesInExt: testCorrectEnumValuesInExt, testFormatRestrictionsOnBufferSize: testFormatRestrictionsOnBufferSize, testTexSubImageDimensions: testTexSubImageDimensions, testTexImageLevelDimensions: testTexImageLevelDimensions, testTexStorageLevelDimensions: testTexStorageLevelDimensions, }; })();