<!--
Copyright (c) 2019 The Khronos Group Inc.
Use of this source code is governed by an MIT-style license that can be
found in the LICENSE.txt file.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../../resources/js-test-style.css"/>
<script src="../../js/js-test-pre.js"></script>
<script src="../../js/webgl-test-utils.js"></script>
<title>WebGL Matrix Attribute Conformance Test</title>
</head>
<body>
<div id="description"></div>
<div id="console"></div>
<canvas id="canvas" width="8" height="8" style="width: 8px; height: 8px;"></canvas>
<script>
"use strict";
description("This tests ensures that matrix attribute locations do not clash with other shader attributes.");

var wtu = WebGLTestUtils;
var canvas = document.getElementById("canvas");
var gl = wtu.create3DContext(canvas, {antialias: false});

// Make sure we have room for at least a mat4.
var maxAttributes = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
debug('MAX_VERTEX_ATTRIBUTES is ' + maxAttributes);
shouldBeGreaterThanOrEqual('maxAttributes', '4');

var glFragmentShader = wtu.loadShader(gl, wtu.simpleColorFragmentShader, gl.FRAGMENT_SHADER);

// prepareMatrixProgram creates a program with glFragmentShader as the fragment shader.
// The vertex shader has numVector number of vectors and a matrix with numMatrixDimensions
// dimensions at location numMatrixPosition in the list of attributes.
// Ensures that every vector and matrix is used by the program.
// Returns a valid program on successfull link; null on link failure.
function prepareMatrixProgram(numVectors, numMatrixDimensions, numMatrixPosition) {
    // Add the matrix and vector attribute declarations. Declare the vectors
    // to have the same number of components as the matrix so we can perform
    // operations on them when we assign to gl_Position later on.
    var strVertexShader = "";
    for (var ii = 1; ii <= numVectors; ++ii) {
        if (numMatrixPosition === ii) {
            strVertexShader += "attribute mat" + numMatrixDimensions + " matrix;\n";
        }
        strVertexShader += "attribute vec" + numMatrixDimensions + " vec_" + ii + ";\n";
    }
    // numMatrixPosition will be one past numVectors if the caller wants it to be
    // last. Hence, we need this check outside the loop as well as inside.
    if (numMatrixPosition === ii) {
        strVertexShader += "attribute mat" + numMatrixDimensions + " matrix;\n";
    }
    // Add the body of the shader. Add up all of the vectors and multiply by the matrix.
    // The operations we perform do not matter. We just need to ensure that all the vector and
    // matrix attributes are used.
    strVertexShader +=  "void main(void) { \ngl_Position = vec4((";
    for (var ii = 1; ii <= numVectors; ++ii) {
        if (ii > 1) {
            strVertexShader += "+"
        }
        strVertexShader += "vec_" + ii;
    }
    strVertexShader += ")*matrix";
    // Ensure the vec4 has the correct number of dimensions in order to be assignable
    // to gl_Position.
    for (var ii = numMatrixDimensions; ii < 4; ++ii) {
        strVertexShader += ",0.0";
    }
    strVertexShader += ");}\n";
    // Load the shader, attach it to a program, and return the link results
    var glVertexShader = wtu.loadShader(gl, strVertexShader, gl.VERTEX_SHADER);
    var strTest = 'Load shader with ' + numVectors + ' vectors and 1 matrix';
    if (glVertexShader !== null) {
        testPassed(strTest);

        var glProgram = gl.createProgram();
        gl.attachShader(glProgram, glVertexShader);
        gl.attachShader(glProgram, glFragmentShader);
        gl.linkProgram(glProgram);
        if (gl.getProgramParameter(glProgram, gl.LINK_STATUS)) {
            wtu.glErrorShouldBe(gl, gl.NO_ERROR, 'linkProgram');
            return glProgram;
        }
    } else {
        testFailed(strTest);
    }
    return null;
}

debug('');

// Test mat2, mat3 and mat4.
for (var mm = 2; mm <= 4; ++mm) {
    // Add maxAttribute number of attributes by saving enough room in the attribute
    // list for a matrix of mm dimensions. All of the other attribute slots will be
    // filled with vectors.
    var numVectors = maxAttributes - mm;
    for (var pp = 1; pp <= numVectors + 1; ++pp) {
        debug('Test ' + mm + ' dimensional matrix at position ' + pp);
        var glProgram = prepareMatrixProgram(numVectors, /*numMatrixDimensions*/mm, /*numMatrixPosition*/pp);
        shouldBeNonNull('glProgram');
        var attribMatrix = gl.getAttribLocation(glProgram, 'matrix');
        debug('Matrix is at attribute location ' + attribMatrix);
        shouldBeTrue('attribMatrix > -1');
        // Per the spec, when an attribute is a matrix attribute, getAttribLocation
        // returns the index of the first component of the matrix. The implementation must
        // leave sufficient room for all the components. Here we ensure none of the vectors
        // in the shader are assigned attribute locations that belong to the matrix.
        for (var vv = 1; vv <= numVectors; ++vv) {
            var strVector = 'vec_' + vv
            var attribVector = gl.getAttribLocation(glProgram, strVector);
            debug(strVector + ' is at attribute location ' + attribVector);
            // Begin with the first attribute location where the matrix begins and ensure
            // the vector's attribute location is not assigned to the matrix. Loop until
            // we've checked all of the attribute locations that belong to the matrix.
            for (var ii = attribMatrix; ii < attribMatrix + mm; ++ii) {
                var testStr = strVector + ' attribute location: ' + attribVector + '.  Should not be ' + ii;
                if (attribVector !== ii) {
                    testPassed(testStr);
                } else {
                    testFailed(testStr);
                }
            }
        }
        debug('');
    }
    debug('');
}

var successfullyParsed = true;
</script>
<script src="../../js/js-test-post.js"></script>
</body>
</html>
