Backed out changeset 8fc3326bce7f (bug 1943032) for causing failures at browser_tab_g...
[gecko.git] / dom / canvas / test / webgl-conf / checkout / resources / glsl-generator.js
blobd0b65bcb4b87015cb114b9fb269320c7ea585a03
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() {
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");
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");
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");
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");
71 var baseFragmentShader = [
72 "precision mediump float;",
73 "varying vec4 vColor;",
74 "",
75 "void main()",
76 "{",
77 " gl_FragColor = vColor;",
78 "}"
79 ].join("\n");
81 var types = [
82 { type: "float",
83 code: [
84 "float $(func)_emu($(args)) {",
85 " return $(func)_base($(baseArgs));",
86 "}"].join("\n")
88 { type: "vec2",
89 code: [
90 "vec2 $(func)_emu($(args)) {",
91 " return vec2(",
92 " $(func)_base($(baseArgsX)),",
93 " $(func)_base($(baseArgsY)));",
94 "}"].join("\n")
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")
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")
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")
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")
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")
147 var replaceRE = /\$\((\w+)\)/g;
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];
157 throw "unknown string param '" + p1 + "'";
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;
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: ""});
180 test = replaceParams(test, {
181 input: input,
182 output: output,
183 func: feature + "_emu"
185 emuFunc = replaceParams(emuFunc, {
186 func: feature
188 args = replaceParams(args, {
189 type: type
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
201 var shader = replaceParams(template, {
202 extra: extra,
203 emu: emuFunc + "\n\n" + typeCode,
204 test: test
206 return shader;
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 || '';
217 test = replaceParams(test, {
218 input: input,
219 output: output,
220 func: feature
222 var shader = replaceParams(template, {
223 extra: extra,
224 emu: '',
225 test: test
227 return shader;
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;
260 imgData.data[imgOffset] = 255;
261 same = false;
266 var diffImg = null;
267 if (!same) {
268 ctx.putImageData(imgData, 0, 0);
269 diffImg = wtu.makeImageFromCanvas(canvas2d);
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);
279 div.appendChild(document.createElement('br'));
281 consoleDiv.appendChild(div);
283 if (!same) {
284 testFailed("images are different" + (firstFailure ? firstFailure : ""));
285 } else {
286 testPassed("images are the same");
289 consoleDiv.appendChild(document.createElement('hr'));
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;
300 description("Testing GLSL feature: " + params.feature);
302 var width = 32;
303 var height = 32;
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;
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);
322 var shaderInfos = [
323 { type: "vertex",
324 input: "color",
325 output: "vColor",
326 vertexShaderTemplate: vertexShaderTemplate,
327 fragmentShaderTemplate: baseFragmentShader,
328 tolerance: vertexTolerance
330 { type: "fragment",
331 input: "vColor",
332 output: "gl_FragColor",
333 vertexShaderTemplate: baseVertexShader,
334 fragmentShaderTemplate: fragmentShaderTemplate,
335 tolerance: fragmentTolerance
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
351 debug("");
352 var str = replaceParams(params.testFunc, {
353 func: params.feature,
354 type: type.type,
355 arg0: type.type
357 var passMsg = "Testing: " + str + " in " + shaderInfo.type + " shader";
358 debug(passMsg);
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]);
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("");
391 if (parseInt(wtu.getUrlOptions().dumpShaders)) {
392 var vRefInfo = {
393 shader: referenceVertexShader,
394 shaderSuccess: true,
395 label: "reference vertex shader",
396 source: referenceVertexShaderSource
398 var fRefInfo = {
399 shader: referenceFragmentShader,
400 shaderSuccess: true,
401 label: "reference fragment shader",
402 source: referenceFragmentShaderSource
404 wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vRefInfo, fRefInfo);
406 var vTestInfo = {
407 shader: testVertexShader,
408 shaderSuccess: true,
409 label: "test vertex shader",
410 source: testVertexShaderSource
412 var fTestInfo = {
413 shader: testFragmentShader,
414 shaderSuccess: true,
415 label: "test fragment shader",
416 source: testFragmentShaderSource
418 wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vTestInfo, fTestInfo);
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);
431 var testImg = wtu.makeImageFromCanvas(canvas);
433 _reportResults(refData, refImg, testData, testImg, shaderInfo.tolerance,
434 width, height, ctx, imgData, wtu, canvas2d, consoleDiv);
438 finishTest();
440 function draw(vertexShader, fragmentShader) {
441 var program = wtu.createProgram(gl, vertexShader, fragmentShader, testFailed);
443 var posLoc = gl.getAttribLocation(program, "aPosition");
444 wtu.setupIndexedQuad(gl, gridRes, posLoc);
446 gl.useProgram(program);
447 wtu.clearAndDrawIndexedQuad(gl, gridRes, [0, 0, 255, 255]);
448 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw");
450 var img = new Uint8Array(width * height * 4);
451 gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img);
452 return img;
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;
465 description("Testing : " + document.getElementsByTagName("title")[0].innerText);
467 var width = 32;
468 var height = 32;
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;
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);
487 var shaderInfos = [
488 { type: "vertex",
489 input: "color",
490 output: "vColor",
491 vertexShaderTemplate: vertexShaderTemplate,
492 fragmentShaderTemplate: baseFragmentShader,
493 tolerance: vertexTolerance
495 { type: "fragment",
496 input: "vColor",
497 output: "gl_FragColor",
498 vertexShaderTemplate: baseVertexShader,
499 fragmentShaderTemplate: fragmentShaderTemplate,
500 tolerance: fragmentTolerance
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);
514 function genShader(shaderInfo, template, shader, subs) {
515 shader = replaceParams(shader, subs, {
516 input: shaderInfo.input,
517 output: shaderInfo.output
519 shader = replaceParams(template, subs, {
520 test: shader,
521 emu: "",
522 extra: ""
524 return shader;
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);
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("");
555 if (parseInt(wtu.getUrlOptions().dumpShaders)) {
556 var vRefInfo = {
557 shader: referenceVertexShader,
558 shaderSuccess: true,
559 label: "reference vertex shader",
560 source: referenceVertexShaderSource
562 var fRefInfo = {
563 shader: referenceFragmentShader,
564 shaderSuccess: true,
565 label: "reference fragment shader",
566 source: referenceFragmentShaderSource
568 wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vRefInfo, fRefInfo);
570 var vTestInfo = {
571 shader: testVertexShader,
572 shaderSuccess: true,
573 label: "test vertex shader",
574 source: testVertexShaderSource
576 var fTestInfo = {
577 shader: testFragmentShader,
578 shaderSuccess: true,
579 label: "test fragment shader",
580 source: testFragmentShaderSource
582 wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vTestInfo, fTestInfo);
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);
592 var testImg = wtu.makeImageFromCanvas(canvas);
594 _reportResults(refData, refImg, testData, testImg, shaderInfo.tolerance,
595 width, height, ctx, imgData, wtu, canvas2d, consoleDiv);
599 finishTest();
601 function draw(vertexShader, fragmentShader) {
602 var program = wtu.createProgram(gl, vertexShader, fragmentShader, testFailed);
604 var posLoc = gl.getAttribLocation(program, "aPosition");
605 wtu.setupIndexedQuad(gl, gridRes, posLoc);
607 gl.useProgram(program);
608 wtu.clearAndDrawIndexedQuad(gl, gridRes, [0, 0, 255, 255]);
609 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw");
611 var img = new Uint8Array(width * height * 4);
612 gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img);
613 return img;
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;
626 description("Testing GLSL feature: " + params.feature);
628 var width = 32;
629 var height = 32;
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;
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);
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.
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();
660 var shaderInfos = [
661 { type: "vertex",
662 input: "color",
663 output: "vColor",
664 vertexShaderTemplate: vertexShaderTemplate,
665 fragmentShaderTemplate: baseFragmentShader,
666 tolerance: vertexTolerance
668 { type: "fragment",
669 input: "vColor",
670 output: "gl_FragColor",
671 vertexShaderTemplate: baseVertexShader,
672 fragmentShaderTemplate: fragmentShaderTemplate,
673 tolerance: fragmentTolerance
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
690 var passMsg = "Testing: " + str + " in " + shaderInfo.type + " shader";
691 debug(passMsg);
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(
717 tests[ii].generator,
718 isVertex ? gridRes : width,
719 isVertex ? gridRes : height,
720 isVertex);
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("");
728 if (parseInt(wtu.getUrlOptions().dumpShaders)) {
729 var vRefInfo = {
730 shader: referenceVertexShader,
731 shaderSuccess: true,
732 label: "reference vertex shader",
733 source: referenceVertexShaderSource
735 var fRefInfo = {
736 shader: referenceFragmentShader,
737 shaderSuccess: true,
738 label: "reference fragment shader",
739 source: referenceFragmentShaderSource
741 wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vRefInfo, fRefInfo);
743 var vTestInfo = {
744 shader: testVertexShader,
745 shaderSuccess: true,
746 label: "test vertex shader",
747 source: testVertexShaderSource
749 var fTestInfo = {
750 shader: testFragmentShader,
751 shaderSuccess: true,
752 label: "test fragment shader",
753 source: testFragmentShaderSource
755 wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vTestInfo, fTestInfo);
758 var refData;
759 if (isVertex) {
760 refData = drawVertexReferenceImage(referenceTextureOrArray);
761 } else {
762 refData = drawFragmentReferenceImage(referenceTextureOrArray);
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);
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);
785 finishTest();
787 function draw(vertexShader, fragmentShader) {
788 var program = wtu.createProgram(gl, vertexShader, fragmentShader, testFailed);
790 var posLoc = gl.getAttribLocation(program, "aPosition");
791 wtu.setupIndexedQuad(gl, gridRes, posLoc);
793 gl.useProgram(program);
794 wtu.clearAndDrawIndexedQuad(gl, gridRes, [0, 0, 255, 255]);
795 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw");
797 var img = new Uint8Array(width * height * 4);
798 gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img);
799 return img;
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");
817 var img = new Uint8Array(width * height * 4);
818 gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img);
819 return img;
822 function drawFragmentReferenceImage(texture) {
823 var program = wtu.setupTexturedQuad(gl);
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");
832 var img = new Uint8Array(width * height * 4);
833 gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img);
834 return img;
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.
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.
854 function generateReferenceImage(
856 generator,
857 width,
858 height,
859 isVertex) {
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;
867 function computeVertexColor(texCoordX, texCoordY) {
868 return [ texCoordX,
869 texCoordY,
870 texCoordX * texCoordY,
871 (1.0 - texCoordX) * texCoordY * 0.5 + 0.5 ];
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).
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;
884 var gridLeft = Math.floor(texCoordX * gridRes);
885 if (gridLeft == gridRes) {
886 --gridLeft;
889 // Calculate coordinates relative to the grid cell.
890 var cellX = texCoordX * gridRes - gridLeft;
891 var cellY = texCoordY * gridRes - gridBottom;
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
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);
905 // Calculate weights.
906 var a, b, c, d;
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;
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]);
926 return interpolated;
929 function clamp(value, minVal, maxVal) {
930 return Math.max(minVal, Math.min(value, maxVal));
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);
940 var color = colorFunc(tcx, tcy);
942 var output = generator(color[0], color[1], color[2], color[3]);
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);
952 function generateFragmentReference() {
953 var data = new Uint8Array(4 * width * height);
955 var horizTexel = 1.0 / width;
956 var vertTexel = 1.0 / height;
957 var halfHorizTexel = 0.5 * horizTexel;
958 var halfVertTexel = 0.5 * vertTexel;
960 var pixel = new Array(4);
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.
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);
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];
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;
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";
999 var texSize = 1 + width;
1000 var data = new Uint8Array(4 * texSize * texSize);
1002 var step = 2.0 / width;
1004 var pixel = new Array(4);
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.
1010 // Compute desired position in clip space
1011 var px = -1.0 + (xi * step);
1012 var py = -1.0 + (yi * step);
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];
1023 return data;
1026 //----------------------------------------------------------------------
1027 // Body of generateReferenceImage
1030 if (isVertex) {
1031 return generateVertexReference();
1032 } else {
1033 return generateFragmentReference();
1038 return {
1040 * runs a bunch of GLSL tests using the passed in parameters
1041 * The parameters are:
1043 * feature:
1044 * the name of the function being tested (eg, sin, dot,
1045 * normalize)
1047 * testFunc:
1048 * The prototype of function to be tested not including the
1049 * return type.
1051 * emuFunc:
1052 * A base function that can be used to generate emulation
1053 * functions. Example for 'ceil'
1055 * float $(func)_base(float value) {
1056 * float m = mod(value, 1.0);
1057 * return m != 0.0 ? (value + 1.0 - m) : value;
1060 * args:
1061 * The arguments to the function
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).
1068 * simpleEmu:
1069 * if supplied, the code that can be used to generate all
1070 * functions for all types.
1072 * Example for 'normalize':
1074 * $(type) $(func)_emu($(args)) {
1075 * return value / length(value);
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.
1084 * tests:
1085 * The code for each test. It is assumed the tests are for
1086 * float, vec2, vec3, vec4 in that order.
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.
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.
1100 runFeatureTest: runFeatureTest,
1103 * Runs a bunch of GLSL tests using the passed in parameters
1105 * The parameters are:
1107 * tests:
1108 * Array of tests. For each test the following parameters are expected
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)
1117 * The parameter for the reference and test shaders are
1119 * shader: the GLSL for the shader
1120 * subs: any substitutions you wish to define for the shader.
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.
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.
1132 * 'input' the input to your GLSL. Always a vec4. All change
1133 * from 0 to 1 over the quad to be drawn.
1135 * 'output' the output color. Also a vec4
1137 * 'emu' a place to insert extra stuff
1138 * 'extra' a place to insert extra stuff.
1140 * You can think of the templates like this
1142 * $(extra)
1143 * $(emu)
1145 * void main() {
1146 * // do math to calculate input
1147 * ...
1149 * $(shader)
1152 * Your shader first has any subs you provided applied as well
1153 * as 'input' and 'output'
1155 * It is then inserted into the template which is also provided
1156 * with your subs.
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.
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.
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.
1176 runBasicTest: runBasicTest,
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:
1183 * feature:
1184 * the name of the function being tested (eg, sin, dot,
1185 * normalize)
1187 * testFunc:
1188 * The prototype of function to be tested not including the
1189 * return type.
1191 * args:
1192 * The arguments to the function
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.
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:
1205 * source: the GLSL source code for the tests
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.
1211 * tolerance: (optional) a per-test tolerance.
1213 * extra: (optional)
1214 * Extra GLSL code inserted at the top of each test's shader.
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.
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.
1228 runReferenceImageTest: runReferenceImageTest,
1230 none: false
1233 }());