| <!-- |
| 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. |
| --> |
| |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="utf-8"> |
| <title>OES_texture_float/WEBGL_color_buffer_float</title> |
| <link rel="stylesheet" href="../../resources/js-test-style.css"/> |
| <script src="../../js/js-test-pre.js"></script> |
| <script src="../../js/webgl-test-utils.js"></script> |
| <script src="../../js/tests/ext-float-blend.js"></script> |
| </head> |
| <body> |
| <div id="description"></div> |
| <canvas id="canvas" style="width: 50px; height: 50px;"> </canvas> |
| <div id="console"></div> |
| <!-- Shaders for testing floating-point textures --> |
| <script id="testFragmentShader" type="x-shader/x-fragment"> |
| 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) < 8.0) { |
| gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); |
| } else { |
| gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); |
| } |
| } |
| </script> |
| <!-- Shaders for testing floating-point render targets --> |
| <script id="positionVertexShader" type="x-shader/x-vertex"> |
| attribute vec4 vPosition; |
| void main() |
| { |
| gl_Position = vPosition; |
| } |
| </script> |
| <script id="floatingPointFragmentShader" type="x-shader/x-fragment"> |
| void main() |
| { |
| gl_FragColor = vec4(10000.0, 10000.0, 10000.0, 10000.0); |
| } |
| </script> |
| <script> |
| "use strict"; |
| description("This test verifies the functionality of the OES_texture_float and WEBGL_color_buffer_float extensions, if available."); |
| |
| debug(""); |
| |
| var wtu = WebGLTestUtils; |
| var canvas = document.getElementById("canvas"); |
| var gl = wtu.create3DContext(canvas); |
| |
| if (!gl) { |
| testFailed("WebGL context does not exist"); |
| } else { |
| testPassed("WebGL context exists"); |
| |
| var texturedShaders = [ |
| wtu.simpleTextureVertexShader, |
| "testFragmentShader" |
| ]; |
| var testProgram = |
| wtu.setupProgram(gl, |
| texturedShaders, |
| ['vPosition', 'texCoord0'], |
| [0, 1]); |
| var quadParameters = wtu.setupUnitQuad(gl, 0, 1); |
| |
| // First verify that allocation of floating-point textures fails if |
| // the extension has not been enabled yet. |
| runTextureCreationTest(testProgram, false); |
| |
| if (!gl.getExtension("OES_texture_float")) { |
| testPassed("No OES_texture_float support -- this is legal"); |
| } else { |
| testPassed("Successfully enabled OES_texture_float extension"); |
| // If alpha value is missing from a texture it gets filled to 1 when sampling according to GLES2.0 table 3.12 |
| runTextureCreationTest(testProgram, true, gl.RGBA, 4, [10000, 10000, 10000, 10000]); |
| runTextureCreationTest(testProgram, true, gl.RGB, 3, [10000, 10000, 10000, 1]); |
| runTextureCreationTest(testProgram, true, gl.LUMINANCE, 1, [10000, 10000, 10000, 1]); |
| runTextureCreationTest(testProgram, true, gl.ALPHA, 1, [0, 0, 0, 10000]); |
| runTextureCreationTest(testProgram, true, gl.LUMINANCE_ALPHA, 2, [10000, 10000, 10000, 10000]); |
| |
| (function() { |
| debug(""); |
| var renderable = isRenderable(gl); |
| var renderableExtName = "WEBGL_color_buffer_float"; |
| var supported = gl.getSupportedExtensions().includes(renderableExtName); |
| if (renderable && !supported) { |
| testFailed("RGBA/FLOAT is color renderable but " + renderableExtName + " not exposed"); |
| } else if (supported && !renderable) { |
| testFailed(renderableExtName + " is exposed but RGBA/FLOAT is not color renderable"); |
| } |
| if (supported) { |
| runRenderTargetAndReadbackTest(testProgram, gl.RGBA, 4, [10000, 10000, 10000, 10000], 0, true); |
| runRenderTargetAndReadbackTest(testProgram, gl.RGB, 3, [10000, 10000, 10000, 1], 0, false); |
| runRenderTargetAndReadbackTest(testProgram, gl.RGBA, 4, [10000, 10000, 10000, 10000], 1, true); |
| runRenderTargetAndReadbackTest(testProgram, gl.RGBA, 4, [10000, 10000, 10000, 10000], 0.5, true); |
| runFramebufferTest(); |
| |
| debug(""); |
| debug("Test float32 blending without EXT_float_blend."); |
| testExtFloatBlend(gl.RGBA); |
| |
| debug(""); |
| debug("Testing that float32 blending succeeds with EXT_float_blend."); |
| if (!gl.getExtension("EXT_float_blend")) { |
| testPassed("No EXT_float_blend support -- this is legal"); |
| return; |
| } |
| testExtFloatBlend(gl.RGBA); |
| } |
| })(); |
| |
| runUniqueObjectTest(); |
| } |
| } |
| |
| function isRenderable(gl) { |
| var tex = gl.createTexture(); |
| gl.bindTexture(gl.TEXTURE_2D, tex); |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.FLOAT, null); |
| |
| var fb = gl.createFramebuffer(); |
| gl.bindFramebuffer(gl.FRAMEBUFFER, fb); |
| gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0); |
| |
| var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); |
| gl.deleteFramebuffer(fb); |
| gl.deleteTexture(tex); |
| |
| return status == gl.FRAMEBUFFER_COMPLETE; |
| } |
| |
| 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 runTextureCreationTest(testProgram, extensionEnabled, opt_format, opt_numChannels, opt_subtractor) |
| { |
| var format = opt_format || gl.RGBA; |
| var numberOfChannels = opt_numChannels || 4; |
| var expectFailure = !extensionEnabled; |
| var subtractor = opt_subtractor || [10000, 10000, 10000, 10000]; |
| |
| debug(""); |
| debug("testing format: " + wtu.glEnumToString(gl, format) + |
| " expect:" + (extensionEnabled ? "success" : "failure")); |
| |
| var texture = allocateTexture(); |
| // Generate data. |
| var width = 2; |
| var height = 2; |
| var data = new Float32Array(width * height * numberOfChannels); |
| for (var ii = 0; ii < data.length; ++ii) { |
| data[ii] = 10000; |
| } |
| gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, gl.FLOAT, data); |
| if (expectFailure) { |
| wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "floating-point texture allocation must be disallowed if OES_texture_float isn't enabled"); |
| return; |
| } else { |
| wtu.glErrorShouldBe(gl, gl.NO_ERROR, "floating-point texture allocation should succeed if OES_texture_float is enabled"); |
| } |
| // Verify that the texture actually works for sampling and contains the expected data. |
| gl.uniform4fv(gl.getUniformLocation(testProgram, "subtractor"), subtractor); |
| wtu.clearAndDrawUnitQuad(gl); |
| checkRenderingResults(); |
| |
| // Check that linear fails. |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); |
| wtu.clearAndDrawUnitQuad(gl); |
| wtu.checkCanvas(gl, [255, 0, 0, 255], "should be red"); |
| } |
| |
| 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 runRenderTargetAndReadbackTest(testProgram, format, numberOfChannels, subtractor, texSubImageCover, requireRenderable) |
| { |
| var formatString = wtu.glEnumToString(gl, format); |
| debug(""); |
| debug("testing floating-point " + formatString + " render target" + (texSubImageCover > 0 ? " after calling texSubImage" : "")); |
| |
| var texture = allocateTexture(); |
| var width = 2; |
| var height = 2; |
| gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, gl.FLOAT, null); |
| wtu.glErrorShouldBe(gl, gl.NO_ERROR, "floating-point texture allocation should succeed if OES_texture_float is enabled"); |
| |
| // 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); |
| // It is legal for a WebGL implementation exposing the OES_texture_float extension to |
| // support floating-point textures but not as attachments to framebuffer objects. |
| if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { |
| if (requireRenderable) |
| testFailed("floating-point " + formatString + " render target not supported"); |
| else |
| debug("floating-point " + formatString + " render target not supported -- this is legal"); |
| 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, gl.FLOAT, data); |
| wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texSubImage2D should succeed if OES_texture_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, |
| ["positionVertexShader", "floatingPointFragmentShader"], |
| ['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(); |
| |
| // Finally, if the implementation supports floating-point readback, verify it. |
| gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); |
| var implFormat = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT); |
| var implType = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE); |
| wtu.glErrorShouldBe(gl, gl.NO_ERROR, "getParameter of IMPLEMENTATION_COLOR_READ_{FORMAT|TYPE} should succeed"); |
| if ((implFormat == gl.RGBA || implFormat == gl.RGB) && implType == gl.FLOAT) { |
| debug("Checking readback of floating-point values"); |
| var arraySize = (implFormat == gl.RGBA) ? 4 : 3 |
| var buf = new Float32Array(arraySize); |
| gl.readPixels(0, 0, 1, 1, implFormat, implType , buf); |
| wtu.glErrorShouldBe(gl, gl.NO_ERROR, "readPixels from floating-point renderbuffer 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 renderbuffer succeeded"); |
| } else { |
| testFailed("readPixels of float-type data from floating-point renderbuffer failed: expected " |
| + arrayToString(subtractor, arraySize) + ", got " + arrayToString(buf)); |
| } |
| } |
| } |
| |
| function runUniqueObjectTest() |
| { |
| debug(""); |
| debug("Testing that getExtension() returns the same object each time"); |
| gl.getExtension("OES_texture_float").myProperty = 2; |
| webglHarnessCollectGarbage(); |
| shouldBe('gl.getExtension("OES_texture_float").myProperty', '2'); |
| } |
| |
| // Make sure we can call readPixels with the passed in arrayBufferConstructor and that the color |
| // channels are the ones we expect. If there is a mismatch between the glType and arrayBuffer type, |
| // fail the test. |
| function verifyReadPixelsColors(red, green, blue, alpha, alphaRGB, glFormat, glType, arrayBufferConstructor) { |
| var typeName = wtu.glEnumToString(gl, glType); |
| |
| debug(wtu.glEnumToString(gl, glFormat) + " framebuffer with " + typeName + " readback."); |
| |
| var arrayBuffer = new arrayBufferConstructor(4); |
| gl.readPixels(0, 0, 1, 1, gl.RGBA, glType, arrayBuffer); |
| wtu.glErrorShouldBe(gl, gl.NO_ERROR, "readPixels should return NO_ERROR when reading " + typeName + " data."); |
| |
| assertMsg(arrayBuffer[0] === red, "Red channel should be " + red + " for " + typeName + " readPixels. Received: " + arrayBuffer[0]); |
| assertMsg(arrayBuffer[1] === green, "Green channel should be " + green + " for " + typeName + " readPixels. Received: " + arrayBuffer[1]); |
| assertMsg(arrayBuffer[2] === blue, "Blue channel should be " + blue + " for " + typeName + " readPixels. Received: " + arrayBuffer[2]); |
| if (glFormat === gl.RGBA) { |
| assertMsg(arrayBuffer[3] === alpha, "Alpha channel should be " + alpha + " for " + typeName + " readPixels. Received: " + arrayBuffer[3]); |
| } else if (glFormat === gl.RGB) { |
| assertMsg(arrayBuffer[3] === alphaRGB, "Alpha channel should be " + alphaRGB + " for " + typeName + " readPixels. Received: " + arrayBuffer[3]); |
| } |
| |
| // Make sure any arrayBuffer types that are not equal to arrayBufferConstructor fail readPixels. |
| if (arrayBufferConstructor !== Uint8Array) { |
| arrayBuffer = new Uint8Array(4); |
| gl.readPixels(0, 0, 1, 1, gl.RGBA, glType, arrayBuffer); |
| wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "readPixels should return INVALID_OPERATION when reading mismatched types. " + Uint8Array.toString()); |
| } |
| if (arrayBufferConstructor !== Float32Array) { |
| arrayBuffer = new Float32Array(4); |
| gl.readPixels(0, 0, 1, 1, gl.RGBA, glType, arrayBuffer); |
| wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "readPixels should return INVALID_OPERATION when reading mismatched types. " + Float32Array.toString()); |
| } |
| if (arrayBufferConstructor !== Uint16Array) { |
| arrayBuffer = new Uint16Array(4); |
| gl.readPixels(0, 0, 1, 1, gl.RGBA, glType, arrayBuffer); |
| wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "readPixels should return INVALID_OPERATION when reading mismatched types. " + Uint16Array.toString()); |
| } |
| } |
| |
| // Verify that float textures attached to frame buffers function correctly with regard to framebuffer |
| // completness, IMPLEMENTATION_COLOR_READ_FORMAT/TYPE and readPixels |
| function runFramebufferTest() { |
| debug(""); |
| debug("Framebuffer Tests"); |
| |
| var texture = allocateTexture(); |
| gl.bindTexture(gl.TEXTURE_2D, texture); |
| |
| var fbo = gl.createFramebuffer(); |
| gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); |
| gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); |
| |
| debug("Ensure non-color-renderable formats [LUMINANCE, LUMINANCE_ALPHA, ALPHA] fail."); |
| var arrayBufferFloatOutput = new Float32Array(4); // 4 color channels |
| [gl.LUMINANCE, gl.LUMINANCE_ALPHA, gl.ALPHA].forEach(function(badFormat) { |
| debug(wtu.glEnumToString(gl, badFormat) + " framebuffer"); |
| |
| gl.texImage2D(gl.TEXTURE_2D, 0, badFormat, 1, 1, 0, badFormat, gl.FLOAT, null); |
| shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); |
| |
| shouldBeNull("gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT)"); |
| wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "IMPLEMENTATION_COLOR_READ_FORMAT should fail for incomplete framebuffers."); |
| |
| shouldBeNull("gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE)"); |
| wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "IMPLEMENTATION_COLOR_READ_TYPE should fail for incomplete framebuffers."); |
| |
| gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, arrayBufferFloatOutput); |
| wtu.glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION , "readPixels should fail on incomplete framebuffers."); |
| debug(""); |
| }); |
| |
| debug("Ensure color renderable formats [RGBA, RGB] succeed."); |
| var arrayBufferFloatInput = new Float32Array(4); // 4 color channels |
| arrayBufferFloatInput[0] = 0; |
| arrayBufferFloatInput[1] = .25; |
| arrayBufferFloatInput[2] = .50; |
| arrayBufferFloatInput[3] = .75; |
| |
| [gl.RGBA, gl.RGB].forEach(function(goodFormat) { |
| debug(""); |
| debug(wtu.glEnumToString(gl, goodFormat) + " framebuffer tests"); |
| |
| gl.texImage2D(gl.TEXTURE_2D, 0, goodFormat, 1, 1, 0, goodFormat, gl.FLOAT, arrayBufferFloatInput); |
| if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { |
| testPassed("Format is not renderable. This is allowed."); |
| return; |
| } |
| verifyReadPixelsColors( |
| 0.00, // red |
| 0.25, // green |
| 0.50, // blue |
| 0.75, // alpha |
| 1.0, // alphaRGB |
| goodFormat, |
| gl.FLOAT, |
| Float32Array); |
| |
| var implementationColorReadFormat = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT); |
| assertMsg(implementationColorReadFormat === gl.RGBA || implementationColorReadFormat === gl.RGB, |
| "IMPLEMENTATION_COLOR_READ_FORMAT should be color renderable: RGBA or RGB. Received: " + wtu.glEnumToString(gl, implementationColorReadFormat)); |
| |
| var implementationColorReadType = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE); |
| assertMsg(implementationColorReadType === gl.UNSIGNED_BYTE || |
| implementationColorReadType === gl.FLOAT || |
| "IMPLEMENTATION_COLOR_READ_TYPE must be one of UNSIGNED_BYTE or FLOAT " + |
| "Received: " + wtu.glEnumToString(gl, implementationColorReadType)); |
| }); |
| } |
| |
| debug(""); |
| var successfullyParsed = true; |
| </script> |
| <script src="../../js/js-test-post.js"></script> |
| |
| </body> |
| </html> |