glsl-generator.js (40685B)
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 GLSLGenerator = (function() { 7 8 var vertexShaderTemplate = [ 9 "attribute vec4 aPosition;", 10 "", 11 "varying vec4 vColor;", 12 "", 13 "$(extra)", 14 "$(emu)", 15 "", 16 "void main()", 17 "{", 18 " gl_Position = aPosition;", 19 " vec2 texcoord = vec2(aPosition.xy * 0.5 + vec2(0.5, 0.5));", 20 " vec4 color = vec4(", 21 " texcoord,", 22 " texcoord.x * texcoord.y,", 23 " (1.0 - texcoord.x) * texcoord.y * 0.5 + 0.5);", 24 " $(test)", 25 "}" 26 ].join("\n"); 27 28 var fragmentShaderTemplate = [ 29 "precision mediump float;", 30 "", 31 "varying vec4 vColor;", 32 "", 33 "$(extra)", 34 "$(emu)", 35 "", 36 "void main()", 37 "{", 38 " $(test)", 39 "}" 40 ].join("\n"); 41 42 var baseVertexShader = [ 43 "attribute vec4 aPosition;", 44 "", 45 "varying vec4 vColor;", 46 "", 47 "void main()", 48 "{", 49 " gl_Position = aPosition;", 50 " vec2 texcoord = vec2(aPosition.xy * 0.5 + vec2(0.5, 0.5));", 51 " vColor = vec4(", 52 " texcoord,", 53 " texcoord.x * texcoord.y,", 54 " (1.0 - texcoord.x) * texcoord.y * 0.5 + 0.5);", 55 "}" 56 ].join("\n"); 57 58 var baseVertexShaderWithColor = [ 59 "attribute vec4 aPosition;", 60 "attribute vec4 aColor;", 61 "", 62 "varying vec4 vColor;", 63 "", 64 "void main()", 65 "{", 66 " gl_Position = aPosition;", 67 " vColor = aColor;", 68 "}" 69 ].join("\n"); 70 71 var baseFragmentShader = [ 72 "precision mediump float;", 73 "varying vec4 vColor;", 74 "", 75 "void main()", 76 "{", 77 " gl_FragColor = vColor;", 78 "}" 79 ].join("\n"); 80 81 var types = [ 82 { type: "float", 83 code: [ 84 "float $(func)_emu($(args)) {", 85 " return $(func)_base($(baseArgs));", 86 "}"].join("\n") 87 }, 88 { type: "vec2", 89 code: [ 90 "vec2 $(func)_emu($(args)) {", 91 " return vec2(", 92 " $(func)_base($(baseArgsX)),", 93 " $(func)_base($(baseArgsY)));", 94 "}"].join("\n") 95 }, 96 { type: "vec3", 97 code: [ 98 "vec3 $(func)_emu($(args)) {", 99 " return vec3(", 100 " $(func)_base($(baseArgsX)),", 101 " $(func)_base($(baseArgsY)),", 102 " $(func)_base($(baseArgsZ)));", 103 "}"].join("\n") 104 }, 105 { type: "vec4", 106 code: [ 107 "vec4 $(func)_emu($(args)) {", 108 " return vec4(", 109 " $(func)_base($(baseArgsX)),", 110 " $(func)_base($(baseArgsY)),", 111 " $(func)_base($(baseArgsZ)),", 112 " $(func)_base($(baseArgsW)));", 113 "}"].join("\n") 114 } 115 ]; 116 117 var bvecTypes = [ 118 { type: "bvec2", 119 code: [ 120 "bvec2 $(func)_emu($(args)) {", 121 " return bvec2(", 122 " $(func)_base($(baseArgsX)),", 123 " $(func)_base($(baseArgsY)));", 124 "}"].join("\n") 125 }, 126 { type: "bvec3", 127 code: [ 128 "bvec3 $(func)_emu($(args)) {", 129 " return bvec3(", 130 " $(func)_base($(baseArgsX)),", 131 " $(func)_base($(baseArgsY)),", 132 " $(func)_base($(baseArgsZ)));", 133 "}"].join("\n") 134 }, 135 { type: "bvec4", 136 code: [ 137 "vec4 $(func)_emu($(args)) {", 138 " return bvec4(", 139 " $(func)_base($(baseArgsX)),", 140 " $(func)_base($(baseArgsY)),", 141 " $(func)_base($(baseArgsZ)),", 142 " $(func)_base($(baseArgsW)));", 143 "}"].join("\n") 144 } 145 ]; 146 147 var replaceRE = /\$\((\w+)\)/g; 148 149 var replaceParams = function(str) { 150 var args = arguments; 151 return str.replace(replaceRE, function(str, p1, offset, s) { 152 for (var ii = 1; ii < args.length; ++ii) { 153 if (args[ii][p1] !== undefined) { 154 return args[ii][p1]; 155 } 156 } 157 throw "unknown string param '" + p1 + "'"; 158 }); 159 }; 160 161 var generateReferenceShader = function( 162 shaderInfo, template, params, typeInfo, test) { 163 var input = shaderInfo.input; 164 var output = shaderInfo.output; 165 var feature = params.feature; 166 var testFunc = params.testFunc; 167 var emuFunc = params.emuFunc || ""; 168 var extra = params.extra || ''; 169 var args = params.args || "$(type) value"; 170 var type = typeInfo.type; 171 var typeCode = typeInfo.code; 172 173 var baseArgs = params.baseArgs || "value$(field)"; 174 var baseArgsX = replaceParams(baseArgs, {field: ".x"}); 175 var baseArgsY = replaceParams(baseArgs, {field: ".y"}); 176 var baseArgsZ = replaceParams(baseArgs, {field: ".z"}); 177 var baseArgsW = replaceParams(baseArgs, {field: ".w"}); 178 var baseArgs = replaceParams(baseArgs, {field: ""}); 179 180 test = replaceParams(test, { 181 input: input, 182 output: output, 183 func: feature + "_emu" 184 }); 185 emuFunc = replaceParams(emuFunc, { 186 func: feature 187 }); 188 args = replaceParams(args, { 189 type: type 190 }); 191 typeCode = replaceParams(typeCode, { 192 func: feature, 193 type: type, 194 args: args, 195 baseArgs: baseArgs, 196 baseArgsX: baseArgsX, 197 baseArgsY: baseArgsY, 198 baseArgsZ: baseArgsZ, 199 baseArgsW: baseArgsW 200 }); 201 var shader = replaceParams(template, { 202 extra: extra, 203 emu: emuFunc + "\n\n" + typeCode, 204 test: test 205 }); 206 return shader; 207 }; 208 209 var generateTestShader = function( 210 shaderInfo, template, params, test) { 211 var input = shaderInfo.input; 212 var output = shaderInfo.output; 213 var feature = params.feature; 214 var testFunc = params.testFunc; 215 var extra = params.extra || ''; 216 217 test = replaceParams(test, { 218 input: input, 219 output: output, 220 func: feature 221 }); 222 var shader = replaceParams(template, { 223 extra: extra, 224 emu: '', 225 test: test 226 }); 227 return shader; 228 }; 229 230 function _reportResults(refData, refImg, testData, testImg, tolerance, 231 width, height, ctx, imgData, wtu, canvas2d, consoleDiv) { 232 var same = true; 233 var firstFailure = null; 234 for (var yy = 0; yy < height; ++yy) { 235 for (var xx = 0; xx < width; ++xx) { 236 var offset = (yy * width + xx) * 4; 237 var imgOffset = ((height - yy - 1) * width + xx) * 4; 238 imgData.data[imgOffset + 0] = 0; 239 imgData.data[imgOffset + 1] = 0; 240 imgData.data[imgOffset + 2] = 0; 241 imgData.data[imgOffset + 3] = 255; 242 if (Math.abs(refData[offset + 0] - testData[offset + 0]) > tolerance || 243 Math.abs(refData[offset + 1] - testData[offset + 1]) > tolerance || 244 Math.abs(refData[offset + 2] - testData[offset + 2]) > tolerance || 245 Math.abs(refData[offset + 3] - testData[offset + 3]) > tolerance) { 246 var detail = 'at (' + xx + ',' + yy + '): ref=(' + 247 refData[offset + 0] + ',' + 248 refData[offset + 1] + ',' + 249 refData[offset + 2] + ',' + 250 refData[offset + 3] + ') test=(' + 251 testData[offset + 0] + ',' + 252 testData[offset + 1] + ',' + 253 testData[offset + 2] + ',' + 254 testData[offset + 3] + ') tolerance=' + tolerance; 255 consoleDiv.appendChild(document.createTextNode(detail)); 256 consoleDiv.appendChild(document.createElement('br')); 257 if (!firstFailure) { 258 firstFailure = ": " + detail; 259 } 260 imgData.data[imgOffset] = 255; 261 same = false; 262 } 263 } 264 } 265 266 var diffImg = null; 267 if (!same) { 268 ctx.putImageData(imgData, 0, 0); 269 diffImg = wtu.makeImageFromCanvas(canvas2d); 270 } 271 272 var div = document.createElement("div"); 273 div.className = "testimages"; 274 wtu.insertImage(div, "ref", refImg); 275 wtu.insertImage(div, "test", testImg); 276 if (diffImg) { 277 wtu.insertImage(div, "diff", diffImg); 278 } 279 div.appendChild(document.createElement('br')); 280 281 consoleDiv.appendChild(div); 282 283 if (!same) { 284 testFailed("images are different" + (firstFailure ? firstFailure : "")); 285 } else { 286 testPassed("images are the same"); 287 } 288 289 consoleDiv.appendChild(document.createElement('hr')); 290 } 291 292 var runFeatureTest = function(params) { 293 var wtu = WebGLTestUtils; 294 var gridRes = params.gridRes; 295 var vertexTolerance = params.tolerance || 0; 296 var fragmentTolerance = params.tolerance || 1; 297 if ('fragmentTolerance' in params) 298 fragmentTolerance = params.fragmentTolerance; 299 300 description("Testing GLSL feature: " + params.feature); 301 302 var width = 32; 303 var height = 32; 304 305 var consoleDiv = document.getElementById("console"); 306 var canvas = document.createElement('canvas'); 307 canvas.width = width; 308 canvas.height = height; 309 var gl = wtu.create3DContext(canvas, { premultipliedAlpha: false }); 310 if (!gl) { 311 testFailed("context does not exist"); 312 finishTest(); 313 return; 314 } 315 316 var canvas2d = document.createElement('canvas'); 317 canvas2d.width = width; 318 canvas2d.height = height; 319 var ctx = canvas2d.getContext("2d"); 320 var imgData = ctx.getImageData(0, 0, width, height); 321 322 var shaderInfos = [ 323 { type: "vertex", 324 input: "color", 325 output: "vColor", 326 vertexShaderTemplate: vertexShaderTemplate, 327 fragmentShaderTemplate: baseFragmentShader, 328 tolerance: vertexTolerance 329 }, 330 { type: "fragment", 331 input: "vColor", 332 output: "gl_FragColor", 333 vertexShaderTemplate: baseVertexShader, 334 fragmentShaderTemplate: fragmentShaderTemplate, 335 tolerance: fragmentTolerance 336 } 337 ]; 338 for (var ss = 0; ss < shaderInfos.length; ++ss) { 339 var shaderInfo = shaderInfos[ss]; 340 var tests = params.tests; 341 var testTypes = params.emuFuncs || (params.bvecTest ? bvecTypes : types); 342 // Test vertex shaders 343 for (var ii = 0; ii < tests.length; ++ii) { 344 var type = testTypes[ii]; 345 if (params.simpleEmu) { 346 type = { 347 type: type.type, 348 code: params.simpleEmu 349 }; 350 } 351 debug(""); 352 var str = replaceParams(params.testFunc, { 353 func: params.feature, 354 type: type.type, 355 arg0: type.type 356 }); 357 var passMsg = "Testing: " + str + " in " + shaderInfo.type + " shader"; 358 debug(passMsg); 359 360 var referenceVertexShaderSource = generateReferenceShader( 361 shaderInfo, 362 shaderInfo.vertexShaderTemplate, 363 params, 364 type, 365 tests[ii]); 366 var referenceFragmentShaderSource = generateReferenceShader( 367 shaderInfo, 368 shaderInfo.fragmentShaderTemplate, 369 params, 370 type, 371 tests[ii]); 372 var testVertexShaderSource = generateTestShader( 373 shaderInfo, 374 shaderInfo.vertexShaderTemplate, 375 params, 376 tests[ii]); 377 var testFragmentShaderSource = generateTestShader( 378 shaderInfo, 379 shaderInfo.fragmentShaderTemplate, 380 params, 381 tests[ii]); 382 383 384 debug(""); 385 var referenceVertexShader = wtu.loadShader(gl, referenceVertexShaderSource, gl.VERTEX_SHADER, testFailed, true, 'reference'); 386 var referenceFragmentShader = wtu.loadShader(gl, referenceFragmentShaderSource, gl.FRAGMENT_SHADER, testFailed, true, 'reference'); 387 var testVertexShader = wtu.loadShader(gl, testVertexShaderSource, gl.VERTEX_SHADER, testFailed, true, 'test'); 388 var testFragmentShader = wtu.loadShader(gl, testFragmentShaderSource, gl.FRAGMENT_SHADER, testFailed, true, 'test'); 389 debug(""); 390 391 if (parseInt(wtu.getUrlOptions().dumpShaders)) { 392 var vRefInfo = { 393 shader: referenceVertexShader, 394 shaderSuccess: true, 395 label: "reference vertex shader", 396 source: referenceVertexShaderSource 397 }; 398 var fRefInfo = { 399 shader: referenceFragmentShader, 400 shaderSuccess: true, 401 label: "reference fragment shader", 402 source: referenceFragmentShaderSource 403 }; 404 wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vRefInfo, fRefInfo); 405 406 var vTestInfo = { 407 shader: testVertexShader, 408 shaderSuccess: true, 409 label: "test vertex shader", 410 source: testVertexShaderSource 411 }; 412 var fTestInfo = { 413 shader: testFragmentShader, 414 shaderSuccess: true, 415 label: "test fragment shader", 416 source: testFragmentShaderSource 417 }; 418 wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vTestInfo, fTestInfo); 419 } 420 421 var refData = draw( 422 referenceVertexShader, referenceFragmentShader); 423 var refImg = wtu.makeImageFromCanvas(canvas); 424 if (ss == 0) { 425 var testData = draw( 426 testVertexShader, referenceFragmentShader); 427 } else { 428 var testData = draw( 429 referenceVertexShader, testFragmentShader); 430 } 431 var testImg = wtu.makeImageFromCanvas(canvas); 432 433 _reportResults(refData, refImg, testData, testImg, shaderInfo.tolerance, 434 width, height, ctx, imgData, wtu, canvas2d, consoleDiv); 435 } 436 } 437 438 finishTest(); 439 440 function draw(vertexShader, fragmentShader) { 441 var program = wtu.createProgram(gl, vertexShader, fragmentShader, testFailed); 442 443 var posLoc = gl.getAttribLocation(program, "aPosition"); 444 wtu.setupIndexedQuad(gl, gridRes, posLoc); 445 446 gl.useProgram(program); 447 wtu.clearAndDrawIndexedQuad(gl, gridRes, [0, 0, 255, 255]); 448 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); 449 450 var img = new Uint8Array(width * height * 4); 451 gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); 452 return img; 453 } 454 455 }; 456 457 var runBasicTest = function(params) { 458 var wtu = WebGLTestUtils; 459 var gridRes = params.gridRes; 460 var vertexTolerance = params.tolerance || 0; 461 var fragmentTolerance = vertexTolerance; 462 if ('fragmentTolerance' in params) 463 fragmentTolerance = params.fragmentTolerance || 0; 464 465 description("Testing : " + document.getElementsByTagName("title")[0].innerText); 466 467 var width = 32; 468 var height = 32; 469 470 var consoleDiv = document.getElementById("console"); 471 var canvas = document.createElement('canvas'); 472 canvas.width = width; 473 canvas.height = height; 474 var gl = wtu.create3DContext(canvas); 475 if (!gl) { 476 testFailed("context does not exist"); 477 finishTest(); 478 return; 479 } 480 481 var canvas2d = document.createElement('canvas'); 482 canvas2d.width = width; 483 canvas2d.height = height; 484 var ctx = canvas2d.getContext("2d"); 485 var imgData = ctx.getImageData(0, 0, width, height); 486 487 var shaderInfos = [ 488 { type: "vertex", 489 input: "color", 490 output: "vColor", 491 vertexShaderTemplate: vertexShaderTemplate, 492 fragmentShaderTemplate: baseFragmentShader, 493 tolerance: vertexTolerance 494 }, 495 { type: "fragment", 496 input: "vColor", 497 output: "gl_FragColor", 498 vertexShaderTemplate: baseVertexShader, 499 fragmentShaderTemplate: fragmentShaderTemplate, 500 tolerance: fragmentTolerance 501 } 502 ]; 503 for (var ss = 0; ss < shaderInfos.length; ++ss) { 504 var shaderInfo = shaderInfos[ss]; 505 var tests = params.tests; 506 // var testTypes = params.emuFuncs || (params.bvecTest ? bvecTypes : types); 507 // Test vertex shaders 508 for (var ii = 0; ii < tests.length; ++ii) { 509 var test = tests[ii]; 510 debug(""); 511 var passMsg = "Testing: " + test.name + " in " + shaderInfo.type + " shader"; 512 debug(passMsg); 513 514 function genShader(shaderInfo, template, shader, subs) { 515 shader = replaceParams(shader, subs, { 516 input: shaderInfo.input, 517 output: shaderInfo.output 518 }); 519 shader = replaceParams(template, subs, { 520 test: shader, 521 emu: "", 522 extra: "" 523 }); 524 return shader; 525 } 526 527 var referenceVertexShaderSource = genShader( 528 shaderInfo, 529 shaderInfo.vertexShaderTemplate, 530 test.reference.shader, 531 test.reference.subs); 532 var referenceFragmentShaderSource = genShader( 533 shaderInfo, 534 shaderInfo.fragmentShaderTemplate, 535 test.reference.shader, 536 test.reference.subs); 537 var testVertexShaderSource = genShader( 538 shaderInfo, 539 shaderInfo.vertexShaderTemplate, 540 test.test.shader, 541 test.test.subs); 542 var testFragmentShaderSource = genShader( 543 shaderInfo, 544 shaderInfo.fragmentShaderTemplate, 545 test.test.shader, 546 test.test.subs); 547 548 debug(""); 549 var referenceVertexShader = wtu.loadShader(gl, referenceVertexShaderSource, gl.VERTEX_SHADER, testFailed, true, 'reference'); 550 var referenceFragmentShader = wtu.loadShader(gl, referenceFragmentShaderSource, gl.FRAGMENT_SHADER, testFailed, true, 'reference'); 551 var testVertexShader = wtu.loadShader(gl, testVertexShaderSource, gl.VERTEX_SHADER, testFailed, true, 'test'); 552 var testFragmentShader = wtu.loadShader(gl, testFragmentShaderSource, gl.FRAGMENT_SHADER, testFailed, true, 'test'); 553 debug(""); 554 555 if (parseInt(wtu.getUrlOptions().dumpShaders)) { 556 var vRefInfo = { 557 shader: referenceVertexShader, 558 shaderSuccess: true, 559 label: "reference vertex shader", 560 source: referenceVertexShaderSource 561 }; 562 var fRefInfo = { 563 shader: referenceFragmentShader, 564 shaderSuccess: true, 565 label: "reference fragment shader", 566 source: referenceFragmentShaderSource 567 }; 568 wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vRefInfo, fRefInfo); 569 570 var vTestInfo = { 571 shader: testVertexShader, 572 shaderSuccess: true, 573 label: "test vertex shader", 574 source: testVertexShaderSource 575 }; 576 var fTestInfo = { 577 shader: testFragmentShader, 578 shaderSuccess: true, 579 label: "test fragment shader", 580 source: testFragmentShaderSource 581 }; 582 wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vTestInfo, fTestInfo); 583 } 584 585 var refData = draw(referenceVertexShader, referenceFragmentShader); 586 var refImg = wtu.makeImageFromCanvas(canvas); 587 if (ss == 0) { 588 var testData = draw(testVertexShader, referenceFragmentShader); 589 } else { 590 var testData = draw(referenceVertexShader, testFragmentShader); 591 } 592 var testImg = wtu.makeImageFromCanvas(canvas); 593 594 _reportResults(refData, refImg, testData, testImg, shaderInfo.tolerance, 595 width, height, ctx, imgData, wtu, canvas2d, consoleDiv); 596 } 597 } 598 599 finishTest(); 600 601 function draw(vertexShader, fragmentShader) { 602 var program = wtu.createProgram(gl, vertexShader, fragmentShader, testFailed); 603 604 var posLoc = gl.getAttribLocation(program, "aPosition"); 605 wtu.setupIndexedQuad(gl, gridRes, posLoc); 606 607 gl.useProgram(program); 608 wtu.clearAndDrawIndexedQuad(gl, gridRes, [0, 0, 255, 255]); 609 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); 610 611 var img = new Uint8Array(width * height * 4); 612 gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); 613 return img; 614 } 615 616 }; 617 618 var runReferenceImageTest = function(params) { 619 var wtu = WebGLTestUtils; 620 var gridRes = params.gridRes; 621 var vertexTolerance = params.tolerance || 0; 622 var fragmentTolerance = vertexTolerance; 623 if ('fragmentTolerance' in params) 624 fragmentTolerance = params.fragmentTolerance || 0; 625 626 description("Testing GLSL feature: " + params.feature); 627 628 var width = 32; 629 var height = 32; 630 631 var consoleDiv = document.getElementById("console"); 632 var canvas = document.createElement('canvas'); 633 canvas.width = width; 634 canvas.height = height; 635 var gl = wtu.create3DContext(canvas, { antialias: false, premultipliedAlpha: false }); 636 if (!gl) { 637 testFailed("context does not exist"); 638 finishTest(); 639 return; 640 } 641 642 var canvas2d = document.createElement('canvas'); 643 canvas2d.width = width; 644 canvas2d.height = height; 645 var ctx = canvas2d.getContext("2d"); 646 var imgData = ctx.getImageData(0, 0, width, height); 647 648 // State for reference images for vertex shader tests. 649 // These are drawn with the same tessellated grid as the test vertex 650 // shader so that the interpolation is identical. The grid is reused 651 // from test to test; the colors are changed. 652 653 var indexedQuadForReferenceVertexShader = 654 wtu.setupIndexedQuad(gl, gridRes, 0); 655 var referenceVertexShaderProgram = 656 wtu.setupProgram(gl, [ baseVertexShaderWithColor, baseFragmentShader ], 657 ["aPosition", "aColor"]); 658 var referenceVertexShaderColorBuffer = gl.createBuffer(); 659 660 var shaderInfos = [ 661 { type: "vertex", 662 input: "color", 663 output: "vColor", 664 vertexShaderTemplate: vertexShaderTemplate, 665 fragmentShaderTemplate: baseFragmentShader, 666 tolerance: vertexTolerance 667 }, 668 { type: "fragment", 669 input: "vColor", 670 output: "gl_FragColor", 671 vertexShaderTemplate: baseVertexShader, 672 fragmentShaderTemplate: fragmentShaderTemplate, 673 tolerance: fragmentTolerance 674 } 675 ]; 676 for (var ss = 0; ss < shaderInfos.length; ++ss) { 677 var shaderInfo = shaderInfos[ss]; 678 var tests = params.tests; 679 var testTypes = params.emuFuncs || (params.bvecTest ? bvecTypes : types); 680 // Test vertex shaders 681 for (var ii = 0; ii < tests.length; ++ii) { 682 var type = testTypes[ii]; 683 var isVertex = (ss == 0); 684 debug(""); 685 var str = replaceParams(params.testFunc, { 686 func: params.feature, 687 type: type.type, 688 arg0: type.type 689 }); 690 var passMsg = "Testing: " + str + " in " + shaderInfo.type + " shader"; 691 debug(passMsg); 692 693 var referenceVertexShaderSource = generateReferenceShader( 694 shaderInfo, 695 shaderInfo.vertexShaderTemplate, 696 params, 697 type, 698 tests[ii].source); 699 var referenceFragmentShaderSource = generateReferenceShader( 700 shaderInfo, 701 shaderInfo.fragmentShaderTemplate, 702 params, 703 type, 704 tests[ii].source); 705 var testVertexShaderSource = generateTestShader( 706 shaderInfo, 707 shaderInfo.vertexShaderTemplate, 708 params, 709 tests[ii].source); 710 var testFragmentShaderSource = generateTestShader( 711 shaderInfo, 712 shaderInfo.fragmentShaderTemplate, 713 params, 714 tests[ii].source); 715 var referenceTextureOrArray = generateReferenceImage( 716 gl, 717 tests[ii].generator, 718 isVertex ? gridRes : width, 719 isVertex ? gridRes : height, 720 isVertex); 721 722 debug(""); 723 var testVertexShader = wtu.loadShader(gl, testVertexShaderSource, gl.VERTEX_SHADER, testFailed, true); 724 var testFragmentShader = wtu.loadShader(gl, testFragmentShaderSource, gl.FRAGMENT_SHADER, testFailed, true); 725 debug(""); 726 727 728 if (parseInt(wtu.getUrlOptions().dumpShaders)) { 729 var vRefInfo = { 730 shader: referenceVertexShader, 731 shaderSuccess: true, 732 label: "reference vertex shader", 733 source: referenceVertexShaderSource 734 }; 735 var fRefInfo = { 736 shader: referenceFragmentShader, 737 shaderSuccess: true, 738 label: "reference fragment shader", 739 source: referenceFragmentShaderSource 740 }; 741 wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vRefInfo, fRefInfo); 742 743 var vTestInfo = { 744 shader: testVertexShader, 745 shaderSuccess: true, 746 label: "test vertex shader", 747 source: testVertexShaderSource 748 }; 749 var fTestInfo = { 750 shader: testFragmentShader, 751 shaderSuccess: true, 752 label: "test fragment shader", 753 source: testFragmentShaderSource 754 }; 755 wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vTestInfo, fTestInfo); 756 } 757 758 var refData; 759 if (isVertex) { 760 refData = drawVertexReferenceImage(referenceTextureOrArray); 761 } else { 762 refData = drawFragmentReferenceImage(referenceTextureOrArray); 763 } 764 var refImg = wtu.makeImageFromCanvas(canvas); 765 var testData; 766 if (isVertex) { 767 var referenceFragmentShader = wtu.loadShader(gl, referenceFragmentShaderSource, gl.FRAGMENT_SHADER, testFailed); 768 testData = draw( 769 testVertexShader, referenceFragmentShader); 770 } else { 771 var referenceVertexShader = wtu.loadShader(gl, referenceVertexShaderSource, gl.VERTEX_SHADER, testFailed); 772 testData = draw( 773 referenceVertexShader, testFragmentShader); 774 } 775 var testImg = wtu.makeImageFromCanvas(canvas); 776 var testTolerance = shaderInfo.tolerance; 777 // Provide per-test tolerance so that we can increase it only for those desired. 778 if ('tolerance' in tests[ii]) 779 testTolerance = tests[ii].tolerance || 0; 780 _reportResults(refData, refImg, testData, testImg, testTolerance, 781 width, height, ctx, imgData, wtu, canvas2d, consoleDiv); 782 } 783 } 784 785 finishTest(); 786 787 function draw(vertexShader, fragmentShader) { 788 var program = wtu.createProgram(gl, vertexShader, fragmentShader, testFailed); 789 790 var posLoc = gl.getAttribLocation(program, "aPosition"); 791 wtu.setupIndexedQuad(gl, gridRes, posLoc); 792 793 gl.useProgram(program); 794 wtu.clearAndDrawIndexedQuad(gl, gridRes, [0, 0, 255, 255]); 795 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); 796 797 var img = new Uint8Array(width * height * 4); 798 gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); 799 return img; 800 } 801 802 function drawVertexReferenceImage(colors) { 803 gl.bindBuffer(gl.ARRAY_BUFFER, indexedQuadForReferenceVertexShader[0]); 804 gl.enableVertexAttribArray(0); 805 gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0); 806 gl.bindBuffer(gl.ARRAY_BUFFER, referenceVertexShaderColorBuffer); 807 gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); 808 gl.enableVertexAttribArray(1); 809 gl.vertexAttribPointer(1, 4, gl.UNSIGNED_BYTE, true, 0, 0); 810 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexedQuadForReferenceVertexShader[1]); 811 gl.useProgram(referenceVertexShaderProgram); 812 wtu.clearAndDrawIndexedQuad(gl, gridRes); 813 gl.disableVertexAttribArray(0); 814 gl.disableVertexAttribArray(1); 815 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); 816 817 var img = new Uint8Array(width * height * 4); 818 gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); 819 return img; 820 } 821 822 function drawFragmentReferenceImage(texture) { 823 var program = wtu.setupTexturedQuad(gl); 824 825 gl.activeTexture(gl.TEXTURE0); 826 gl.bindTexture(gl.TEXTURE_2D, texture); 827 var texLoc = gl.getUniformLocation(program, "tex"); 828 gl.uniform1i(texLoc, 0); 829 wtu.clearAndDrawUnitQuad(gl); 830 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); 831 832 var img = new Uint8Array(width * height * 4); 833 gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); 834 return img; 835 } 836 837 /** 838 * Creates and returns either a Uint8Array (for vertex shaders) or 839 * WebGLTexture (for fragment shaders) containing the reference 840 * image for the function being tested. Exactly how the function is 841 * evaluated, and the size of the returned texture or array, depends on 842 * whether we are testing a vertex or fragment shader. If a fragment 843 * shader, the function is evaluated at the pixel centers. If a 844 * vertex shader, the function is evaluated at the triangle's 845 * vertices. 846 * 847 * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use to generate texture objects. 848 * @param {!function(number,number,number,number): !Array.<number>} generator The reference image generator function. 849 * @param {number} width The width of the texture to generate if testing a fragment shader; the grid resolution if testing a vertex shader. 850 * @param {number} height The height of the texture to generate if testing a fragment shader; the grid resolution if testing a vertex shader. 851 * @param {boolean} isVertex True if generating a reference image for a vertex shader; false if for a fragment shader. 852 * @return {!WebGLTexture|!Uint8Array} The texture object or array that was generated. 853 */ 854 function generateReferenceImage( 855 gl, 856 generator, 857 width, 858 height, 859 isVertex) { 860 861 // Note: the math in this function must match that in the vertex and 862 // fragment shader templates above. 863 function computeTexCoord(x) { 864 return x * 0.5 + 0.5; 865 } 866 867 function computeVertexColor(texCoordX, texCoordY) { 868 return [ texCoordX, 869 texCoordY, 870 texCoordX * texCoordY, 871 (1.0 - texCoordX) * texCoordY * 0.5 + 0.5 ]; 872 } 873 874 /** 875 * Computes fragment color according to the algorithm used for interpolation 876 * in OpenGL (GLES 2.0 spec 3.5.1, OpenGL 4.3 spec 14.6.1). 877 */ 878 function computeInterpolatedColor(texCoordX, texCoordY) { 879 // Calculate grid line indexes below and to the left from texCoord. 880 var gridBottom = Math.floor(texCoordY * gridRes); 881 if (gridBottom == gridRes) { 882 --gridBottom; 883 } 884 var gridLeft = Math.floor(texCoordX * gridRes); 885 if (gridLeft == gridRes) { 886 --gridLeft; 887 } 888 889 // Calculate coordinates relative to the grid cell. 890 var cellX = texCoordX * gridRes - gridLeft; 891 var cellY = texCoordY * gridRes - gridBottom; 892 893 // Barycentric coordinates inside either triangle ACD or ABC 894 // are used as weights for the vertex colors in the corners: 895 // A--B 896 // |\ | 897 // | \| 898 // D--C 899 900 var aColor = computeVertexColor(gridLeft / gridRes, (gridBottom + 1) / gridRes); 901 var bColor = computeVertexColor((gridLeft + 1) / gridRes, (gridBottom + 1) / gridRes); 902 var cColor = computeVertexColor((gridLeft + 1) / gridRes, gridBottom / gridRes); 903 var dColor = computeVertexColor(gridLeft / gridRes, gridBottom / gridRes); 904 905 // Calculate weights. 906 var a, b, c, d; 907 908 if (cellX + cellY < 1) { 909 // In bottom triangle ACD. 910 a = cellY; // area of triangle C-D-(cellX, cellY) relative to ACD 911 c = cellX; // area of triangle D-A-(cellX, cellY) relative to ACD 912 d = 1 - a - c; 913 b = 0; 914 } else { 915 // In top triangle ABC. 916 a = 1 - cellX; // area of the triangle B-C-(cellX, cellY) relative to ABC 917 c = 1 - cellY; // area of the triangle A-B-(cellX, cellY) relative to ABC 918 b = 1 - a - c; 919 d = 0; 920 } 921 922 var interpolated = []; 923 for (var ii = 0; ii < aColor.length; ++ii) { 924 interpolated.push(a * aColor[ii] + b * bColor[ii] + c * cColor[ii] + d * dColor[ii]); 925 } 926 return interpolated; 927 } 928 929 function clamp(value, minVal, maxVal) { 930 return Math.max(minVal, Math.min(value, maxVal)); 931 } 932 933 // Evaluates the function at clip coordinates (px,py), storing the 934 // result in the array "pixel". Each channel's result is clamped 935 // between 0 and 255. 936 function evaluateAtClipCoords(px, py, pixel, colorFunc) { 937 var tcx = computeTexCoord(px); 938 var tcy = computeTexCoord(py); 939 940 var color = colorFunc(tcx, tcy); 941 942 var output = generator(color[0], color[1], color[2], color[3]); 943 944 // Multiply by 256 to get even distribution for all values between 0 and 1. 945 // Use rounding rather than truncation to more closely match the GPU's behavior. 946 pixel[0] = clamp(Math.round(256 * output[0]), 0, 255); 947 pixel[1] = clamp(Math.round(256 * output[1]), 0, 255); 948 pixel[2] = clamp(Math.round(256 * output[2]), 0, 255); 949 pixel[3] = clamp(Math.round(256 * output[3]), 0, 255); 950 } 951 952 function generateFragmentReference() { 953 var data = new Uint8Array(4 * width * height); 954 955 var horizTexel = 1.0 / width; 956 var vertTexel = 1.0 / height; 957 var halfHorizTexel = 0.5 * horizTexel; 958 var halfVertTexel = 0.5 * vertTexel; 959 960 var pixel = new Array(4); 961 962 for (var yi = 0; yi < height; ++yi) { 963 for (var xi = 0; xi < width; ++xi) { 964 // The function must be evaluated at pixel centers. 965 966 // Compute desired position in clip space 967 var px = -1.0 + 2.0 * (halfHorizTexel + xi * horizTexel); 968 var py = -1.0 + 2.0 * (halfVertTexel + yi * vertTexel); 969 970 evaluateAtClipCoords(px, py, pixel, computeInterpolatedColor); 971 var index = 4 * (width * yi + xi); 972 data[index + 0] = pixel[0]; 973 data[index + 1] = pixel[1]; 974 data[index + 2] = pixel[2]; 975 data[index + 3] = pixel[3]; 976 } 977 } 978 979 var texture = gl.createTexture(); 980 gl.bindTexture(gl.TEXTURE_2D, texture); 981 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 982 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 983 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 984 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 985 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, 986 gl.RGBA, gl.UNSIGNED_BYTE, data); 987 return texture; 988 } 989 990 function generateVertexReference() { 991 // We generate a Uint8Array which contains the evaluation of the 992 // function at the vertices of the triangle mesh. It is expected 993 // that the width and the height are identical, and equivalent 994 // to the grid resolution. 995 if (width != height) { 996 throw "width and height must be equal"; 997 } 998 999 var texSize = 1 + width; 1000 var data = new Uint8Array(4 * texSize * texSize); 1001 1002 var step = 2.0 / width; 1003 1004 var pixel = new Array(4); 1005 1006 for (var yi = 0; yi < texSize; ++yi) { 1007 for (var xi = 0; xi < texSize; ++xi) { 1008 // The function is evaluated at the triangles' vertices. 1009 1010 // Compute desired position in clip space 1011 var px = -1.0 + (xi * step); 1012 var py = -1.0 + (yi * step); 1013 1014 evaluateAtClipCoords(px, py, pixel, computeVertexColor); 1015 var index = 4 * (texSize * yi + xi); 1016 data[index + 0] = pixel[0]; 1017 data[index + 1] = pixel[1]; 1018 data[index + 2] = pixel[2]; 1019 data[index + 3] = pixel[3]; 1020 } 1021 } 1022 1023 return data; 1024 } 1025 1026 //---------------------------------------------------------------------- 1027 // Body of generateReferenceImage 1028 // 1029 1030 if (isVertex) { 1031 return generateVertexReference(); 1032 } else { 1033 return generateFragmentReference(); 1034 } 1035 } 1036 }; 1037 1038 return { 1039 /** 1040 * runs a bunch of GLSL tests using the passed in parameters 1041 * The parameters are: 1042 * 1043 * feature: 1044 * the name of the function being tested (eg, sin, dot, 1045 * normalize) 1046 * 1047 * testFunc: 1048 * The prototype of function to be tested not including the 1049 * return type. 1050 * 1051 * emuFunc: 1052 * A base function that can be used to generate emulation 1053 * functions. Example for 'ceil' 1054 * 1055 * float $(func)_base(float value) { 1056 * float m = mod(value, 1.0); 1057 * return m != 0.0 ? (value + 1.0 - m) : value; 1058 * } 1059 * 1060 * args: 1061 * The arguments to the function 1062 * 1063 * baseArgs: (optional) 1064 * The arguments when a base function is used to create an 1065 * emulation function. For example 'float sign_base(float v)' 1066 * is used to implemenent vec2 sign_emu(vec2 v). 1067 * 1068 * simpleEmu: 1069 * if supplied, the code that can be used to generate all 1070 * functions for all types. 1071 * 1072 * Example for 'normalize': 1073 * 1074 * $(type) $(func)_emu($(args)) { 1075 * return value / length(value); 1076 * } 1077 * 1078 * gridRes: (optional) 1079 * The resolution of the mesh to generate. The default is a 1080 * 1x1 grid but many vertex shaders need a higher resolution 1081 * otherwise the only values passed in are the 4 corners 1082 * which often have the same value. 1083 * 1084 * tests: 1085 * The code for each test. It is assumed the tests are for 1086 * float, vec2, vec3, vec4 in that order. 1087 * 1088 * tolerance: (optional) 1089 * Allow some tolerance in the comparisons. The tolerance is applied to 1090 * both vertex and fragment shaders. The default tolerance is 0, meaning 1091 * the values have to be identical. 1092 * 1093 * fragmentTolerance: (optional) 1094 * Specify a tolerance which only applies to fragment shaders. The 1095 * fragment-only tolerance will override the shared tolerance for 1096 * fragment shaders if both are specified. Fragment shaders usually 1097 * use mediump float precision so they sometimes require higher tolerance 1098 * than vertex shaders which use highp by default. 1099 */ 1100 runFeatureTest: runFeatureTest, 1101 1102 /* 1103 * Runs a bunch of GLSL tests using the passed in parameters 1104 * 1105 * The parameters are: 1106 * 1107 * tests: 1108 * Array of tests. For each test the following parameters are expected 1109 * 1110 * name: 1111 * some description of the test 1112 * reference: 1113 * parameters for the reference shader (see below) 1114 * test: 1115 * parameters for the test shader (see below) 1116 * 1117 * The parameter for the reference and test shaders are 1118 * 1119 * shader: the GLSL for the shader 1120 * subs: any substitutions you wish to define for the shader. 1121 * 1122 * Each shader is created from a basic template that 1123 * defines an input and an output. You can see the 1124 * templates at the top of this file. The input and output 1125 * change depending on whether or not we are generating 1126 * a vertex or fragment shader. 1127 * 1128 * All this code function does is a bunch of string substitutions. 1129 * A substitution is defined by $(name). If name is found in 1130 * the 'subs' parameter it is replaced. 4 special names exist. 1131 * 1132 * 'input' the input to your GLSL. Always a vec4. All change 1133 * from 0 to 1 over the quad to be drawn. 1134 * 1135 * 'output' the output color. Also a vec4 1136 * 1137 * 'emu' a place to insert extra stuff 1138 * 'extra' a place to insert extra stuff. 1139 * 1140 * You can think of the templates like this 1141 * 1142 * $(extra) 1143 * $(emu) 1144 * 1145 * void main() { 1146 * // do math to calculate input 1147 * ... 1148 * 1149 * $(shader) 1150 * } 1151 * 1152 * Your shader first has any subs you provided applied as well 1153 * as 'input' and 'output' 1154 * 1155 * It is then inserted into the template which is also provided 1156 * with your subs. 1157 * 1158 * gridRes: (optional) 1159 * The resolution of the mesh to generate. The default is a 1160 * 1x1 grid but many vertex shaders need a higher resolution 1161 * otherwise the only values passed in are the 4 corners 1162 * which often have the same value. 1163 * 1164 * tolerance: (optional) 1165 * Allow some tolerance in the comparisons. The tolerance is applied to 1166 * both vertex and fragment shaders. The default tolerance is 0, meaning 1167 * the values have to be identical. 1168 * 1169 * fragmentTolerance: (optional) 1170 * Specify a tolerance which only applies to fragment shaders. The 1171 * fragment-only tolerance will override the shared tolerance for 1172 * fragment shaders if both are specified. Fragment shaders usually 1173 * use mediump float precision so they sometimes require higher tolerance 1174 * than vertex shaders which use highp. 1175 */ 1176 runBasicTest: runBasicTest, 1177 1178 /** 1179 * Runs a bunch of GLSL tests using the passed in parameters. The 1180 * expected results are computed as a reference image in JavaScript 1181 * instead of on the GPU. The parameters are: 1182 * 1183 * feature: 1184 * the name of the function being tested (eg, sin, dot, 1185 * normalize) 1186 * 1187 * testFunc: 1188 * The prototype of function to be tested not including the 1189 * return type. 1190 * 1191 * args: 1192 * The arguments to the function 1193 * 1194 * gridRes: (optional) 1195 * The resolution of the mesh to generate. The default is a 1196 * 1x1 grid but many vertex shaders need a higher resolution 1197 * otherwise the only values passed in are the 4 corners 1198 * which often have the same value. 1199 * 1200 * tests: 1201 * Array of tests. It is assumed the tests are for float, vec2, 1202 * vec3, vec4 in that order. For each test the following 1203 * parameters are expected: 1204 * 1205 * source: the GLSL source code for the tests 1206 * 1207 * generator: a JavaScript function taking four parameters 1208 * which evaluates the same function as the GLSL source, 1209 * returning its result as a newly allocated array. 1210 * 1211 * tolerance: (optional) a per-test tolerance. 1212 * 1213 * extra: (optional) 1214 * Extra GLSL code inserted at the top of each test's shader. 1215 * 1216 * tolerance: (optional) 1217 * Allow some tolerance in the comparisons. The tolerance is applied to 1218 * both vertex and fragment shaders. The default tolerance is 0, meaning 1219 * the values have to be identical. 1220 * 1221 * fragmentTolerance: (optional) 1222 * Specify a tolerance which only applies to fragment shaders. The 1223 * fragment-only tolerance will override the shared tolerance for 1224 * fragment shaders if both are specified. Fragment shaders usually 1225 * use mediump float precision so they sometimes require higher tolerance 1226 * than vertex shaders which use highp. 1227 */ 1228 runReferenceImageTest: runReferenceImageTest, 1229 1230 none: false 1231 }; 1232 1233 }());