"use strict"; function allocateTexture() { var texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texture parameter setup should succeed"); return texture; } function checkRenderingResults() { wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green"); } function arrayToString(arr, size) { var mySize; if (!size) mySize = arr.length; else mySize = size; var out = "["; for (var ii = 0; ii < mySize; ++ii) { if (ii > 0) { out += ", "; } out += arr[ii]; } return out + "]"; } function runReadbackTest(testProgram, subtractor) { // Verify floating point readback debug("Checking readback of floating-point values"); var buf = new Float32Array(4); gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, buf); wtu.glErrorShouldBe(gl, gl.NO_ERROR, "readPixels from floating-point framebuffer should succeed"); var ok = true; var tolerance = 8.0; // TODO: factor this out from both this test and the subtractor shader above. for (var ii = 0; ii < buf.length; ++ii) { if (Math.abs(buf[ii] - subtractor[ii]) > tolerance) { ok = false; break; } } if (ok) { testPassed("readPixels of float-type data from floating-point framebuffer succeeded"); } else { testFailed("readPixels of float-type data from floating-point framebuffer failed: expected " + arrayToString(subtractor, 4) + ", got " + arrayToString(buf)); } } function runFloatTextureRenderTargetTest(enabled, internalFormatString, format, type, testProgram, numberOfChannels, subtractor, texSubImageCover) { let internalFormat = eval(internalFormatString); debug(""); debug("testing floating-point " + internalFormatString + " texture render target" + (texSubImageCover > 0 ? " after calling texSubImage" : "")); var texture = allocateTexture(); var width = 2; var height = 2; gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, null); wtu.glErrorShouldBe(gl, gl.NO_ERROR, "floating-point texture allocation should succeed"); // Try to use this texture as a render target. var fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); gl.bindTexture(gl.TEXTURE_2D, null); var completeStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER); if (!enabled) { if (completeStatus == gl.FRAMEBUFFER_COMPLETE && !enabled) testFailed("floating-point " + internalFormatString + " render target should not be supported"); else testPassed("floating-point " + internalFormatString + " render target should not be supported"); return; } if (completeStatus != gl.FRAMEBUFFER_COMPLETE) { if (version == 1 && format == gl.RGB) testPassed("floating-point " + internalFormatString + " render target not supported; this is allowed.") else testFailed("floating-point " + internalFormatString + " render target not supported"); return; } if (texSubImageCover > 0) { // Ensure that replacing the whole texture or a part of it with texSubImage2D doesn't affect renderability gl.bindTexture(gl.TEXTURE_2D, texture); var data = new Float32Array(width * height * numberOfChannels * texSubImageCover); gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height * texSubImageCover, format, type, data); wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texSubImage2D should succeed if EXT_color_buffer_half_float is enabled"); gl.bindTexture(gl.TEXTURE_2D, null); if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { testFailed("render target support changed after calling texSubImage2D"); return; } } var renderProgram = wtu.setupProgram(gl, [wtu.simpleVertexShader, `void main() { gl_FragColor = vec4(1000.0, 1000.0, 1000.0, 1000.0); }`], ['vPosition'], [0]); wtu.clearAndDrawUnitQuad(gl); wtu.glErrorShouldBe(gl, gl.NO_ERROR, "rendering to floating-point texture should succeed"); // Now sample from the floating-point texture and verify we got the correct values. gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.bindTexture(gl.TEXTURE_2D, texture); gl.useProgram(testProgram); gl.uniform1i(gl.getUniformLocation(testProgram, "tex"), 0); gl.uniform4fv(gl.getUniformLocation(testProgram, "subtractor"), subtractor); wtu.clearAndDrawUnitQuad(gl); wtu.glErrorShouldBe(gl, gl.NO_ERROR, "rendering from floating-point texture should succeed"); checkRenderingResults(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); runReadbackTest(testProgram, subtractor); } function runFloatRenderbufferRenderTargetTest(enabled, internalFormatString, testProgram, numberOfChannels, subtractor) { var internalFormat = eval(internalFormatString); var samples = [0]; if (enabled && version > 1) { samples = Array.prototype.slice.call(gl.getInternalformatParameter(gl.RENDERBUFFER, internalFormat, gl.SAMPLES)); samples.push(0); } for (var ndx = 0; ndx < samples.length; ++ndx) { debug(""); debug("testing floating-point " + internalFormatString + " renderbuffer render target with number of samples " + samples[ndx]); var colorbuffer = gl.createRenderbuffer(); var width = 2; var height = 2; gl.bindRenderbuffer(gl.RENDERBUFFER, colorbuffer); if (samples[ndx] == 0) gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, width, height); else gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples[ndx], internalFormat, width, height); if (!enabled) { wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "floating-point renderbuffer allocation should fail if EXT_color_buffer_half_float is not enabled or this is a 32 bit format"); return; } else { wtu.glErrorShouldBe(gl, gl.NO_ERROR, "floating-point renderbuffer allocation should succeed if EXT_color_buffer_half_float is enabled"); } // Try to use this renderbuffer as a render target. var fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorbuffer); var completeStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER); if (completeStatus != gl.FRAMEBUFFER_COMPLETE) { testFailed("floating-point " + internalFormatString + " render target not supported"); return; } var resolveColorRbo = null; var resolveFbo = null; if (samples[ndx] > 0) { resolveColorRbo = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, resolveColorRbo); gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, width, height); resolveFbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, resolveFbo); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, resolveColorRbo); completeStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER); if (completeStatus != gl.FRAMEBUFFER_COMPLETE) { testFailed("Failed to create resolve framebuffer"); return; } } gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); gl.clearColor(1000.0, 1000.0, 1000.0, 1000.0); gl.clear(gl.COLOR_BUFFER_BIT); if (samples[ndx] > 0) { gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, resolveFbo); gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST); gl.bindFramebuffer(gl.READ_FRAMEBUFFER, resolveFbo); } runReadbackTest(testProgram, subtractor); } } function runRGB16FNegativeTest() { debug(""); debug("testing RGB16F isn't color renderable"); var texture = allocateTexture(); var width = 2; var height = 2; gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB16F, width, height, 0, gl.RGB, gl.FLOAT, null); wtu.glErrorShouldBe(gl, gl.NO_ERROR, "RGB16F texture allocation should succeed"); // Try to use this texture as a render target. var fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); gl.bindTexture(gl.TEXTURE_2D, null); var completeStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER); if (completeStatus == gl.FRAMEBUFFER_COMPLETE) testFailed("RGB16F render target should not be supported with or without enabling EXT_color_buffer_half_float"); else testPassed("RGB16F render target should not be supported with or without enabling EXT_color_buffer_half_float"); gl.deleteTexture(texture); var colorbuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, colorbuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGB16F, width, height); wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "RGB16F renderbuffer allocation should fail with or without enabling EXT_color_buffer_half_float"); gl.bindRenderbuffer(gl.RENDERBUFFER, null); gl.deleteRenderbuffer(colorbuffer); gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.deleteFramebuffer(fbo); } function runUniqueObjectTest() { debug(""); debug("Testing that getExtension() returns the same object each time"); gl.getExtension("EXT_color_buffer_half_float").myProperty = 2; webglHarnessCollectGarbage(); shouldBe('gl.getExtension("EXT_color_buffer_half_float").myProperty', '2'); } function runInternalFormatQueryTest() { debug(""); debug("testing the internal format query"); var maxSamples = gl.getParameter(gl.MAX_SAMPLES); const formats = [gl.RGBA16F, gl.R16F, gl.RG16F]; var firstMultiOnlyFormat = 4; for (var fmt = 0; fmt < formats.length; ++fmt) { var samples = gl.getInternalformatParameter(gl.RENDERBUFFER, formats[fmt], gl.SAMPLES); if (fmt >= firstMultiOnlyFormat && (samples.length == 0 || samples[0] < maxSamples)) { testFailed("the maximum value in SAMPLES should be at least " + maxSamples); return; } var prevSampleCount = 0; var sampleCount; for (var ndx = 0; ndx < samples.length; ++ndx, prevSampleCount = sampleCount) { sampleCount = samples[ndx]; // sample count must be > 0 if (sampleCount <= 0) { testFailed("Expected sample count to be at least one; got " + sampleCount); return; } // samples must be ordered descending if (ndx > 0 && sampleCount >= prevSampleCount) { testFailed("Expected sample count to be ordered in descending order; got " + prevSampleCount + " at index " + (ndx - 1) + ", and " + sampleCount + " at index " + ndx); return; } } } testPassed("Internal format query succeeded"); } function runCopyTexImageTest(enabled) { var width = 16; var height = 16; var level = 0; var cases = [ { internalformat: "RGBA16F", format: "RGBA", destFormat: "R16F", valid: true, renderable: true, }, { internalformat: "RGBA16F", format: "RGBA", destFormat: "RG16F", valid: true, renderable: true, }, { internalformat: "RGBA16F", format: "RGBA", destFormat: "RGB16F", valid: true, renderable: true, }, { internalformat: "RGBA16F", format: "RGBA", destFormat: "RGBA16F", valid: true, renderable: true, }, { internalformat: "RGB16F", format: "RGB", destFormat: "R16F", valid: true, renderable: false, }, { internalformat: "RGB16F", format: "RGB", destFormat: "RG16F", valid: true, renderable: false, }, { internalformat: "RGB16F", format: "RGB", destFormat: "RGB16F", valid: true, renderable: false, }, { internalformat: "RGB16F", format: "RGB", destFormat: "RGBA16F", valid: false, renderable: false, }, { internalformat: "RG16F", format: "RG", destFormat: "R16F", valid: true, renderable: true, }, { internalformat: "RG16F", format: "RG", destFormat: "RG16F", valid: true, renderable: true, }, { internalformat: "RG16F", format: "RG", destFormat: "RGB16F", valid: false, renderable: true, }, { internalformat: "RG16F", format: "RG", destFormat: "RGBA16F", valid: false, renderable: true, }, { internalformat: "R16F", format: "RED", destFormat: "R16F", valid: true, renderable: true, }, { internalformat: "R16F", format: "RED", destFormat: "RG16F", valid: false, renderable: true, }, { internalformat: "R16F", format: "RED", destFormat: "RGB16F", valid: false, renderable: true, }, { internalformat: "R16F", format: "RED", destFormat: "RGBA16F", valid: false, renderable: true, }, ]; if (version == 1) { cases = [ { valid: true, renderable: true, format: "RGBA", destFormat: "LUMINANCE", }, { valid: true, renderable: true, format: "RGBA", destFormat: "ALPHA", }, { valid: true, renderable: true, format: "RGBA", destFormat: "LUMINANCE_ALPHA", }, { valid: true, renderable: true, format: "RGBA", destFormat: "RGB", }, { valid: true, renderable: true, format: "RGBA", destFormat: "RGBA", }, { valid: true, renderable: true, format: "RGB", destFormat: "LUMINANCE", }, { valid: false, renderable: true, format: "RGB", destFormat: "ALPHA", }, { valid: false, renderable: true, format: "RGB", destFormat: "LUMINANCE_ALPHA", }, { valid: true, renderable: true, format: "RGB", destFormat: "RGB", }, { valid: false, renderable: true, format: "RGB", destFormat: "RGBA", }, { valid: true, renderable: false, format: "ALPHA", destFormat: "ALPHA", }, { valid: true, renderable: false, format: "LUMINANCE", destFormat: "LUMINANCE", }, { valid: true, renderable: false, format: "LUMINANCE_ALPHA", destFormat: "LUMINANCE_ALPHA", }, ]; } cases.forEach(function(testcase) { debug(""); debug(`Testing CopyTexImage2D for format: ${testcase.format}, internalformat: ${testcase.internalformat}, destformat: ${testcase.destFormat}`); var format = gl[testcase.format]; var internalformat = version > 1 ? gl[testcase.internalformat] : format; var type = version > 1 ? gl.HALF_FLOAT : 0x8D61 /* HALF_FLOAT_OES */; var destFormat = gl[testcase.destFormat]; var texSrc = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texSrc); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); var data = new Uint16Array(width * height * 4); gl.texImage2D(gl.TEXTURE_2D, level, internalformat, width, height, 0, format, type, data); var fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texSrc, level); var texDest = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texDest); wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setup framebuffer with texture should succeed."); if (enabled && testcase.renderable) { if (version == 1 && format == gl.RGB && gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT) { testPassed("RGB framebuffer attachment not supported. This is allowed.") } else { shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE"); gl.copyTexImage2D(gl.TEXTURE_2D, level, destFormat, 0, 0, width, height, 0); wtu.glErrorShouldBe(gl, testcase.valid ? gl.NO_ERROR : [gl.INVALID_ENUM, gl.INVALID_OPERATION], "CopyTexImage2D"); } } else { shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); gl.copyTexImage2D(gl.TEXTURE_2D, level, destFormat, 0, 0, width, height, 0); wtu.glErrorShouldBe(gl, [gl.INVALID_ENUM, gl.INVALID_FRAMEBUFFER_OPERATION], "CopyTexImage2D should fail."); } gl.deleteTexture(texDest); gl.deleteTexture(texSrc); gl.deleteFramebuffer(fbo); }); } description("This test verifies the functionality of the EXT_color_buffer_half_float extension, if it is available."); debug(""); var wtu = WebGLTestUtils; var canvas = document.getElementById("canvas"); var gl = wtu.create3DContext(canvas, null, version); if (version < 2) { // These are exposed on the extension, but we need them before the extension has been requested so we can // make sure they don't work. gl.R16F = 0x822D; gl.RG16F = 0x822F; gl.RGB16F = 0x881B; gl.RGBA16F = 0x881A; } if (!gl) { testFailed("WebGL context does not exist"); } else { testPassed("WebGL context exists"); var texturedShaders = [ wtu.simpleTextureVertexShader, `precision mediump float; uniform sampler2D tex; uniform vec4 subtractor; varying vec2 texCoord; void main() { vec4 color = texture2D(tex, texCoord); if (abs(color.r - subtractor.r) + abs(color.g - subtractor.g) + abs(color.b - subtractor.b) + abs(color.a - subtractor.a) < 16.0) { gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); } else { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } }`, ]; var testProgram = wtu.setupProgram(gl, texturedShaders, ['vPosition', 'texCoord0'], [0, 1]); var quadParameters = wtu.setupUnitQuad(gl, 0, 1); if (version > 1) { // Ensure these formats can't be used for rendering if the extension is disabled runFloatTextureRenderTargetTest(false, "gl.R16F", gl.RED, gl.FLOAT); runFloatTextureRenderTargetTest(false, "gl.RG16F", gl.RG, gl.FLOAT); runFloatTextureRenderTargetTest(false, "gl.RGBA16F", gl.RGBA, gl.FLOAT); } runFloatRenderbufferRenderTargetTest(false, "gl.R16F"); runFloatRenderbufferRenderTargetTest(false, "gl.RG16F"); runFloatRenderbufferRenderTargetTest(false, "gl.RGBA16F"); runFloatRenderbufferRenderTargetTest(false, "gl.R32F"); runFloatRenderbufferRenderTargetTest(false, "gl.RG32F"); runFloatRenderbufferRenderTargetTest(false, "gl.RGBA32F"); runFloatRenderbufferRenderTargetTest(false, "gl.R11F_G11F_B10F"); if (version > 1) { runCopyTexImageTest(false); // Ensure RGB16F can't be used for rendering. runRGB16FNegativeTest(); } let oesTextureHalfFloat = null; if (version == 1) { // oesTextureHalfFloat implicitly enables EXT_color_buffer_half_float if supported oesTextureHalfFloat = gl.getExtension("OES_texture_half_float"); if (oesTextureHalfFloat && gl.getSupportedExtensions().includes("EXT_color_buffer_half_float")) { runFloatTextureRenderTargetTest(true, "gl.RGBA", gl.RGBA, oesTextureHalfFloat.HALF_FLOAT_OES, testProgram, 4, [1000, 1000, 1000, 1000], 0); runFloatTextureRenderTargetTest(true, "gl.RGB", gl.RGB, oesTextureHalfFloat.HALF_FLOAT_OES, testProgram, 3, [1000, 1000, 1000, 1], 0); } } var ext = null; if (!(ext = gl.getExtension("EXT_color_buffer_half_float"))) { testPassed("No EXT_color_buffer_half_float support -- this is legal"); } else { testPassed("Successfully enabled EXT_color_buffer_half_float extension"); shouldBe("ext.RGB16F_EXT", "gl.RGB16F"); shouldBe("ext.RGBA16F_EXT", "gl.RGBA16F"); shouldBe("ext.FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT", "0x8211"); shouldBe("ext.UNSIGNED_NORMALIZED_EXT", "0x8C17"); if (version > 1) { runInternalFormatQueryTest(); runFloatTextureRenderTargetTest(true, "gl.R16F", gl.RED, gl.FLOAT, testProgram, 1, [1000, 1, 1, 1], 0); runFloatTextureRenderTargetTest(true, "gl.RG16F", gl.RG, gl.FLOAT, testProgram, 2, [1000, 1000, 1, 1], 0); runFloatTextureRenderTargetTest(true, "gl.RGBA16F", gl.RGBA, gl.FLOAT, testProgram, 4, [1000, 1000, 1000, 1000], 0); runFloatRenderbufferRenderTargetTest(true, "gl.R16F", testProgram, 1, [1000, 1, 1, 1]); runFloatRenderbufferRenderTargetTest(true, "gl.RG16F", testProgram, 2, [1000, 1000, 1, 1]); runFloatRenderbufferRenderTargetTest(true, "gl.RGBA16F", testProgram, 4, [1000, 1000, 1000, 1000]); } if (version == 1) { shouldBeNonNull(oesTextureHalfFloat); // Required by spec runFloatTextureRenderTargetTest(true, "gl.RGBA", gl.RGBA, oesTextureHalfFloat.HALF_FLOAT_OES, testProgram, 4, [1000, 1000, 1000, 1000], 0); runFloatTextureRenderTargetTest(true, "gl.RGB", gl.RGB, oesTextureHalfFloat.HALF_FLOAT_OES, testProgram, 3, [1000, 1000, 1000, 1], 0); runFloatTextureRenderTargetTest(false, "gl.LUMINANCE_ALPHA", gl.LUMINANCE_ALPHA, oesTextureHalfFloat.HALF_FLOAT_OES, testProgram, 2, [1000, 1000, 1000, 1000], 0); runFloatTextureRenderTargetTest(false, "gl.LUMINANCE", gl.LUMINANCE, oesTextureHalfFloat.HALF_FLOAT_OES, testProgram, 1, [1000, 1, 1, 1], 0); } if (version > 1) runRGB16FNegativeTest(); // Ensure EXT_color_buffer_half_float does not enable RGB16F as color renderable. runCopyTexImageTest(true); runUniqueObjectTest(); } } debug(""); var successfullyParsed = true;