oes-sample-variables.html (16899B)
1 <!-- 2 Copyright (c) 2023 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_sample_variables 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 <canvas width="32" height="32" id="c"></canvas> 18 <div id="description"></div> 19 <div id="console"></div> 20 <script> 21 "use strict"; 22 description("This test verifies the functionality of the OES_sample_variables extension, if it is available."); 23 24 debug(""); 25 26 var wtu = WebGLTestUtils; 27 var gl = wtu.create3DContext("c", { antialias: false }, 2); 28 var ext; 29 30 function runShaderTests(extensionEnabled) { 31 debug(""); 32 debug("Testing various shader compiles with extension " + (extensionEnabled ? "enabled" : "disabled")); 33 34 const macro = `#version 300 es 35 precision highp float; 36 out vec4 my_FragColor; 37 void main() { 38 #ifdef GL_OES_sample_variables 39 my_FragColor = vec4(1.0, 0.0, 0.0, 1.0); 40 #else 41 #error no GL_OES_sample_variables; 42 #endif 43 }`; 44 45 // Expect the macro shader to succeed ONLY if enabled 46 if (wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, macro])) { 47 if (extensionEnabled) { 48 testPassed("Macro defined in shaders when extension is enabled"); 49 } else { 50 testFailed("Macro defined in shaders when extension is disabled"); 51 } 52 } else { 53 if (extensionEnabled) { 54 testFailed("Macro not defined in shaders when extension is enabled"); 55 } else { 56 testPassed("Macro not defined in shaders when extension is disabled"); 57 } 58 } 59 60 const missing = `#version 300 es 61 precision highp float; 62 out vec4 my_FragColor; 63 void main() { 64 gl_SampleMask[0] = gl_SampleMaskIn[0] & 0x55555555; 65 my_FragColor = vec4(gl_SamplePosition.yx, float(gl_SampleID), float(gl_MaxSamples + gl_NumSamples)); 66 }`; 67 68 // Always expect the shader missing the #extension pragma to fail (whether enabled or not) 69 if (wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, missing])) { 70 testFailed("Sample variables allowed without #extension pragma"); 71 } else { 72 testPassed("Sample variables disallowed without #extension pragma"); 73 } 74 75 const valid = `#version 300 es 76 #extension GL_OES_sample_variables : enable 77 precision highp float; 78 out vec4 my_FragColor; 79 void main() { 80 gl_SampleMask[0] = gl_SampleMaskIn[0] & 0x55555555; 81 my_FragColor = vec4(gl_SamplePosition.yx, float(gl_SampleID), float(gl_MaxSamples + gl_NumSamples)); 82 }`; 83 84 // Try to compile a shader using sample variables that should only succeed if enabled 85 if (wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, valid])) { 86 if (extensionEnabled) { 87 testPassed("Sample variables compiled successfully when extension enabled"); 88 } else { 89 testFailed("Sample variables compiled successfully when extension disabled"); 90 } 91 } else { 92 if (extensionEnabled) { 93 testFailed("Sample variables failed to compile when extension enabled"); 94 } else { 95 testPassed("Sample variables failed to compile when extension disabled"); 96 } 97 } 98 99 debug(""); 100 } 101 102 function runMaxSamplesTest() { 103 debug(""); 104 debug("Testing gl_MaxSamples"); 105 106 const frag = `#version 300 es 107 #extension GL_OES_sample_variables : require 108 precision highp float; 109 out vec4 color; 110 void main() { 111 color = vec4(float(gl_MaxSamples * 4) / 255.0, 0.0, 0.0, 1.0); 112 }`; 113 gl.useProgram(wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, frag])); 114 115 wtu.setupUnitQuad(gl); 116 wtu.drawUnitQuad(gl); 117 118 wtu.checkCanvas(gl, [gl.getParameter(gl.MAX_SAMPLES) * 4, 0, 0, 255], "should match MAX_SAMPLES", 1); 119 } 120 121 function runNumSamplesTest() { 122 debug(""); 123 debug("Testing gl_NumSamples"); 124 125 const rbo = gl.createRenderbuffer(); 126 gl.bindRenderbuffer(gl.RENDERBUFFER, rbo); 127 gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, 32, 32); 128 129 const fbo = gl.createFramebuffer(); 130 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 131 gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo); 132 133 wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE); 134 135 const frag = `#version 300 es 136 #extension GL_OES_sample_variables : require 137 precision highp float; 138 out vec4 color; 139 void main() { 140 if (gl_NumSamples == 4) { 141 color = vec4(0.0, 1.0, 0.0, 1.0); 142 } else { 143 color = vec4(1.0, 0.0, 0.0, 1.0); 144 } 145 }`; 146 gl.useProgram(wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, frag])); 147 148 wtu.setupUnitQuad(gl); 149 wtu.drawUnitQuad(gl); 150 151 gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo); 152 gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null); 153 gl.blitFramebuffer(0, 0, 32, 32, 0, 0, 32, 32, gl.COLOR_BUFFER_BIT, gl.NEAREST); 154 155 gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null); 156 wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green"); 157 } 158 159 function runSampleIDTest() { 160 debug(""); 161 debug("Testing gl_SampleID"); 162 163 const frag = `#version 300 es 164 #extension GL_OES_sample_variables : require 165 precision highp float; 166 out vec4 color; 167 uniform int id; 168 void main() { 169 // Special value when the selected sample is processed, 0.0 otherwise 170 float r = float(gl_SampleID == id ? (1 << gl_SampleID) : 0) * 32.0 / 255.0; 171 // Must always be 0.0 172 float g = float(gl_SampleID < 0 || gl_SampleID >= gl_NumSamples); 173 color = vec4(r, g, 0.0, 1.0); 174 }`; 175 const program = wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, frag]); 176 gl.useProgram(program); 177 178 wtu.setupUnitQuad(gl); 179 180 const rbo = gl.createRenderbuffer(); 181 gl.bindRenderbuffer(gl.RENDERBUFFER, rbo); 182 gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, 32, 32); 183 184 const fbo = gl.createFramebuffer(); 185 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 186 gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo); 187 188 wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE); 189 190 for (let sample = 0; sample < 4; sample++) { 191 debug(`Sample ${sample} is selected`); 192 193 gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo); 194 gl.uniform1i(gl.getUniformLocation(program, "id"), sample); 195 wtu.drawUnitQuad(gl); 196 197 gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo); 198 gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null); 199 gl.blitFramebuffer(0, 0, 32, 32, 0, 0, 32, 32, gl.COLOR_BUFFER_BIT, gl.NEAREST); 200 201 gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null); 202 wtu.checkCanvas(gl, [(1 << sample) * 8, 0, 0, 255], undefined, 1); 203 } 204 } 205 206 function runSampleMaskInTest() { 207 debug(""); 208 debug("Testing gl_SampleMaskIn"); 209 210 const frag = `#version 300 es 211 #extension GL_OES_sample_variables : require 212 precision highp float; 213 out vec4 color; 214 uint popcount(uint v) { 215 uint c = 0u; 216 for (; v != 0u; v >>= 1) c += v & 1u; 217 return c; 218 } 219 void main() { 220 float r = float(popcount(uint(gl_SampleMaskIn[0]))); 221 color = vec4(r * 4.0 / 255.0, 0, 0, 1); 222 }`; 223 224 const program = wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, frag]); 225 gl.useProgram(program); 226 227 // Use a triangle instead of the WTU's quad 228 // to avoid artifacts along the diagonal 229 const vertices = gl.createBuffer(); 230 gl.bindBuffer(gl.ARRAY_BUFFER, vertices); 231 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 232 -1.0, 1.0, 233 1.0, -1.0, 234 -1.0, -1.0]), gl.STATIC_DRAW); 235 gl.enableVertexAttribArray(0); 236 gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); 237 238 function test(sampleCount, sampleCoverageEnabled, coverage) { 239 if (sampleCoverageEnabled) { 240 gl.enable(gl.SAMPLE_COVERAGE); 241 } else { 242 gl.disable(gl.SAMPLE_COVERAGE); 243 } 244 245 gl.sampleCoverage(coverage, false); 246 247 const rbo = gl.createRenderbuffer(); 248 gl.bindRenderbuffer(gl.RENDERBUFFER, rbo); 249 gl.renderbufferStorageMultisample(gl.RENDERBUFFER, sampleCount, gl.RGBA8, 32, 32); 250 251 const fbo = gl.createFramebuffer(); 252 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 253 gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo); 254 255 wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE); 256 257 gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo); 258 gl.clear(gl.COLOR_BUFFER_BIT); 259 gl.drawArrays(gl.TRIANGLES, 0, 3); 260 261 gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo); 262 gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null); 263 gl.blitFramebuffer(0, 0, 32, 32, 0, 0, 32, 32, gl.COLOR_BUFFER_BIT, gl.NEAREST); 264 265 // Shader scales up the number of input samples to increase precision in unorm8 space. 266 let expected = Math.max(sampleCount, 1) * 4; 267 268 // Sample coverage must not affect single sampled buffers 269 if (sampleCoverageEnabled && sampleCount > 0) { 270 // The number of samples in gl_SampleMaskIn must be affected by the sample 271 // coverage GL state and then the resolved value must be scaled down again. 272 expected *= coverage * coverage; 273 } 274 275 // Check only the red channel 276 gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null); 277 const pixel = new Uint8Array(4); 278 gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel); 279 const message = `Expected: ${expected}, Actual: ${pixel[0]}, ` + 280 `Samples: ${sampleCount}, Sample Coverage: ${sampleCoverageEnabled}, Coverage: ${coverage}`; 281 if (Math.abs(pixel[0] - expected) > 2) { 282 testFailed(message); 283 } else { 284 testPassed(message); 285 } 286 } 287 288 // Include all exposed sample counts and additionally test single-sampled rendering 289 const sampleCounts = [...gl.getInternalformatParameter(gl.RENDERBUFFER, gl.RGBA8, gl.SAMPLES), 0]; 290 291 for (const sampleCount of sampleCounts) { 292 if (sampleCount > 32) { 293 // This test will not work with more than 32 samples. 294 continue; 295 } 296 297 for (const sampleCoverageEnabled of [false, true]) { 298 for (const coverage of [0.0, 0.5, 1.0]) { 299 if (sampleCount == 1 && coverage != 0.0 && coverage != 1.0) { 300 continue; 301 } 302 test(sampleCount, sampleCoverageEnabled, coverage); 303 } 304 } 305 } 306 } 307 308 function runSampleMaskInPerSampleTest() { 309 debug(""); 310 debug("Testing gl_SampleMaskIn with per-sample shading"); 311 312 const frag = `#version 300 es 313 #extension GL_OES_sample_variables : require 314 precision highp float; 315 out vec4 color; 316 void main() { 317 float r = float(gl_SampleMaskIn[0] == (1 << gl_SampleID)); 318 color = vec4(r, 0, 0, 1); 319 }`; 320 const program = wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, frag]); 321 gl.useProgram(program); 322 323 wtu.setupUnitQuad(gl); 324 325 // Include all exposed sample counts and additionally test single-sampled rendering 326 const sampleCounts = [...gl.getInternalformatParameter(gl.RENDERBUFFER, gl.RGBA8, gl.SAMPLES), 0]; 327 for (const sampleCount of sampleCounts) { 328 if (sampleCount > 32) { 329 // This test will not work with more than 32 samples. 330 continue; 331 } 332 333 const rbo = gl.createRenderbuffer(); 334 gl.bindRenderbuffer(gl.RENDERBUFFER, rbo); 335 gl.renderbufferStorageMultisample(gl.RENDERBUFFER, sampleCount, gl.RGBA8, 32, 32); 336 337 const fbo = gl.createFramebuffer(); 338 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 339 gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo); 340 341 wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE); 342 343 gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo); 344 wtu.drawUnitQuad(gl); 345 346 gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo); 347 gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null); 348 gl.blitFramebuffer(0, 0, 32, 32, 0, 0, 32, 32, gl.COLOR_BUFFER_BIT, gl.NEAREST); 349 350 gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null); 351 wtu.checkCanvas(gl, [255, 0, 0, 255], `Samples: ${sampleCount}`, 1); 352 } 353 } 354 355 function runSampleMaskTest() { 356 debug(""); 357 debug("Testing gl_SampleMask"); 358 359 const frag = `#version 300 es 360 #extension GL_OES_sample_variables : require 361 precision highp float; 362 uniform highp int sampleMask; 363 out vec4 color; 364 void main() { 365 gl_SampleMask[0] = sampleMask; 366 color = vec4(1, 0, 0, 1); 367 }`; 368 const program = wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, frag]); 369 gl.useProgram(program); 370 371 // Use a triangle instead of the WTU's quad 372 // to avoid artifacts along the diagonal 373 const vertices = gl.createBuffer(); 374 gl.bindBuffer(gl.ARRAY_BUFFER, vertices); 375 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 376 -1.0, 1.0, 377 1.0, -1.0, 378 -1.0, -1.0]), gl.STATIC_DRAW); 379 gl.enableVertexAttribArray(0); 380 gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); 381 382 function test(sampleCount, sampleMask) { 383 const rbo = gl.createRenderbuffer(); 384 gl.bindRenderbuffer(gl.RENDERBUFFER, rbo); 385 gl.renderbufferStorageMultisample(gl.RENDERBUFFER, sampleCount, gl.RGBA8, 32, 32); 386 387 const fbo = gl.createFramebuffer(); 388 gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); 389 gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo); 390 391 wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE); 392 393 gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo); 394 gl.clear(gl.COLOR_BUFFER_BIT); 395 gl.uniform1i(gl.getUniformLocation(program, "sampleMask"), sampleMask); 396 gl.drawArrays(gl.TRIANGLES, 0, 3); 397 398 gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo); 399 gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null); 400 gl.blitFramebuffer(0, 0, 32, 32, 0, 0, 32, 32, gl.COLOR_BUFFER_BIT, gl.NEAREST); 401 402 let expected = 1.0; 403 if (sampleCount > 0) { 404 let mask = sampleMask & ((1 << Math.max(sampleCount, 1)) - 1); 405 let bits = 0; 406 for (; mask != 0; mask >>= 1) bits += mask & 1; 407 expected = bits / Math.max(sampleCount, 1); 408 } 409 expected *= 255; 410 411 // Check only the red channel 412 gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null); 413 const pixel = new Uint8Array(4); 414 gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel); 415 const message = `Samples: ${sampleCount}, ` 416 + `gl_SampleMask[0]: 0x${sampleMask.toString(16).padStart(8, "0").toUpperCase()}, ` 417 + `Actual: ${pixel[0]}, Expected: ${expected}`; 418 if (Math.abs(pixel[0] - expected) > 2) { 419 testFailed(message); 420 } else { 421 testPassed(message); 422 } 423 } 424 425 // Include all exposed sample counts and additionally test single-sampled rendering 426 const sampleCounts = [...gl.getInternalformatParameter(gl.RENDERBUFFER, gl.RGBA8, gl.SAMPLES), 0]; 427 428 for (const sampleCount of sampleCounts) { 429 if (sampleCount > 31) { 430 // This test will not work with more than 31 samples. 431 continue; 432 } 433 434 for (const sampleMask of [0xFFFFFFFF, 0x55555555, 0xAAAAAAAA, 0x00000000]) { 435 test(sampleCount, sampleMask); 436 } 437 } 438 } 439 440 function runTest() { 441 if (!gl) { 442 testFailed("WebGL context does not exist"); 443 return; 444 } 445 testPassed("WebGL context exists"); 446 447 runShaderTests(false); 448 449 ext = gl.getExtension("OES_sample_variables"); 450 wtu.runExtensionSupportedTest(gl, "OES_sample_variables", ext !== null); 451 452 if (!ext) { 453 testPassed("No OES_sample_variables support -- this is legal"); 454 } else { 455 testPassed("Successfully enabled OES_sample_variables extension"); 456 runShaderTests(true); 457 458 debug("Testing sample variables"); 459 runMaxSamplesTest(); 460 runNumSamplesTest(); 461 runSampleIDTest(); 462 runSampleMaskInTest(); 463 runSampleMaskInPerSampleTest(); 464 runSampleMaskTest(); 465 } 466 } 467 468 runTest(); 469 470 var successfullyParsed = true; 471 </script> 472 <script src="../../js/js-test-post.js"></script> 473 </body> 474 </html>