oes-texture-half-float.html (20406B)
1 <!-- 2 Copyright (c) 2019 The Khronos Group Inc. 3 Use of this source code is governed by an MIT-style license that can be 4 found in the LICENSE.txt file. 5 --> 6 7 <!DOCTYPE html> 8 <html> 9 <head> 10 <meta charset="utf-8"> 11 <title>WebGL OES_texture_half_float Conformance Tests</title> 12 <link rel="stylesheet" href="../../resources/js-test-style.css"/> 13 <script src="../../js/js-test-pre.js"></script> 14 <script src="../../js/webgl-test-utils.js"></script> 15 </head> 16 <body> 17 <div id="description"></div> 18 <canvas id="canvas" style="width: 50px; height: 50px;"> </canvas> 19 <canvas id="canvas2d" style="width: 50px; height: 50px;"> </canvas> 20 <div id="console"></div> 21 <script id="testFragmentShader" type="x-shader/x-fragment"> 22 precision mediump float; 23 uniform sampler2D tex; 24 uniform vec4 subtractor; 25 varying vec2 texCoord; 26 void main() 27 { 28 vec4 color = texture2D(tex, texCoord); 29 if (abs(color.r - subtractor.r) + 30 abs(color.g - subtractor.g) + 31 abs(color.b - subtractor.b) + 32 abs(color.a - subtractor.a) < 8.0) { 33 gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); 34 } else { 35 gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); 36 } 37 } 38 </script> 39 <!-- Shaders for testing half-floating-point render targets --> 40 <script id="floatingPointFragmentShader" type="x-shader/x-fragment"> 41 void main() 42 { 43 gl_FragColor = vec4(10000.0, 10000.0, 10000.0, 10000.0); 44 } 45 </script> 46 <script> 47 "use strict" 48 description("This test verifies the functionality of OES_texture_half_float with null/non-null ArrayBufferView"); 49 50 debug(""); 51 var wtu = WebGLTestUtils; 52 var canvas = document.getElementById("canvas"); 53 var colorCanvas = document.getElementById("canvas2d"); 54 colorCanvas.width = 2; 55 colorCanvas.height = 2; 56 var ctx = colorCanvas.getContext("2d"); 57 ctx.fillStyle = "rgb(255,0,0)"; 58 ctx.fillRect(0, 0, 2, 2); 59 var gl = wtu.create3DContext(canvas); 60 // This constant must be defined in order to run the texture creation test without the extension enabled. 61 var halfFloatOESEnum = 0x8D61; 62 var ext = null; 63 64 65 if (!gl) { 66 testFailed("WebGL context does not exists"); 67 } else { 68 testPassed("WebGL context exists"); 69 70 // Verify that allocation of texture fails if extension is not enabled 71 runTextureCreationTest(false); 72 ext = gl.getExtension("OES_texture_half_float") 73 if (!ext) { 74 testPassed("No OES_texture_half_float support. This is legal"); 75 } else { 76 testPassed("Successfully enabled OES_texture_half_float extension"); 77 78 var program = wtu.setupTexturedQuad(gl); 79 80 // Check if creation of texture succeed's with various formats and null ArrayBufferView 81 var formats = [ 82 { format: gl.RGBA, expected: [255, 0, 0, 255], }, 83 { format: gl.RGB, expected: [255, 0, 0, 255], }, 84 { format: gl.LUMINANCE, expected: [255, 255, 255, 255], }, 85 { format: gl.ALPHA, expected: [ 0, 0, 0, 255], }, 86 { format: gl.LUMINANCE_ALPHA, expected: [255, 255, 255, 255], }, 87 ]; 88 formats.forEach(function(f) { 89 runTextureCreationTest(true, f.format, null, f.expected); 90 }); 91 92 // Texture creation should fail when passed with non-null, non-Uint16 ArrayBufferView 93 formats.forEach(function(f) { 94 var width = 2; 95 var height = 2; 96 var format = f.format; 97 98 // Float32Array 99 var float32Data = new Float32Array(width * height * getNumberOfChannels(format)); 100 for (var ii = 0; ii < float32Data.length; ii++) { 101 float32Data[ii] = 1000; 102 } 103 runTextureCreationTest(true, format, float32Data, null); 104 105 // Int16Array 106 var int16Data = new Int16Array(width * height * getNumberOfChannels(format)); 107 for (var ii = 0; ii < int16Data.length; ii++) { 108 int16Data[ii] = 1000; 109 } 110 runTextureCreationTest(true, format, int16Data, null); 111 }); 112 113 // Test that Uint16 encoded half float values can be used as texture data. 114 115 // First test that values in the 0-1 range can be written and read. 116 var halfFloatOneThird = 0x3555; // Half float 1/3 117 var uint16Formats = [ 118 { format: gl.RGBA, expected: [85, 85, 85, 85], }, 119 { format: gl.RGB, expected: [85, 85, 85, 255], }, 120 { format: gl.LUMINANCE, expected: [85, 85, 85, 255], }, 121 { format: gl.ALPHA, expected: [ 0, 0, 0, 85], }, 122 { format: gl.LUMINANCE_ALPHA, expected: [85, 85, 85, 85], }, 123 ]; 124 125 uint16Formats.forEach(function(f) { 126 var width = 2; 127 var height = 2; 128 var format = f.format; 129 130 var uint16Data = new Uint16Array(width * height * getNumberOfChannels(format)); 131 for (var ii = 0; ii < uint16Data.length; ii++) { 132 uint16Data[ii] = halfFloatOneThird; 133 } 134 runTextureCreationTest(true, format, uint16Data, f.expected); 135 }); 136 137 // Next check that values outside the 0-1 range can be written. 138 var halfFloatTenK = 0x70E2; // Half float 10000 139 var uint16Formats2 = [ 140 { format: gl.RGBA, subtractor: [10000, 10000, 10000, 10000], requireRenderable: true}, 141 { format: gl.RGB, subtractor: [10000, 10000, 10000, 1], requireRenderable: false}, 142 ]; 143 144 uint16Formats2.forEach(function(f) { 145 var width = 2; 146 var height = 2; 147 var format = f.format; 148 149 var uint16Data = new Uint16Array(width * height * getNumberOfChannels(format)); 150 for (var ii = 0; ii < uint16Data.length; ii++) { 151 uint16Data[ii] = halfFloatTenK; 152 } 153 runRenderTest(format, f.subtractor, uint16Data, f.requireRenderable); 154 }); 155 156 (function() { 157 debug(""); 158 var renderable = isRenderable(gl, ext); 159 var renderableExtName = "EXT_color_buffer_half_float"; 160 var supported = gl.getSupportedExtensions().includes(renderableExtName); 161 if (renderable && !supported) { 162 testFailed("RGBA/HALF_FLOAT_OES is color renderable but " + renderableExtName + " not exposed"); 163 } else if (supported && !renderable) { 164 testFailed(renderableExtName + " is exposed but RGBA/HALF_FLOAT_OES is not color renderable"); 165 } 166 if (supported) { 167 runRenderTest(gl.RGBA, [10000, 10000, 10000, 10000], null, true); 168 runRenderTest(gl.RGB, [10000, 10000, 10000, 1], null, false); 169 runFramebufferTest(); 170 } 171 })(); 172 173 // Check of getExtension() returns same object 174 runUniqueObjectTest(); 175 } 176 } 177 178 function isRenderable(gl, ext) { 179 var tex = gl.createTexture(); 180 gl.bindTexture(gl.TEXTURE_2D, tex); 181 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, ext.HALF_FLOAT_OES, null); 182 183 var fb = gl.createFramebuffer(); 184 gl.bindFramebuffer(gl.FRAMEBUFFER, fb); 185 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0); 186 187 var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); 188 gl.deleteFramebuffer(fb); 189 gl.deleteTexture(tex); 190 191 return status == gl.FRAMEBUFFER_COMPLETE; 192 } 193 194 function getNumberOfChannels(format) 195 { 196 if (format == gl.RGBA) 197 return 4; 198 else if (format == gl.RGB) 199 return 3; 200 else if (format == gl.LUMINANCE || format == gl.ALPHA) 201 return 1; 202 else if (format == gl.LUMINANCE_ALPHA) 203 return 2; 204 } 205 206 function allocateTexture() 207 { 208 var texture = gl.createTexture(); 209 gl.bindTexture(gl.TEXTURE_2D, texture); 210 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 211 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 212 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 213 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 214 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texture parameter setup should succeed"); 215 return texture; 216 } 217 218 function runTextureCreationTest(extensionEnabled, opt_format, opt_data, opt_expected) 219 { 220 var format = opt_format || gl.RGBA; 221 var data = opt_data || null; 222 var expectSuccess = true; 223 224 if (!extensionEnabled || !opt_expected) 225 expectSuccess = false; 226 debug("Testing texture creation with extension " + (extensionEnabled ? "enabled" : "disabled") + 227 ", format " + wtu.glEnumToString(gl, format) + ", and data " + (data ? "non-null" : "null") + 228 ". Expect " + (expectSuccess ? "Success" : "Failure")); 229 230 var texture = allocateTexture(); 231 var width = 2; 232 var height = 2; 233 gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, halfFloatOESEnum, data); 234 if(!extensionEnabled) { 235 wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "Half floating point texture must be disallowed if OES_texture_half_float isn't enabled"); 236 return; 237 } else if (!opt_expected) { 238 wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Half floating point texture allocation must be disallowed when ArrayBufferView is not-null and not-Uint16"); 239 return; 240 } else { 241 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Half floating point texture allocation should succeed if OES_texture_half_float is enabled"); 242 243 if (!data) { 244 gl.texImage2D(gl.TEXTURE_2D, 0, format, format, halfFloatOESEnum, colorCanvas); 245 } 246 wtu.clearAndDrawUnitQuad(gl); 247 wtu.checkCanvas(gl, opt_expected); 248 // Check that linear fails. 249 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 250 wtu.clearAndDrawUnitQuad(gl); 251 wtu.checkCanvas(gl, [0, 0, 0, 255], "should be black"); 252 } 253 254 } 255 256 function checkRenderingResults() 257 { 258 wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green"); 259 } 260 261 function runRenderTest(format, subtractor, data, requireRenderable) 262 { 263 var formatString = wtu.glEnumToString(gl, format); 264 265 debug(""); 266 267 if (!data) { 268 debug("Testing half floating point " + formatString + " render target"); 269 } else { 270 debug("Testing half floating point " + formatString + " from a Uint16Array"); 271 } 272 273 var texture = allocateTexture(); 274 var width = 2; 275 var height = 2; 276 277 gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, ext.HALF_FLOAT_OES, data); 278 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Half floating point texture allocation should succeed if OES_texture_half_float is enabled"); 279 280 if (!data) { 281 // Try to use this texture as render target 282 var fbo = gl.createFramebuffer(); 283 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 284 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); 285 gl.bindTexture(gl.TEXTURE_2D, null); 286 287 // It is legal for a WebGL implementation exposing the OES_texture_half_float extension to 288 // support half floating point textures but not as attachments to framebuffer objects. 289 if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { 290 if (requireRenderable) { 291 testFailed(formatString + " render targets not supported."); 292 } else { 293 debug(formatString + " render targets not supported -- this is legal"); 294 } 295 return; 296 } 297 298 var renderProgram = 299 wtu.setupProgram(gl, 300 [wtu.simpleVertexShader, "floatingPointFragmentShader"], 301 ['vPosition'], 302 [0]); 303 wtu.drawUnitQuad(gl); 304 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Rendering to half floating point texture should succeed"); 305 } 306 307 // Now sample from the half floating-point texture and verify we got the correct values. 308 var texturedShaders = [ 309 wtu.simpleTextureVertexShader, 310 "testFragmentShader" 311 ]; 312 var testProgram = 313 wtu.setupProgram(gl, 314 [wtu.simpleTextureVertexShader, "testFragmentShader"], 315 ['vPosition', 'texCoord0'], 316 [0, 1]); 317 var quadParameters = wtu.setupUnitQuad(gl, 0, 1); 318 gl.bindFramebuffer(gl.FRAMEBUFFER, null); 319 gl.bindTexture(gl.TEXTURE_2D, texture); 320 gl.useProgram(testProgram); 321 gl.uniform4fv(gl.getUniformLocation(testProgram, "subtractor"), subtractor); 322 wtu.drawUnitQuad(gl); 323 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "rendering from half floating point texture should succeed"); 324 checkRenderingResults(); 325 } 326 327 function runUniqueObjectTest() 328 { 329 debug(""); 330 debug("Testing that getExtension() returns the same object each time"); 331 ext = null; 332 gl.getExtension("OES_texture_half_float").myProperty = 2; 333 webglHarnessCollectGarbage(); 334 shouldBe('gl.getExtension("OES_texture_half_float").myProperty', '2'); 335 } 336 337 // Make sure we can call readPixels with the passed in arrayBufferConstructor and that the color 338 // channels are the ones we expect. If there is a mismatch between the glType and arrayBuffer type, 339 // fail the test. 340 function verifyReadPixelsColors(red, green, blue, alpha, alphaRGB, glFormat, glType, arrayBufferConstructor) { 341 var typeName = wtu.glEnumToString(gl, glType); 342 343 debug(wtu.glEnumToString(gl, glFormat) + " framebuffer with " + typeName + " readback."); 344 345 var arrayBuffer = new arrayBufferConstructor(4); 346 gl.readPixels(0, 0, 1, 1, gl.RGBA, glType, arrayBuffer); 347 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "readPixels should return NO_ERROR when reading " + typeName + " data."); 348 349 assertMsg(arrayBuffer[0] === red, "Red channel should be " + red + " for " + typeName + " readPixels. Received: " + arrayBuffer[0]); 350 assertMsg(arrayBuffer[1] === green, "Green channel should be " + green + " for " + typeName + " readPixels. Received: " + arrayBuffer[1]); 351 assertMsg(arrayBuffer[2] === blue, "Blue channel should be " + blue + " for " + typeName + " readPixels. Received: " + arrayBuffer[2]); 352 if (glFormat === gl.RGBA) { 353 assertMsg(arrayBuffer[3] === alpha, "Alpha channel should be " + alpha + " for " + typeName + " readPixels. Received: " + arrayBuffer[3]); 354 } else if (glFormat === gl.RGB) { 355 assertMsg(arrayBuffer[3] === alphaRGB, "Alpha channel should be " + alphaRGB + " for " + typeName + " readPixels. Received: " + arrayBuffer[3]); 356 } 357 358 // Make sure any arrayBuffer types that are not equal to arrayBufferConstructor fail readPixels. 359 if (arrayBufferConstructor !== Uint8Array) { 360 arrayBuffer = new Uint8Array(4); 361 gl.readPixels(0, 0, 1, 1, gl.RGBA, glType, arrayBuffer); 362 wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "readPixels should return INVALID_OPERATION when reading mismatched types. " + Uint8Array.toString()); 363 } 364 if (arrayBufferConstructor !== Float32Array) { 365 arrayBuffer = new Float32Array(4); 366 gl.readPixels(0, 0, 1, 1, gl.RGBA, glType, arrayBuffer); 367 wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "readPixels should return INVALID_OPERATION when reading mismatched types. " + Float32Array.toString()); 368 } 369 if (arrayBufferConstructor !== Uint16Array) { 370 arrayBuffer = new Uint16Array(4); 371 gl.readPixels(0, 0, 1, 1, gl.RGBA, glType, arrayBuffer); 372 wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "readPixels should return INVALID_OPERATION when reading mismatched types. " + Uint16Array.toString()); 373 } 374 } 375 376 // Verify that half float textures attached to frame buffers function correctly with regard to framebuffer 377 // completness, IMPLEMENTATION_COLOR_READ_FORMAT/TYPE and readPixels 378 function runFramebufferTest() { 379 debug(""); 380 debug("Framebuffer Tests"); 381 382 var texture = allocateTexture(); 383 gl.bindTexture(gl.TEXTURE_2D, texture); 384 385 var fbo = gl.createFramebuffer(); 386 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 387 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); 388 389 debug("Ensure non-color-renderable formats [LUMINANCE, LUMINANCE_ALPHA, ALPHA] fail."); 390 var arrayBufferFloatOutput = new Float32Array(4); // 4 color channels 391 [gl.LUMINANCE, gl.LUMINANCE_ALPHA, gl.ALPHA].forEach(function(badFormat) { 392 debug(wtu.glEnumToString(gl, badFormat) + " framebuffer"); 393 394 gl.texImage2D(gl.TEXTURE_2D, 0, badFormat, 1, 1, 0, badFormat, ext.HALF_FLOAT_OES, null); 395 shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); 396 397 shouldBeNull("gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT)"); 398 wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "IMPLEMENTATION_COLOR_READ_FORMAT should fail for incomplete framebuffers."); 399 400 shouldBeNull("gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE)"); 401 wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "IMPLEMENTATION_COLOR_READ_TYPE should fail for incomplete framebuffers."); 402 403 gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, arrayBufferFloatOutput); 404 wtu.glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION , "readPixels should fail on incomplete framebuffers."); 405 debug(""); 406 }); 407 408 debug("Ensure color renderable formats [RGBA, RGB] succeed."); 409 var arrayBufferHalfFloatInput = new Uint16Array(4); // 4 color channels 410 arrayBufferHalfFloatInput[0] = 0; // 0 in half float 411 arrayBufferHalfFloatInput[1] = 0x3400; // 0.25 in half float 412 arrayBufferHalfFloatInput[2] = 0x3800; // 0.50 in half float 413 arrayBufferHalfFloatInput[3] = 0x3A00; // 0.75 in half float 414 415 [gl.RGBA, gl.RGB].forEach(function(goodFormat) { 416 debug(wtu.glEnumToString(gl, goodFormat) + " framebuffer tests"); 417 debug(""); 418 419 gl.texImage2D(gl.TEXTURE_2D, 0, goodFormat, 1, 1, 0, goodFormat, ext.HALF_FLOAT_OES, arrayBufferHalfFloatInput); 420 if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) { 421 // Per the OES_color_buffer_half_float, RGBA/FLOAT should always succeed for readPixels 422 verifyReadPixelsColors( 423 0.00, // red 424 0.25, // green 425 0.50, // blue 426 0.75, // alpha 427 1.0, // alphaRGB 428 goodFormat, 429 gl.FLOAT, 430 Float32Array); 431 432 var implementationColorReadFormat = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT); 433 assertMsg(implementationColorReadFormat === gl.RGBA || implementationColorReadFormat === gl.RGB, 434 "IMPLEMENTATION_COLOR_READ_FORMAT should be color renderable: RGBA or RGB. Received: " + wtu.glEnumToString(gl, implementationColorReadFormat)); 435 436 var implementationColorReadType = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE); 437 438 // There is nothing in the specifications that keeps the 439 // implementation color read format and type from being the 440 // same as the implicitly supported one. For this reason, keep 441 // gl.FLOAT as one of the valid options. 442 assertMsg(implementationColorReadType === gl.UNSIGNED_BYTE || 443 implementationColorReadType === gl.FLOAT || 444 implementationColorReadType === ext.HALF_FLOAT_OES || 445 implementationColorReadType === gl.UNSIGNED_SHORT_4_4_4_4 || 446 implementationColorReadType === gl.UNSIGNED_SHORT_5_5_5_1 || 447 implementationColorReadType === gl.UNSIGNED_SHORT_5_6_5, 448 "IMPLEMENTATION_COLOR_READ_TYPE must be one of UNSIGNED_BYTE, UNSIGNED_SHORT_4_4_4_4, UNSIGNED_SHORT_5_5_5_1, UNSIGNED_SHORT_5_6_5, FLOAT, or HALF_FLOAT_OES. " + 449 "Received: " + wtu.glEnumToString(gl, implementationColorReadType)); 450 451 // Test the RGBA/HALF_FLOAT_OES combination 452 if (implementationColorReadFormat === gl.RGBA && implementationColorReadType === ext.HALF_FLOAT_OES) { 453 verifyReadPixelsColors( 454 0, // red 455 0x3400, // green 456 0x3800, // blue 457 0x3A00, // alpha 458 0x3C00, // alphaRGB 459 goodFormat, 460 ext.HALF_FLOAT_OES, 461 Uint16Array); 462 } 463 } 464 debug(""); 465 }); 466 } 467 468 debug(""); 469 var successfullyParsed = true; 470 </script> 471 <script src="../../js/js-test-post.js"></script> 472 473 </body> 474 </html>