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.
6 OpenGLESTestRunner = (function(){
7 var wtu = WebGLTestUtils;
10 var HALF_GRID_MAX_SIZE = 32;
16 var GTFPIXELTOLERANCE = 24;
17 var GTFACCEPTABLEFAILURECONT = 10;
18 var GTFAMDPIXELTOLERANCE = 12;
19 var GTFSCORETOLERANCE = 0.65;
20 var GTFNCCTOLARANCEZERO = 0.25;
21 var GTFKERNALSIZE = 5;
27 function compareImages(refData, tstData, width, height, diff) {
28 function isPixelSame(offset) {
29 // First do simple check
30 if (Math.abs(refData[offset + 0] - tstData[offset + 0]) <= GTFPIXELTOLERANCE &&
31 Math.abs(refData[offset + 1] - tstData[offset + 1]) <= GTFPIXELTOLERANCE &&
32 Math.abs(refData[offset + 2] - tstData[offset + 2]) <= GTFPIXELTOLERANCE) {
36 // TODO: Implement crazy check that's used in OpenGL ES 2.0 conformance tests.
37 // NOTE: on Desktop things seem to be working. Maybe the more complex check
38 // is needed for embedded systems?
43 for (var yy = 0; yy < height; ++yy) {
44 for (var xx = 0; xx < width; ++xx) {
45 var offset = (yy * width + xx) * 4;
46 var diffOffset = ((height - yy - 1) * width + xx) * 4;
47 diff[diffOffset + 0] = 0;
48 diff[diffOffset + 1] = 0;
49 diff[diffOffset + 2] = 0;
50 diff[diffOffset + 3] = 255;
51 if (!isPixelSame(offset)) {
52 diff[diffOffset] = 255;
55 testFailed("pixel @ (" + xx + ", " + yy + " was [" +
56 tstData[offset + 0] + "," +
57 tstData[offset + 1] + "," +
58 tstData[offset + 2] + "," +
59 tstData[offset + 3] + "] expected [" +
60 refData[offset + 0] + "," +
61 refData[offset + 1] + "," +
62 refData[offset + 2] + "," +
63 refData[offset + 3] + "]")
71 function persp(fovy, aspect, n, f) {
73 var rad = fovy / 2.0 * 3.14159265 / 180;
75 var s = Math.sin(rad);
76 if (dz == 0 || s == 0 || aspect == 0)
79 var cot = Math.cos(rad) / s;
104 function setAttribs(attribs, buffers) {
105 for (var name in attribs) {
106 var buffer = buffers[name];
108 testFailed("no buffer for attrib:" + name);
111 var loc = attribs[name];
112 log("setup attrib: " + loc + " as " + name);
113 var buf = gl.createBuffer();
114 gl.bindBuffer(gl.ARRAY_BUFFER, buf);
115 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(buffer.data), gl.STATIC_DRAW);
116 gl.enableVertexAttribArray(loc);
117 gl.vertexAttribPointer(loc, buffer.numComponents, gl.FLOAT, false, 0, 0);
121 function drawSquare(attribs) {
141 "gtf_SecondaryColor": {
159 "gtf_MultiTexCoord0": {
178 setAttribs(attribs, buffers);
179 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
182 function drawFrontBackSquare(attribs) {
202 "gtf_MultiTexCoord0": {
212 setAttribs(attribs, front);
213 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
234 "gtf_MultiTexCoord0": {
244 setAttribs(attribs, back);
245 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
248 function drawGrid(attribs, width, height) {
249 var n = Math.min(Math.floor(Math.max(width, height) / 4), HALF_GRID_MAX_SIZE);
251 var numVertices = (n + n) * (n + n) * 6;
253 var gridVertices = [];
255 var gridSecColors = [];
256 var gridNormals = [];
257 var gridFogCoords = [];
258 var gridTexCoords0 = [];
260 var currentVertex = 0;
261 var currentColor = 0;
262 var currentSecColor = 0;
263 var currentTexCoord0 = 0;
264 var currentNormal = 0;
265 var currentFogCoord = 0;
268 for(var i = -n; i < n; ++i)
271 var x2 = (i + 1) / n;
272 for(var j = -n; j < n; ++j)
275 var y2 = (j + 1) / n;
278 gridVertices[currentVertex++] = x1;
279 gridVertices[currentVertex++] = y1;
280 gridVertices[currentVertex++] = z;
281 gridColors[currentColor++] = 1.0 - (x1 + y1 + 2.0) / 4.0;
282 gridColors[currentColor++] = (x1 + 1.0) / 2.0;
283 gridColors[currentColor++] = (y1 + 1.0) / 2.0;
284 gridSecColors[currentSecColor++] = 1.0 - (x2 + y2 + 2.0) / 4.0;
285 gridSecColors[currentSecColor++] = (x2 + 1.0) / 2.0;
286 gridSecColors[currentSecColor++] = (y2 + 1.0) / 2.0;
287 gridTexCoords0[currentTexCoord0++] = (x1 + 1.0) / 2.0;
288 gridTexCoords0[currentTexCoord0++] = (y1 + 1.0) / 2.0;
289 gridNormals[currentNormal++] = 1.0 - (x2 + y2 + 2.0) / 4.0;
290 gridNormals[currentNormal++] = (x2 + 1.0) / 2.0;
291 gridNormals[currentNormal++] = (y2 + 1.0) / 2.0;
292 gridFogCoords[currentFogCoord++] = (y1 + 1.0) / 2.0;
295 gridVertices[currentVertex++] = x2;
296 gridVertices[currentVertex++] = y1;
297 gridVertices[currentVertex++] = z;
298 gridColors[currentColor++] = 1.0 - (x2 + y1 + 2.0) / 4.0;
299 gridColors[currentColor++] = (x2 + 1.0) / 2.0;
300 gridColors[currentColor++] = (y1 + 1.0) / 2.0;
301 gridSecColors[currentSecColor++] = 1.0 - (x1 + y2 + 2.0) / 4.0;
302 gridSecColors[currentSecColor++] = (x1 + 1.0) / 2.0;
303 gridSecColors[currentSecColor++] = (y2 + 1.0) / 2.0;
304 gridTexCoords0[currentTexCoord0++] = (x2 + 1.0) / 2.0;
305 gridTexCoords0[currentTexCoord0++] = (y1 + 1.0) / 2.0;
306 gridNormals[currentNormal++] = 1.0 - (x1 + y2 + 2.0) / 4.0;
307 gridNormals[currentNormal++] = (x1 + 1.0) / 2.0;
308 gridNormals[currentNormal++] = (y2 + 1.0) / 2.0;
309 gridFogCoords[currentFogCoord++] = (y1 + 1.0) / 2.0;
312 gridVertices[currentVertex++] = x2;
313 gridVertices[currentVertex++] = y2;
314 gridVertices[currentVertex++] = z;
315 gridColors[currentColor++] = 1.0 - (x2 + y2 + 2.0) / 4.0;
316 gridColors[currentColor++] = (x2 + 1.0) / 2.0;
317 gridColors[currentColor++] = (y2 + 1.0) / 2.0;
318 gridSecColors[currentSecColor++] = 1.0 - (x1 + y1 + 2.0) / 4.0;
319 gridSecColors[currentSecColor++] = (x1 + 1.0) / 2.0;
320 gridSecColors[currentSecColor++] = (y1 + 1.0) / 2.0;
321 gridTexCoords0[currentTexCoord0++] = (x2 + 1.0) / 2.0;
322 gridTexCoords0[currentTexCoord0++] = (y2 + 1.0) / 2.0;
323 gridNormals[currentNormal++] = 1.0 - (x1 + y1 + 2.0) / 4.0;
324 gridNormals[currentNormal++] = (x1 + 1.0) / 2.0;
325 gridNormals[currentNormal++] = (y1 + 1.0) / 2.0;
326 gridFogCoords[currentFogCoord++] = (y2 + 1.0) / 2.0;
329 gridVertices[currentVertex++] = x2;
330 gridVertices[currentVertex++] = y2;
331 gridVertices[currentVertex++] = z;
332 gridColors[currentColor++] = 1.0 - (x2 + y2 + 2.0) / 4.0;
333 gridColors[currentColor++] = (x2 + 1.0) / 2.0;
334 gridColors[currentColor++] = (y2 + 1.0) / 2.0;
335 gridSecColors[currentSecColor++] = 1.0 - (x1 + y1 + 2.0) / 4.0;
336 gridSecColors[currentSecColor++] = (x1 + 1.0) / 2.0;
337 gridSecColors[currentSecColor++] = (y1 + 1.0) / 2.0;
338 gridTexCoords0[currentTexCoord0++] = (x2 + 1.0) / 2.0;
339 gridTexCoords0[currentTexCoord0++] = (y2 + 1.0) / 2.0;
340 gridNormals[currentNormal++] = 1.0 - (x1 + y1 + 2.0) / 4.0;
341 gridNormals[currentNormal++] = (x1 + 1.0) / 2.0;
342 gridNormals[currentNormal++] = (y1 + 1.0) / 2.0;
343 gridFogCoords[currentFogCoord++] = (y2 + 1.0) / 2.0;
346 gridVertices[currentVertex++] = x1;
347 gridVertices[currentVertex++] = y2;
348 gridVertices[currentVertex++] = z;
349 gridColors[currentColor++] = 1.0 - (x1 + y2 + 2.0) / 4.0;
350 gridColors[currentColor++] = (x1 + 1.0) / 2.0;
351 gridColors[currentColor++] = (y2 + 1.0) / 2.0;
352 gridSecColors[currentSecColor++] = 1.0 - (x2 + y1 + 2.0) / 4.0;
353 gridSecColors[currentSecColor++] = (x2 + 1.0) / 2.0;
354 gridSecColors[currentSecColor++] = (y1 + 1.0) / 2.0;
355 gridTexCoords0[currentTexCoord0++] = (x1 + 1.0) / 2.0;
356 gridTexCoords0[currentTexCoord0++] = (y2 + 1.0) / 2.0;
357 gridNormals[currentNormal++] = 1.0 - (x2 + y1 + 2.0) / 4.0;
358 gridNormals[currentNormal++] = (x2 + 1.0) / 2.0;
359 gridNormals[currentNormal++] = (y1 + 1.0) / 2.0;
360 gridFogCoords[currentFogCoord++] = (y2 + 1.0) / 2.0;
363 gridVertices[currentVertex++] = x1;
364 gridVertices[currentVertex++] = y1;
365 gridVertices[currentVertex++] = z;
366 gridColors[currentColor++] = 1.0 - (x1 + y1 + 2.0) / 4.0;
367 gridColors[currentColor++] = (x1 + 1.0) / 2.0;
368 gridColors[currentColor++] = (y1 + 1.0) / 2.0;
369 gridSecColors[currentSecColor++] = 1.0 - (x2 + y2 + 2.0) / 4.0;
370 gridSecColors[currentSecColor++] = (x2 + 1.0) / 2.0;
371 gridSecColors[currentSecColor++] = (y2 + 1.0) / 2.0;
372 gridTexCoords0[currentTexCoord0++] = (x1 + 1.0) / 2.0;
373 gridTexCoords0[currentTexCoord0++] = (y1 + 1.0) / 2.0;
374 gridNormals[currentNormal++] = 1.0 - (x2 + y2 + 2.0) / 4.0;
375 gridNormals[currentNormal++] = (x2 + 1.0) / 2.0;
376 gridNormals[currentNormal++] = (y2 + 1.0) / 2.0;
377 gridFogCoords[currentFogCoord++] = (y1 + 1.0) / 2.0;
382 "gtf_Vertex": { data: gridVertices, numComponents: 3 },
383 "gtf_Color": { data: gridColors, numComponents: 3 },
384 "gtf_SecondaryColor": { data: gridSecColors, numComponents: 3 },
385 "gtf_Normal": { data: gridNormals, numComponents: 3 },
386 "gtf_FogCoord": { data: gridFogCoords, numComponents: 1 },
387 "gtf_MultiTexCoord0": { data: gridTexCoords0, numComponents: 2 }
389 setAttribs(attribs, buffers);
390 gl.drawArrays(gl.TRIANGLES, 0, numVertices);
395 frontbacksquare: drawFrontBackSquare,
399 function drawWithProgram(program, programInfo, test) {
400 gl.useProgram(program);
403 var numAttribs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
404 for (var ii = 0; ii < numAttribs; ++ii) {
405 var info = gl.getActiveAttrib(program, ii);
406 var name = info.name;
407 var location = gl.getAttribLocation(program, name);
408 attribs[name] = location;
410 if (KNOWN_ATTRIBS.indexOf(name) < 0) {
411 testFailed("unknown attrib:" + name)
416 var numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
417 for (var ii = 0; ii < numUniforms; ++ii) {
418 var info = gl.getActiveUniform(program, ii);
419 var name = info.name;
420 if (name.match(/\[0\]$/)) {
421 name = name.substr(0, name.length - 3);
423 var location = gl.getUniformLocation(program, name);
424 uniforms[name] = {location: location};
427 var getUniformLocation = function(name) {
428 var uniform = uniforms[name];
431 return uniform.location;
436 // Set known uniforms
437 var loc = getUniformLocation("gtf_ModelViewProjectionMatrix");
442 persp(60, 1, 1, 30));
444 var loc = getUniformLocation("viewportwidth");
446 gl.uniform1f(loc, gl.canvas.width);
448 var loc = getUniformLocation("viewportheight");
450 gl.uniform1f(loc, gl.canvas.height);
453 // Set test specific uniforms
454 for (var name in programInfo.uniforms) {
455 var location = getUniformLocation(name);
459 var uniform = programInfo.uniforms[name];
460 var type = uniform.type;
461 var value = uniform.value;
462 var transpose = uniform.transpose;
463 if (transpose !== undefined) {
464 log("gl." + type + '("' + name + '", ' + transpose + ", " + value + ")");
465 gl[type](location, transpose, value);
466 } else if (!type.match("v$")) {
467 var args = [location];
468 for (var ii = 0; ii < value.length; ++ii) {
469 args.push(value[ii]);
471 gl[type].apply(gl, args);
472 log("gl." + type + '("' + name + '", ' + args.slice(1) + ")");
474 log("gl." + type + '("' + name + '", ' + value + ")");
475 gl[type](location, value);
477 var err = gl.getError();
478 if (err != gl.NO_ERROR) {
479 testFailed(wtu.glEnumToString(gl, err) + " generated setting uniform: " + name);
483 // Filter out specified built-in uniforms
484 if (programInfo.builtin_uniforms) {
485 var num_builtins_found = 0;
486 var valid_values = programInfo.builtin_uniforms.valid_values;
487 for (var index in valid_values) {
488 var uniform = uniforms[valid_values[index]];
490 ++num_builtins_found;
491 uniform.builtin = true;
495 var min_required = programInfo.builtin_uniforms.min_required;
496 if (num_builtins_found < min_required) {
497 testFailed("only found " + num_builtins_found + " of " + min_required +
498 " required built-in uniforms: " + valid_values);
502 // Check for unset uniforms
503 for (var name in uniforms) {
504 var uniform = uniforms[name];
505 if (!uniform.used && !uniform.builtin) {
506 testFailed("uniform " + name + " never set");
511 for (var state in test.state) {
512 var fields = test.state[state];
515 gl.depthRange(fields.near, fields.far);
518 testFailed("unknown state: " + state)
522 gl.clearColor(0, 0, 0, 0);
523 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
525 var model = test.model || "square";
526 var fn = MODEL_FUNCS[model];
528 testFailed("unknown model type: " + model)
530 log("draw as: " + model)
531 fn(attribs, gl.canvas.width, gl.canvas.height);
534 var pixels = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);
535 gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
537 width: gl.canvas.width,
538 height: gl.canvas.height,
540 img: wtu.makeImageFromCanvas(gl.canvas)
544 function runProgram(programInfo, test, label, callback) {
549 function loadShader(path, type, index) {
550 wtu.loadTextFileAsync(path, function(success, text) {
551 addShader(success, text, type, path, index);
555 function addShader(success, text, type, path, index) {
558 testFailed("could not load: " + path);
560 var shader = wtu.loadShader(gl, text, type);
561 shaders.push(shader);
562 source[index] = text;
566 if (shaders.length == 2) {
569 var consoleDiv = document.getElementById("console");
570 wtu.addShaderSources(
571 gl, consoleDiv, label + " vertex shader", shaders[0], source[0],
572 programInfo.vertexShader);
573 wtu.addShaderSources(
574 gl, consoleDiv, label + " fragment shader", shaders[1], source[1],
575 programInfo.fragmentShader);
577 var program = wtu.createProgram(gl, shaders[0], shaders[1]);
578 result = drawWithProgram(program, programInfo, test);
584 loadShader(programInfo.vertexShader, gl.VERTEX_SHADER, 0);
585 loadShader(programInfo.fragmentShader, gl.FRAGMENT_SHADER, 1);
588 function compareResults(expected, actual) {
589 var width = expected.width;
590 var height = expected.height;
591 var canvas = document.createElement("canvas");
592 canvas.width = width;
593 canvas.height = height;
594 var ctx = canvas.getContext("2d");
595 var imgData = ctx.getImageData(0, 0, width, height);
598 var expData = expected.pixels;
599 var actData = actual.pixels;
601 var same = compareImages(expData, actData, width, height, imgData.data);
603 var console = document.getElementById("console");
606 ctx.putImageData(imgData, 0, 0);
607 diffImg = wtu.makeImageFromCanvas(canvas);
611 var div = document.createElement("div");
612 div.className = "testimages";
613 wtu.insertImage(div, "reference", expected.img);
614 wtu.insertImage(div, "test", actual.img);
616 wtu.insertImage(div, "diff", diffImg);
618 div.appendChild(document.createElement('br'));
620 console.appendChild(div);
624 testFailed("images are different");
626 testPassed("images are the same");
630 console.appendChild(document.createElement('hr'));
633 function runCompareTest(test, callback) {
635 debug("test: " + test.name);
639 function storeResults(index) {
640 return function(result) {
641 results[index] = result;
644 compareResults(results[0], results[1]);
645 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
651 runProgram(test.referenceProgram, test, "reference", storeResults(0));
652 runProgram(test.testProgram, test, "test", storeResults(1));
655 function runBuildTest(test, callback) {
657 debug("test: " + test.name);
659 var shaders = [null, null];
660 var source = ["",""];
661 var success = [undefined, undefined];
664 function loadShader(path, type, index) {
665 if (path == "empty") {
666 shaders[index] = gl.createShader();
667 success[index] = true;
668 source[index] = "/* empty */";
671 wtu.loadTextFileAsync(path, function(loadSuccess, text) {
673 success[index] = false;
674 source[index] = "/* could not load */";
675 testFailed("could not load:" + path);
677 source[index] = text;
678 shaders[index] = wtu.loadShader(gl, text, type, function(index) {
679 return function(msg) {
680 success[index] = false
683 if (success[index] === undefined) {
684 success[index] = true;
692 function attachAndLink() {
697 var c = document.getElementById("console");
699 c, "vertex shader", source[0], test.testProgram.vertexShader);
700 debug("compile: " + (success[0] ? "success" : "fail"));
702 c, "fragment shader", source[1], test.testProgram.fragmentShader);
703 debug("compile: " + (success[1] ? "success" : "fail"));
705 compileSuccess = (success[0] && success[1]);
706 if (!test.compstat) {
707 if (compileSuccess) {
708 testFailed("expected compile failure but was successful");
710 testPassed("expected compile failure and it failed");
713 if (compileSuccess) {
714 testPassed("expected compile success and it was successful");
716 testFailed("expected compile success but it failed");
718 var linkSuccess = true;
719 var program = wtu.createProgram(gl, shaders[0], shaders[1], function() {
722 if (linkSuccess !== test.linkstat) {
723 testFailed("expected link to " + (test.linkstat ? "succeed" : "fail"));
725 testPassed("shaders compiled and linked as expected.");
732 loadShader(test.testProgram.vertexShader, gl.VERTEX_SHADER, 0);
733 loadShader(test.testProgram.fragmentShader, gl.FRAGMENT_SHADER, 1);
737 compare: runCompareTest,
740 dummy: null // just here to mark the end
743 function LogGLCall(functionName, args) {
744 console.log("gl." + functionName + "(" +
745 WebGLDebugUtils.glFunctionArgsToString(functionName, args) + ")");
748 // Runs the tests async since they will load shaders.
752 var canvas = document.getElementById("example");
753 gl = wtu.create3DContext(canvas);
754 if (window.WebGLDebugUtils) {
755 gl = WebGLDebugUtils.makeDebugContext(gl, undefined, LogGLCall);
758 testFailed("context does not exist");
763 if (gl.canvas.width != 500 || gl.canvas.height != 500) {
764 testFailed("canvas must be 500x500 pixels: Several shaders are hard coded to this size.");
767 var tests = obj.tests;
770 function runNextTest() {
771 if (ndx < tests.length) {
772 var test = tests[ndx++];
773 var fn = testPatterns[test.pattern];
775 testFailed("test pattern: " + test.pattern + " not supoprted")
778 fn(test, runNextTest);