Test that points are clamped to ALIASED_POINT_SIZE_RANGE

This test verifies that the point size written to gl_PointSize is
clipped to the range ALIASED_POINT_SIZE_RANGE before rasterization.

See also: http://crbug.com/740560
In particular this comment: http://crbug.com/740560#c27

New tests: dEQP-GLES2.functional.rasterization.limits.points
Components: AOSP
Change-Id: I98708ebece4be9c2bce3c7ba3b57454aec657cce
diff --git a/android/cts/master/gles2-master.txt b/android/cts/master/gles2-master.txt
index 2b6f7c7..483dfdd 100644
--- a/android/cts/master/gles2-master.txt
+++ b/android/cts/master/gles2-master.txt
@@ -11696,6 +11696,7 @@
 dEQP-GLES2.functional.rasterization.primitives.line_strip
 dEQP-GLES2.functional.rasterization.primitives.line_loop
 dEQP-GLES2.functional.rasterization.primitives.points
+dEQP-GLES2.functional.rasterization.limits.points
 dEQP-GLES2.functional.rasterization.fill_rules.basic_quad
 dEQP-GLES2.functional.rasterization.fill_rules.basic_quad_reverse
 dEQP-GLES2.functional.rasterization.fill_rules.clipped_full
diff --git a/modules/gles2/functional/es2fRasterizationTests.cpp b/modules/gles2/functional/es2fRasterizationTests.cpp
index 5f8c1c5..21a439c 100644
--- a/modules/gles2/functional/es2fRasterizationTests.cpp
+++ b/modules/gles2/functional/es2fRasterizationTests.cpp
@@ -101,7 +101,6 @@
 	float					m_pointSize;
 	float					m_lineWidth;
 
-private:
 	glu::ShaderProgram*		m_shader;
 };
 
@@ -541,6 +540,135 @@
 		m_testCtx.getLog() << tcu::TestLog::Message << "Point " << (pointNdx+1) << ":\t" << outPoints[pointNdx].position << tcu::TestLog::EndMessage;
 }
 
