1 //
2 // Copyright 2015 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // Some of the pointsprite tests below were ported from Khronos WebGL
7 // conformance test suite.
8 
9 #include "test_utils/ANGLETest.h"
10 
11 #include <cmath>
12 
13 using namespace angle;
14 
15 class PointSpritesTest : public ANGLETest
16 {
17   protected:
18     const int windowWidth = 256;
19     const int windowHeight = 256;
PointSpritesTest()20     PointSpritesTest()
21     {
22         setWindowWidth(windowWidth);
23         setWindowHeight(windowHeight);
24         setConfigRedBits(8);
25         setConfigGreenBits(8);
26         setConfigBlueBits(8);
27         setConfigAlphaBits(8);
28     }
29 
SetUp()30     virtual void SetUp()
31     {
32         ANGLETest::SetUp();
33     }
34 
s2p(float s)35     float s2p(float s)
36     {
37         return (s + 1.0f) * 0.5f * (GLfloat)windowWidth;
38     }
39 };
40 
41 // Checks gl_PointCoord and gl_PointSize
42 // https://www.khronos.org/registry/webgl/sdk/tests/conformance/glsl/variables/gl-pointcoord.html
TEST_P(PointSpritesTest,PointCoordAndPointSizeCompliance)43 TEST_P(PointSpritesTest, PointCoordAndPointSizeCompliance)
44 {
45     // TODO(jmadill): figure out why this fails
46     if (IsIntel() && GetParam() == ES2_D3D9())
47     {
48         std::cout << "Test skipped on Intel due to failures." << std::endl;
49         return;
50     }
51 
52     const std::string fs = SHADER_SOURCE
53     (
54         precision mediump float;
55         void main()
56         {
57             gl_FragColor = vec4(
58                 gl_PointCoord.x,
59                 gl_PointCoord.y,
60                 0,
61                 1);
62         }
63     );
64 
65     const std::string vs = SHADER_SOURCE
66     (
67         attribute vec4 vPosition;
68         uniform float uPointSize;
69         void main()
70         {
71             gl_PointSize = uPointSize;
72             gl_Position = vPosition;
73         }
74     );
75 
76     GLuint program = CompileProgram(vs, fs);
77     ASSERT_NE(program, 0u);
78     ASSERT_GL_NO_ERROR();
79 
80     glUseProgram(program);
81 
82     GLfloat pointSizeRange[2] = {};
83     glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);
84 
85     GLfloat maxPointSize = pointSizeRange[1];
86 
87     ASSERT_TRUE(maxPointSize >= 1);
88     maxPointSize = floorf(maxPointSize);
89     ASSERT_TRUE((int)maxPointSize % 1 == 0);
90 
91     maxPointSize = std::min(maxPointSize, 64.0f);
92     GLfloat pointWidth = maxPointSize / windowWidth;
93     GLint step = static_cast<GLint>(floorf(maxPointSize / 4));
94     GLint pointStep = std::max<GLint>(1, step);
95 
96     GLint pointSizeLoc = glGetUniformLocation(program, "uPointSize");
97     ASSERT_GL_NO_ERROR();
98 
99     glUniform1f(pointSizeLoc, maxPointSize);
100     ASSERT_GL_NO_ERROR();
101 
102     GLfloat pixelOffset = ((int)maxPointSize % 2) ? (1.0f / (GLfloat)windowWidth) : 0;
103     GLuint vertexObject = 0;
104     glGenBuffers(1, &vertexObject);
105     ASSERT_NE(vertexObject, 0U);
106     ASSERT_GL_NO_ERROR();
107 
108     glBindBuffer(GL_ARRAY_BUFFER, vertexObject);
109     ASSERT_GL_NO_ERROR();
110 
111     GLfloat thePoints[] = { -0.5f + pixelOffset, -0.5f + pixelOffset,
112                              0.5f + pixelOffset, -0.5f + pixelOffset,
113                             -0.5f + pixelOffset,  0.5f + pixelOffset,
114                              0.5f + pixelOffset,  0.5f + pixelOffset };
115 
116     glBufferData(GL_ARRAY_BUFFER, sizeof(thePoints), thePoints, GL_STATIC_DRAW);
117     ASSERT_GL_NO_ERROR();
118 
119     glEnableVertexAttribArray(0);
120     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
121 
122     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
123 
124     glDrawArrays(GL_POINTS, 0, 4);
125     ASSERT_GL_NO_ERROR();
126 
127     glDeleteBuffers(1, &vertexObject);
128 
129     std::string debugText;
130     for (float py = 0; py < 2; ++py) {
131         for (float px = 0; px < 2; ++px) {
132             float pointX = -0.5f + px + pixelOffset;
133             float pointY = -0.5f + py + pixelOffset;
134             for (int yy = 0; yy < maxPointSize; yy += pointStep) {
135                 for (int xx = 0; xx < maxPointSize; xx += pointStep) {
136                     // formula for s and t from OpenGL ES 2.0 spec section 3.3
137                     float xw = s2p(pointX);
138                     float yw = s2p(pointY);
139                     float u = xx / maxPointSize * 2 - 1;
140                     float v = yy / maxPointSize * 2 - 1;
141                     int xf = static_cast<int>(floorf(s2p(pointX + u * pointWidth)));
142                     int yf = static_cast<int>(floorf(s2p(pointY + v * pointWidth)));
143                     float s = 0.5f + (xf + 0.5f - xw) / maxPointSize;
144                     float t = 0.5f + (yf + 0.5f - yw) / maxPointSize;
145                     GLubyte color[4] = { static_cast<GLubyte>(floorf(s * 255)), static_cast<GLubyte>(floorf((1 - t) * 255)), 0, 255 };
146                     EXPECT_PIXEL_NEAR(xf, yf, color[0], color[1], color[2], color[3], 4);
147                 }
148             }
149         }
150     }
151 }
152 
153 // Verify that drawing a point without enabling any attributes succeeds
154 // https://www.khronos.org/registry/webgl/sdk/tests/conformance/rendering/point-no-attributes.html
TEST_P(PointSpritesTest,PointWithoutAttributesCompliance)155 TEST_P(PointSpritesTest, PointWithoutAttributesCompliance)
156 {
157     // clang-format off
158     const std::string fs = SHADER_SOURCE
159     (
160         precision mediump float;
161         void main()
162         {
163             gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
164         }
165     );
166 
167     const std::string vs = SHADER_SOURCE
168     (
169         void main()
170         {
171             gl_PointSize = 2.0;
172             gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
173         }
174     );
175     // clang-format on
176 
177     GLuint program = CompileProgram(vs, fs);
178     ASSERT_NE(program, 0u);
179     ASSERT_GL_NO_ERROR();
180 
181     glUseProgram(program);
182 
183     glDrawArrays(GL_POINTS, 0, 1);
184     ASSERT_GL_NO_ERROR();
185 
186     // expect the center pixel to be green
187     EXPECT_PIXEL_EQ((windowWidth - 1) / 2, (windowHeight - 1) / 2, 0, 255, 0, 255);
188 }
189 
190 // This is a regression test for a graphics driver bug affecting end caps on roads in MapsGL
191 // https://www.khronos.org/registry/webgl/sdk/tests/conformance/rendering/point-with-gl-pointcoord-in-fragment-shader.html
TEST_P(PointSpritesTest,PointCoordRegressionTest)192 TEST_P(PointSpritesTest, PointCoordRegressionTest)
193 {
194     const std::string fs = SHADER_SOURCE
195     (
196         precision mediump float;
197         varying vec4 v_color;
198         void main()
199         {
200             // It seems as long as this mathematical expression references
201             // gl_PointCoord, the fragment's color is incorrect.
202             vec2 diff = gl_PointCoord - vec2(.5, .5);
203             if (length(diff) > 0.5)
204                 discard;
205 
206             // The point should be a solid color.
207             gl_FragColor = v_color;
208         }
209     );
210 
211     const std::string vs = SHADER_SOURCE
212     (
213         varying vec4 v_color;
214         // The X and Y coordinates of the center of the point.
215         attribute vec2 a_vertex;
216         uniform float u_pointSize;
217         void main()
218         {
219             gl_PointSize = u_pointSize;
220             gl_Position = vec4(a_vertex, 0.0, 1.0);
221             // The color of the point.
222             v_color = vec4(0.0, 1.0, 0.0, 1.0);
223         }
224     );
225 
226     GLuint program = CompileProgram(vs, fs);
227     ASSERT_NE(program, 0u);
228     ASSERT_GL_NO_ERROR();
229 
230     glUseProgram(program);
231 
232     GLfloat pointSizeRange[2] = {};
233     glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);
234 
235     GLfloat maxPointSize = pointSizeRange[1];
236 
237     ASSERT_TRUE(maxPointSize > 2);
238 
239     glClearColor(0, 0, 0, 1);
240     glDisable(GL_DEPTH_TEST);
241     glClear(GL_COLOR_BUFFER_BIT);
242 
243     GLint pointSizeLoc = glGetUniformLocation(program, "u_pointSize");
244     ASSERT_GL_NO_ERROR();
245 
246     GLfloat pointSize = std::min<GLfloat>(20.0f, maxPointSize);
247     glUniform1f(pointSizeLoc, pointSize);
248     ASSERT_GL_NO_ERROR();
249 
250     GLuint vertexObject = 0;
251     glGenBuffers(1, &vertexObject);
252     ASSERT_NE(vertexObject, 0U);
253     ASSERT_GL_NO_ERROR();
254 
255     glBindBuffer(GL_ARRAY_BUFFER, vertexObject);
256     ASSERT_GL_NO_ERROR();
257 
258     GLfloat thePoints[] = { 0.0f, 0.0f };
259 
260     glBufferData(GL_ARRAY_BUFFER, sizeof(thePoints), thePoints, GL_STATIC_DRAW);
261     ASSERT_GL_NO_ERROR();
262 
263     glEnableVertexAttribArray(0);
264     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
265 
266     glDrawArrays(GL_POINTS, 0, 1);
267     ASSERT_GL_NO_ERROR();
268 
269     // expect the center pixel to be green
270     EXPECT_PIXEL_EQ((windowWidth - 1) / 2, (windowHeight - 1) / 2, 0, 255, 0, 255);
271 
272     glDeleteBuffers(1, &vertexObject);
273 }
274 
275 // Verify GL_VERTEX_PROGRAM_POINT_SIZE is enabled
276 // https://www.khronos.org/registry/webgl/sdk/tests/conformance/rendering/point-size.html
TEST_P(PointSpritesTest,PointSizeEnabledCompliance)277 TEST_P(PointSpritesTest, PointSizeEnabledCompliance)
278 {
279     const std::string fs = SHADER_SOURCE
280     (
281         precision mediump float;
282         varying vec4 color;
283 
284         void main()
285         {
286             gl_FragColor = color;
287         }
288     );
289 
290     const std::string vs = SHADER_SOURCE
291     (
292         attribute vec3 pos;
293         attribute vec4 colorIn;
294         uniform float pointSize;
295         varying vec4 color;
296 
297         void main()
298         {
299             gl_PointSize = pointSize;
300             color = colorIn;
301             gl_Position = vec4(pos, 1.0);
302         }
303     );
304 
305     // The WebGL test is drawn on a 2x2 canvas. Emulate this by setting a 2x2 viewport.
306     glViewport(0, 0, 2, 2);
307 
308     GLuint program = CompileProgram(vs, fs);
309     ASSERT_NE(program, 0u);
310     ASSERT_GL_NO_ERROR();
311 
312     glUseProgram(program);
313 
314     glDisable(GL_BLEND);
315 
316     // The choice of (0.4, 0.4) ensures that the centers of the surrounding
317     // pixels are not contained within the point when it is of size 1, but
318     // that they definitely are when it is of size 2.
319     GLfloat vertices[] = { 0.4f, 0.4f, 0.0f };
320     GLubyte colors[] = { 255, 0, 0, 255 };
321 
322     GLuint vertexObject = 0;
323     glGenBuffers(1, &vertexObject);
324     ASSERT_NE(vertexObject, 0U);
325     ASSERT_GL_NO_ERROR();
326 
327     glBindBuffer(GL_ARRAY_BUFFER, vertexObject);
328     ASSERT_GL_NO_ERROR();
329 
330     glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(colors), NULL, GL_STATIC_DRAW);
331     ASSERT_GL_NO_ERROR();
332 
333     glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
334     ASSERT_GL_NO_ERROR();
335 
336     glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(colors), colors);
337     ASSERT_GL_NO_ERROR();
338 
339     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
340 
341     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
342     glEnableVertexAttribArray(0);
343 
344     glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, (GLvoid*)sizeof(vertices));
345     glEnableVertexAttribArray(1);
346 
347     GLint pointSizeLoc = glGetUniformLocation(program, "pointSize");
348     ASSERT_GL_NO_ERROR();
349 
350     glUniform1f(pointSizeLoc, 1.0f);
351     ASSERT_GL_NO_ERROR();
352 
353     glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(ArraySize(vertices)) / 3);
354     ASSERT_GL_NO_ERROR();
355 
356     // Test the pixels around the target Red pixel to ensure
357     // they are the expected color values
358     for (GLint y = 0; y < 2; ++y)
359     {
360         for (GLint x = 0; x < 2; ++x)
361         {
362             // 1x1 is expected to be a red pixel
363             // All others are black
364             GLubyte expectedColor[4] = { 0, 0, 0, 0 };
365             if (x == 1 && y == 1)
366             {
367                 expectedColor[0] = 255;
368                 expectedColor[3] = 255;
369             }
370             EXPECT_PIXEL_EQ(x, y, expectedColor[0], expectedColor[1], expectedColor[2], expectedColor[3]);
371         }
372     }
373 
374     GLfloat pointSizeRange[2] = {};
375     glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);
376 
377     if (pointSizeRange[1] >= 2.0)
378     {
379         // Draw a point of size 2 and verify it fills the appropriate region.
380         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
381 
382         glUniform1f(pointSizeLoc, 2.0f);
383         ASSERT_GL_NO_ERROR();
384 
385         glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(ArraySize(vertices)) / 3);
386         ASSERT_GL_NO_ERROR();
387 
388         // Test the pixels to ensure the target is ALL Red pixels
389         for (GLint y = 0; y < 2; ++y)
390         {
391             for (GLint x = 0; x < 2; ++x)
392             {
393                 EXPECT_PIXEL_EQ(x, y, 255, 0, 0, 255);
394             }
395         }
396     }
397 
398     glDeleteBuffers(1, &vertexObject);
399 }
400 
401 // Verify that rendering works correctly when gl_PointSize is declared in a shader but isn't used
TEST_P(PointSpritesTest,PointSizeDeclaredButUnused)402 TEST_P(PointSpritesTest, PointSizeDeclaredButUnused)
403 {
404     const std::string vs = SHADER_SOURCE
405     (
406         attribute highp vec4 position;
407 
408         void main(void)
409         {
410             gl_PointSize = 1.0;
411             gl_Position = position;
412         }
413     );
414 
415     const std::string fs = SHADER_SOURCE
416     (
417         void main(void)
418         {
419             gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
420         }
421     );
422 
423     GLuint program = CompileProgram(vs, fs);
424     ASSERT_NE(program, 0u);
425     ASSERT_GL_NO_ERROR();
426 
427     glUseProgram(program);
428     drawQuad(program, "position", 0.5f, 1.0f);
429     ASSERT_GL_NO_ERROR();
430 
431     // expect the center pixel to be red
432     EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 255, 0, 0, 255);
433 
434     glDeleteProgram(program);
435 }
436 
437 // Test to cover a bug where the D3D11 rasterizer state would not be update when switching between
438 // draw types.  This causes the cull face to potentially be incorrect when drawing emulated point
439 // spites.
TEST_P(PointSpritesTest,PointSpriteAlternatingDrawTypes)440 TEST_P(PointSpritesTest, PointSpriteAlternatingDrawTypes)
441 {
442     // clang-format off
443     const std::string pointFS = SHADER_SOURCE
444     (
445         precision mediump float;
446         void main()
447         {
448             gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
449         }
450     );
451 
452     const std::string pointVS = SHADER_SOURCE
453     (
454         void main()
455         {
456             gl_PointSize = 16.0;
457             gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
458         }
459     );
460 
461     const std::string quadFS = SHADER_SOURCE
462     (
463         precision mediump float;
464         void main()
465         {
466             gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
467         }
468     );
469 
470     const std::string quadVS = SHADER_SOURCE
471     (
472         precision mediump float;
473         attribute vec4 pos;
474         void main()
475         {
476             gl_Position = pos;
477         }
478     );
479     // clang-format on
480 
481     GLuint pointProgram = CompileProgram(pointVS, pointFS);
482     ASSERT_NE(pointProgram, 0u);
483     ASSERT_GL_NO_ERROR();
484 
485     GLuint quadProgram = CompileProgram(quadVS, quadFS);
486     ASSERT_NE(pointProgram, 0u);
487     ASSERT_GL_NO_ERROR();
488 
489     glEnable(GL_CULL_FACE);
490     glCullFace(GL_FRONT);
491 
492     const GLfloat quadVertices[] = {
493         -1.0f, 1.0f, 0.5f, 1.0f, -1.0f, 0.5f, -1.0f, -1.0f, 0.5f,
494 
495         -1.0f, 1.0f, 0.5f, 1.0f, 1.0f,  0.5f, 1.0f,  -1.0f, 0.5f,
496     };
497 
498     glUseProgram(quadProgram);
499     GLint positionLocation = glGetAttribLocation(quadProgram, "pos");
500     glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices);
501     glEnableVertexAttribArray(positionLocation);
502     glDrawArrays(GL_TRIANGLES, 0, 6);
503 
504     glUseProgram(pointProgram);
505     glDrawArrays(GL_POINTS, 0, 1);
506     ASSERT_GL_NO_ERROR();
507 
508     // expect the center pixel to be green
509     EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 0, 255, 0, 255);
510 
511     glDeleteProgram(pointProgram);
512     glDeleteProgram(quadProgram);
513 }
514 
515 // Use this to select which configurations (e.g. which renderer, which GLES
516 // major version) these tests should be run against.
517 //
518 // We test on D3D11 9_3 because the existing D3D11 PointSprite implementation
519 // uses Geometry Shaders which are not supported for 9_3.
520 // D3D9 and D3D11 are also tested to ensure no regressions.
521 ANGLE_INSTANTIATE_TEST(PointSpritesTest,
522                        ES2_D3D9(),
523                        ES2_D3D11(),
524                        ES2_D3D11_FL9_3(),
525                        ES2_OPENGL(),
526                        ES2_OPENGLES());
527