summaryrefslogtreecommitdiffstats
path: root/dom/canvas/test/webgl-conf/checkout/js/tests/no-over-optimizations-on-uniform-array.js
blob: 1202b7868cddece634cd61da6963593605bfd48d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/*
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.
*/
NoOverOptimizeOnUniformArrayTester = (function(){

var vshader = [
    "attribute vec4 a_position;",
    "void main()",
    "{",
    "    gl_Position = a_position;",
    "}"
].join('\n');

var fshader_max = [
    "precision mediump float;",
    "uniform vec4 colora[$(maxUniformVectors)];",
    "void main()",
    "{",
    "    gl_FragColor = vec4(colora[$(usedUniformVector)]);",
    "}"
].join('\n');

var fshader_max_ab_ab = [
    "precision mediump float;",
    "uniform vec4 $(decl1);",
    "uniform vec4 $(decl2);",
    "void main()",
    "{",
    "gl_FragColor = vec4($(usage1) + $(usage2));",
    "}"
].join('\n');

// MaxInt32 is 2^32-1. We need +1 of that to test overflow conditions
var MaxInt32PlusOne = 4294967296;

function setupTests(gl) {
    var tests = [];
    var maxUniformVectors = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);

    // This test is to test drivers the have bugs related to optimizing
    // an array of uniforms when only 1 of those uniforms is used.
    tests.push({
        desc: "using last element",
        maxUniformVectors: maxUniformVectors,
        usedUniformVector: maxUniformVectors - 1,
        shader: "fshader-max",
        color: [0, 1, 0, 1],
        arrayName: "colora",
        extraName: "colorb",
    });
    tests.push({
        desc: "using first element",
        maxUniformVectors: maxUniformVectors,
        usedUniformVector: 0,
        shader: "fshader-max",
        color: [0, 1, 0, 1],
        arrayName: "colora",
        extraName: "colorb",
    });

    // Generate test shaders. We're trying to force the driver to
    // overflow from 1 array into the next if it optimizes. So for example if it was C
    //
    //   int big[4];
    //   int little[1];
    //   big[5] = 124;
    //
    // Would end up setting little[0] instead of big. Some drivers optimize
    // where if you only use say 'big[3]' it will actually only allocate just 1 element
    // for big.
    //
    // But, some drivers have a bug where the fact that they optimized big to 1 element
    // does not get passed down to glUniform so when setting the uniform 'big[3]' they
    // overwrite memory.
    //
    // If the driver crashes, yea. We found a bug. We can block the driver.
    // Otherwise we try various combinations so that setting 'little[0]' first
    // and then setting all elements of 'big' we hope it will overwrite 'little[0]'
    // which will show the bug and again we can block the driver.
    //
    // We don't know how the driver will order, in memory, the various uniforms
    // or for that matter we don't even know if they will be contiguous in memory
    // but to hopefully expose any bugs we try various combinations.
    //
    //    It could be the compiler orders uniforms alphabetically.
    //    It could be it orders them in order of declaration.
    //    It could be it orders them in order of usage.
    //
    // We also test using only first element of big or just the last element of big.
    //
    for (var nameOrder = 0; nameOrder < 2; ++nameOrder) {
        var name1 = nameOrder ? "colora" : "colorb";
        var name2 = nameOrder ? "colorb" : "colora";
        for (var last = 0; last < 2; ++last) {
            var usedUniformVector = last ? maxUniformVectors - 2 : 0;
            for (var declOrder = 0; declOrder < 2; ++declOrder) {
                var bigName    = declOrder ? name1 : name2;
                var littleName = declOrder ? name2 : name1;
                var decl1 = bigName + "[" + (maxUniformVectors - 1) + "]";
                var decl2 = littleName + "[1]";
                if (declOrder) {
                    var t = decl1;
                    decl1 = decl2;
                    decl2 = t;
                }
                for (var usageOrder = 0; usageOrder < 2; ++usageOrder) {
                    var usage1 = bigName + "[" + usedUniformVector + "]";
                    var usage2 = littleName + "[0]";
                    if (usageOrder) {
                        var t = usage1;
                        usage1 = usage2;
                        usage2 = t;
                    }
                    var fSrc = wtu.replaceParams(fshader_max_ab_ab, {
                        decl1: decl1,
                        decl2: decl2,
                        usage1: usage1,
                        usage2: usage2,
                    });
                    var desc = "testing: " + name1 + ":" + name2 + " using " + (last ? "last" : "first") +
                        " creating uniforms " + decl1 + " " + decl2 + " and accessing " + usage1 + " " + usage2;
                    tests.push({
                        desc: desc,
                        maxUniformVectors: maxUniformVectors - 1,
                        usedUniformVector: usedUniformVector,
                        source: fSrc,
                        color: [0, 0, 0, 1],
                        arrayName: bigName,
                        extraName: littleName,
                    });
                }
            }
        }
    }
    return tests;
};

function testUniformOptimizationIssues(test) {
    debug("");
    debug(test.desc);
    var fshader = test.source;
    if (!fshader) {
        fshader = wtu.replaceParams(fshader_max, test);
    }

    var consoleElem = document.getElementById("console");
    wtu.addShaderSource(
        consoleElem, "vertex shader", vshader);
    wtu.addShaderSource(
        consoleElem, "fragment shader", fshader);

    var program = wtu.loadProgram(gl, vshader, fshader);
    gl.useProgram(program);

    var colorbLocation = gl.getUniformLocation(program, test.extraName + "[0]");
    if (colorbLocation) {
        gl.uniform4fv(colorbLocation, [0, 1, 0, 0]);
    }

    // Ensure that requesting an array uniform past MaxInt32PlusOne returns no uniform
    var nameMaxInt32PlusOne = test.arrayName + "[" + (test.usedUniformVector + MaxInt32PlusOne) + "]";
    assertMsg(gl.getUniformLocation(program, nameMaxInt32PlusOne) === null,
        "Requesting " + nameMaxInt32PlusOne + " uniform should return a null uniform location");

    // Set just the used uniform
    var name = test.arrayName + "[" + test.usedUniformVector + "]";
    var uniformLocation = gl.getUniformLocation(program, name);
    gl.uniform4fv(uniformLocation, test.color);
    wtu.setupIndexedQuad(gl, 1);
    wtu.clearAndDrawIndexedQuad(gl, 1);
    wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");

    // Set all the unused uniforms
    var locations = [];
    var allRequiredUniformLocationsQueryable = true;
    for (var ii = 0; ii < test.maxUniformVectors; ++ii) {
        var name = test.arrayName + "[" + ii + "]";
        var uniformLocation = gl.getUniformLocation(program, name);
        locations.push(uniformLocation);
        if (ii == test.usedUniformVector) {
            continue;
        }
        // Locations > usedUnformVector may not exist.
        // Locations <= usedUniformVector MUST exist.
        if (ii <= test.usedUniformVector && (uniformLocation === undefined || uniformLocation === null)) {
            allRequiredUniformLocationsQueryable = false;
        }
        gl.uniform4fv(uniformLocation, [1, 0, 0, 1]);
    }
    if (allRequiredUniformLocationsQueryable) {
        testPassed("allRequiredUniformLocationsQueryable is true.");
    }
    else {
        testFailed("allRequiredUniformLocationsQueryable should be true. Was false.");
    }
    var positionLoc = gl.getAttribLocation(program, "a_position");
    wtu.setupIndexedQuad(gl, 1, positionLoc);
    wtu.clearAndDrawIndexedQuad(gl, 1);
    wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");

    // Check we can read & write each uniform.
    // Note: uniforms past test.usedUniformVector might not exist.
    for (var ii = 0; ii < test.maxUniformVectors; ++ii) {
        gl.uniform4fv(locations[ii], [ii + 4, ii + 2, ii + 3, ii + 1]);
    }

    var kEpsilon = 0.01;
    var isSame = function(v1, v2) {
        return Math.abs(v1 - v2) < kEpsilon;
    };

    for (var ii = 0; ii < test.maxUniformVectors; ++ii) {
        var location = locations[ii];
        if (location) {
            var value = gl.getUniform(program, locations[ii]);
            if (!isSame(value[0], ii + 4) ||
                !isSame(value[1], ii + 2) ||
                !isSame(value[2], ii + 3) ||
                !isSame(value[3], ii + 1)) {
                testFailed("location: " + ii + " was not correct value");
                break;
            }
        }
    }
}

function runOneTest(gl, test) {
    testUniformOptimizationIssues(test);
};

function runTests(gl, tests) {
    debug("");
    debug("Test drivers don't over optimize unused array elements");

    for (var ii = 0; ii < tests.length; ++ii) {
        runOneTest(gl, tests[ii]);
    }
};

return {
    setupTests : setupTests,
    runTests : runTests
};

}());