+class PointSizeClampedTest : public BaseRenderingCase
+{
+public:
+	PointSizeClampedTest (Context& context, const char* name, const char* desc)
+		: BaseRenderingCase	(context, name, desc)
+	{
+	}
+
+	IterateResult iterate ()
+	{
+		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
+		const glw::Functions& gl = m_context.getRenderContext().getFunctions();
+
+		// Tests that point sizes (written to gl_PointSize) are clamped,
+		// before rasterization, to the ALIASED_POINT_SIZE_RANGE
+		// given by the implementation.
+		static const int fboHeight = 4;
+		static const int testAreaWidth = 4;
+		static const int testAreaWidthWithMargin = testAreaWidth + 4;
+		static const float pointRadiusOverage = 8;
+		int fboWidth = 0;
+		int maxPointDiameter = 0;
+		{
+			int maxRenderbufferSize = 0;
+			int maxViewportDims[2] = {};
+			gl.getIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxRenderbufferSize);
+			gl.getIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims);
+			int maxFboWidth = std::min(maxRenderbufferSize, maxViewportDims[0]);
+
+			float pointSizeRange[2] = {};
+			gl.getFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);
+			m_testCtx.getLog() << tcu::TestLog::Message
+				<< "GL_ALIASED_POINT_SIZE_RANGE is [" << pointSizeRange[0] << ", " << pointSizeRange[1] << "]"
+				<< tcu::TestLog::EndMessage;
+			// Typically (in the correct case), maxPointDiameter is an odd integer.
+			maxPointDiameter = (int) pointSizeRange[1];
+			// maxPointRadius is inclusive of the center point.
+			int maxPointRadius = (maxPointDiameter + 1) / 2;
+			if (maxPointRadius > maxFboWidth - testAreaWidthWithMargin)
+			{
+				m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "max framebuffer size isn't large enough to test max point size");
+				return STOP;
+			}
+			fboWidth = maxPointRadius + testAreaWidthWithMargin;
+			// Round up to the nearest multiple of 2:
+			fboWidth = ((fboWidth + 1) / 2) * 2;
+		}
+		float pointSize = ((float) maxPointDiameter) + pointRadiusOverage * 2;
+		TCU_CHECK(gl.getError() == GL_NO_ERROR);
+
+		m_testCtx.getLog() << tcu::TestLog::Message
+			<< "Testing with pointSize = " << pointSize
+			<< ", fboWidth = " << fboWidth
+			<< tcu::TestLog::EndMessage;
+
+		// Create a framebuffer that is (fboWidth)x(fboHeight), cleared to green:
+		// +---------------------------+
+		// |ggggggggggggggggggggggggggg|
+		// +---------------------------+
+		gl.viewport(0, 0, fboWidth, fboHeight);
+		deUint32 fbo = 0;
+		gl.genFramebuffers(1, &fbo);
+		gl.bindFramebuffer(GL_FRAMEBUFFER, fbo);
+		deUint32 rbo = 0;
+		gl.genRenderbuffers(1, &rbo);
+		gl.bindRenderbuffer(GL_RENDERBUFFER, rbo);
+		gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, fboWidth, fboHeight);
+		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
+		if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+		{
+			m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "couldn't complete a framebuffer suitable to test max point size");
+			return STOP;
+		}
+		gl.clearColor(0.0f, 1.0f, 0.0f, 1.0f);
+		gl.clear(GL_COLOR_BUFFER_BIT);
+		TCU_CHECK(gl.getError() == GL_NO_ERROR);
+
+		// (Framebuffer is still bound.)
+
+		// Draw a red point, with size pointSize, at the far right:
+		// +---------------------------+
+		// |ggggggggRRRRRRRRRRRRRRRRRRR|
+		// +---------------------------+
+		//                            x                           point center
+		//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^                           fboWidth
+		//  ^^^^                                                  testAreaWidth = 4 (this is the area that's tested)
+		//  ^^^^^^^^                                              testAreaWidthWithMargin = 8 (extra 4 pixels for tolerance)
+		//          ^^^^^^^^^^^^^^^^^^x^^^^^^^^^^^^^^^^^^         maxPointDiameter = 37
+		//  ^^^^^^^^                                     ^^^^^^^^ pointRadiusOverage = 8 * 2
+		//  ^^^^^^^^^^^^^^^^^^^^^^^^^^x^^^^^^^^^^^^^^^^^^^^^^^^^^ pointSize = 53
+		//          ^^^^^^^^^^^^^^^^^^^ area of resulting draw, if the size is clamped properly = 19
+		{
+			const glw::GLint		positionLoc		= gl.getAttribLocation(m_shader->getProgram(), "a_position");
+			const glw::GLint		colorLoc		= gl.getAttribLocation(m_shader->getProgram(), "a_color");
+			const glw::GLint		pointSizeLoc	= gl.getUniformLocation(m_shader->getProgram(), "u_pointSize");
+			static const float position[] = {1.0f, 0.0f, 0.0f, 1.0f};
+			static const float color[] = {1.0f, 0.0f, 0.0f, 1.0f};
+			gl.useProgram(m_shader->getProgram());
+			gl.enableVertexAttribArray(positionLoc);
+			gl.vertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, position);
+			gl.enableVertexAttribArray(colorLoc);
+			gl.vertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, 0, color);
+			gl.uniform1f(pointSizeLoc, pointSize);
+			gl.drawArrays(GL_POINTS, 0, 1);
+			gl.disableVertexAttribArray(colorLoc);
+			gl.disableVertexAttribArray(positionLoc);
+			gl.useProgram(0);
+			TCU_CHECK(gl.getError() == GL_NO_ERROR);
+		}
+
+		// And test the resulting draw (the test area should still be green).
+		deUint32 pixels[testAreaWidth * fboHeight] = {};
+		gl.readPixels(0, 0, testAreaWidth, fboHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+		TCU_CHECK(gl.getError() == GL_NO_ERROR);
+
+		const tcu::RGBA threshold(12, 12, 12, 12);
+		for (deUint32 y = 0; y < fboHeight; ++y)
+		{
+			for (deUint32 x = 0; x < testAreaWidth; ++x)
+			{
+				tcu::RGBA color(pixels[y * testAreaWidth + x]);
+				TCU_CHECK(compareThreshold(color, tcu::RGBA::green(), threshold));
+			}
+		}
+
+		return STOP;
+	}
+};
+
 class TrianglesCase : public BaseTriangleCase
 {
 public:
@@ -1839,6 +1967,15 @@
 		primitives->addChild(new PointCase			(m_context, "points",			"Render primitives as GL_POINTS, verify rasterization result",							PRIMITIVEWIDENESS_WIDE));
 	}
 
+	// .limits
+	{
+		tcu::TestCaseGroup* const limits = new tcu::TestCaseGroup(m_testCtx, "limits", "Primitive width limits");
+
+		addChild(limits);
+
+		limits->addChild(new PointSizeClampedTest(m_context, "points", "gl_PointSize is clamped to ALIASED_POINT_SIZE_RANGE"));
+	}
+
 	// .fill_rules
 	{
 		tcu::TestCaseGroup* const fillRules = new tcu::TestCaseGroup(m_testCtx, "fill_rules", "Primitive fill rules");