1 /*
2  * Copyright © 2011 Intel Corporation
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  */
23 
24 /**
25  * \file clipping-transforms.c
26  *
27  * This test verifies that clip planes are transformed using the
28  * correct matrices, at the correct times.
29  *
30  * The transformations affecting clipping in fixed functionality mode
31  * (with no vertex shader) are described in the OpenGL 2.1 spec,
32  * section 2.12 ("Clipping"):
33  *
34  *     "A client-defined clip plane is specified with
35  *
36  *         void ClipPlane( enum p, double eqn[4] );
37  *
38  *     ... eqn is an array of four double-precision floating-point
39  *     values. These are the coefficients of a plane equation in
40  *     object coordinates: p1 , p2 , p3 , and p4 (in that order). The
41  *     inverse of the current model-view matrix is applied to these
42  *     coefficients, at the time they are specified, yielding
43  *
44  *         (p1' p2' p3' p4') = (p1 p2 p3 p4) M^-1
45  *
46  *     (where M is the current model-view matrix; the resulting plane
47  *     equation is undefined if M is singular and may be inaccurate if
48  *     M is poorly-conditioned) to obtain the plane equation
49  *     coefficients in eye coordinates.  All points with eye
50  *     coordinates (xe ye ze we)^T that satisfy
51  *
52  *         (p1' p2' p3' p4') (xe ye ze we)^T >= 0
53  *
54  *     lie in the half-space defined by the plane; points that do not
55  *     satisfy this condition do not lie in the half-space."
56  *
57  * Thus, the clip planes should be modified by the value of the
58  * model-view matrix at the time clip planes are specified; the value
59  * of the model-view transformation at drawing time should have no
60  * effect on which part of the scene is clipped.
61  *
62  * The projection matrix, on the other hand, should be the opposite:
63  * its value at the time clip planes are specified should have no
64  * effect, but its value at drawing time should determine where on the
65  * screen clipping takes place (since clipping is performed on eye
66  * coordinates, before the perspective matrix is applied).
67  *
68  * The transformations affecting clipping when a vertex shader is
69  * present can be inferred from the text that follows:
70  *
71  *     "When a vertex shader is active, the vector (xe ye ze we)^T is
72  *     no longer computed. Instead, the value of the gl_ClipVertex
73  *     built-in variable is used in its place."
74  *
75  * So, as before, the model-view matrix affects clip planes at the
76  * time they are specified, but not at draw time.  However, the
77  * projection matrix no longer necessarily has an effect; instead, the
78  * place on the screen where clipping takes place is determined by the
79  * relationship between the values of gl_Position and gl_ClipVertex
80  * that are output by the vertex shader.
81  *
82  * It's also possible that the vertex shader might not store a value
83  * in gl_ClipVertex at all; what happens in this case is less clear.
84  * According to the GL 2.1 spec (from the same section):
85  *
86  *     "If gl ClipVertex is not written by the vertex shader, its
87  *     value is undefined, which implies that the results of clipping
88  *     to any client-defined clip planes are also undefined."
89  *
90  * The GL 3.0 spec says the same thing, and the GLSL 1.10 and 1.20
91  * specs have compatible language (from section 7.1: Vertex Shader
92  * Special Variables):
93  *
94  *     "If gl_PointSize or gl_ClipVertex are not written to, their
95  *     values are undefined."
96  *
97  * However, the GLSL 1.30 spec says:
98  *
99  *     "If a linked set of shaders forming the vertex stage contains
100  *     no static write to gl_ClipVertex or gl_ClipDistance, but the
101  *     application has requested clipping against user clip planes
102  *     through the API, then the coordinate written to gl_Position is
103  *     used for comparison against the user clip planes."
104  *
105  * So, if GLSL 1.30 is to be believed, if the vertex shader does not
106  * write to gl_Position, then the place on the screen where clipping
107  * takes place is determined exclusively by the plane equation (p1'
108  * p2' p3' p4').  No further transformation is applied.
109  *
110  * Note that strictly speaking, this doesn't contradict any of the
111  * other specs, since a conformant implementation may do anything it
112  * desires when behavior is "undefined", including clipping based on
113  * gl_Position.  Since this behavior is only specified in GLSL 1.30,
114  * we include a "#version 130" directive in the shader when testing
115  * it.
116  *
117  *
118  * The test operates by constructing four clip plane equations which
119  * are only satisfied by points within a small square region near (1,
120  * 0).  Setting all matrices to the identity matrix, and setting
121  * gl_Position == gl_ClipVertex == gl_Vertex, it draws a large square,
122  * large enough to cover the entire window, and then probes the
123  * resulting image to determine where pixels were actually drawn; due
124  * to clipping, they should be drawn only near (1, 0).
125  *
126  * Then it performs a 20 degree rotation in each of the following ways
127  * in turn, leaving all other transformations as the identity
128  * transformation:
129  * - Using the model-view matrix at the time clip planes are specified
130  * - Using the projection matrix at the time clip planes are specified
131  * - Using the model-view matrix at the time of drawing
132  * - Using the projection matrix at the time of drawing
133  * - Using the vertex shader to rotate gl_Position with respect to gl_Vertex
134  * - Using the vertex shader to rotate gl_ClipVertex with respect to gl_Vertex
135  *
136  * In each case it probes the resulting image to determine where
137  * pixels were actually drawn, and compares the result to the expected
138  * behavior from the spec.
139  *
140  *
141  * The test may be run in one of four modes, chosen with a single
142  * command line argument:
143  * - "fixed": test using fixed functionality (no vertex shader)
144  * - "arb": test using GL_ARB_vertex_program extension (see below)
145  * - "pos": test using a vertex shader that sets gl_Position only
146  * - "pos_clipvert": test using a vertex shader that sets gl_Position first,
147  *                   then gl_ClipVertex
148  * - "clipvert_pos": test using a vertex shader that sets gl_ClipVertex first,
149  *                   then gl_Position
150  *
151  * The reason for distinguishing between "pos_clipvert" and
152  * "clipvert_pos" is that in the present Mesa implementation, the
153  * variables gl_Position and gl_ClipVertex are aliases of each other,
154  * so the order in which values are stored into these two variables
155  * may affect shader behavior.
156  *
157  * Note: "arb" mode tests using an ARB vertex program, as defined in
158  * the GL_ARB_vertex_program extension.  From the extension spec:
159  *
160  *     "User-defined clipping is not supported in standard vertex
161  *     program mode.  User-defined clipping support will be provided
162  *     for programs that use the "position invariant" option, where
163  *     all vertex transformation operations are performed by the
164  *     fixed-function pipeline."
165  *
166  * The strong implication seems to be that for ARB vertex programs
167  * that use the "position invariant" option, clipping should behave as
168  * it does in fixed function mode.
169  */
170 
171 #include "piglit-util-gl.h"
172 
173 PIGLIT_GL_TEST_CONFIG_BEGIN
174 
175 	config.supports_gl_compat_version = 10;
176 
177 	config.window_visual = PIGLIT_GL_VISUAL_RGB | PIGLIT_GL_VISUAL_DOUBLE;
178 
179 PIGLIT_GL_TEST_CONFIG_END
180 
181 GLint position_angle_loc;
182 GLint clipVertex_angle_loc;
183 bool use_ff = false;
184 bool use_arb = false;
185 bool use_glsl = false;
186 bool use_clip_vertex = false;
187 bool use_glsl_130 = false;
188 
189 /**
190  * GLSL code used to set gl_Position and/or gl_ClipVertex in the
191  * vertex shader.
192  */
193 char *setters;
194 
195 void
setup_glsl_programs()196 setup_glsl_programs()
197 {
198 	GLuint prog;
199 
200 	char vert[4096];
201 	char frag[4096];
202 	char *version_directive;
203 
204 	if (use_glsl_130) {
205 		version_directive = "#version 130";
206 	} else {
207 		version_directive = "";
208 	}
209 
210 	sprintf(vert,
211 		"%s\n"
212 		"uniform float position_angle;\n"
213 		"uniform float clipVertex_angle;\n"
214 		"mat4 rotate(float angle)\n"
215 		"{\n"
216 		"  angle = radians(angle);\n"
217 		"  return mat4( cos(angle), sin(angle), 0.0, 0.0,\n"
218 		"              -sin(angle), cos(angle), 0.0, 0.0,\n"
219 		"                      0.0,        0.0, 1.0, 0.0,\n"
220 		"                      0.0,        0.0, 0.0, 1.0);\n"
221 		"}\n"
222 		"void main()\n"
223 		"{\n"
224 		"%s\n"
225 		"}",
226 		version_directive, setters);
227 	sprintf(frag,
228 		"%s\n"
229 		"void main()\n"
230 		"{\n"
231 		"  gl_FragColor = vec4(1.0);\n"
232 		"}",
233 		version_directive);
234 
235 	prog = piglit_build_simple_program(vert, frag);
236 	glUseProgram(prog);
237 	position_angle_loc = glGetUniformLocation(prog, "position_angle");
238 	if (use_clip_vertex) {
239 		clipVertex_angle_loc =
240 			glGetUniformLocation(prog, "clipVertex_angle");
241 	}
242 }
243 
244 void
setup_arb_program()245 setup_arb_program()
246 {
247 	char vert[] =
248 		"!!ARBvp1.0\n"
249 		"OPTION ARB_position_invariant;\n"
250 		"MOV result.color, { 1.0, 1.0, 1.0, 1.0 };"
251 		"END";
252 	GLuint vert_prog;
253 
254 	glGenProgramsARB(1, &vert_prog);
255 	glBindProgramARB(GL_VERTEX_PROGRAM_ARB, vert_prog);
256 	glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB,
257 			   strlen(vert), vert);
258 	glEnable(GL_VERTEX_PROGRAM_ARB);
259 }
260 
261 void
print_usage_and_exit(char * prog_name)262 print_usage_and_exit(char *prog_name)
263 {
264 	printf("Usage: %s <mode>\n"
265 	       "  where <mode> is one of:\n"
266 	       "    fixed\n"
267 	       "    arb\n"
268 	       "    pos\n"
269 	       "    pos_clipvert\n"
270 	       "    clipvert_pos\n", prog_name);
271 	exit(1);
272 }
273 
274 void
piglit_init(int argc,char ** argv)275 piglit_init(int argc, char **argv)
276 {
277 	if (argc != 2)
278 		print_usage_and_exit(argv[0]);
279 	if (strcmp(argv[1], "fixed") == 0) {
280 		use_ff = true;
281 	} else if (strcmp(argv[1], "arb") == 0) {
282 		use_arb = true;
283 	} else if (strcmp(argv[1], "pos") == 0) {
284 		use_glsl = true;
285 		setters = "  gl_Position = rotate(position_angle) * gl_Vertex;\n";
286 		use_glsl_130 = true;
287 	} else if (strcmp(argv[1], "pos_clipvert") == 0) {
288 		use_glsl = true;
289 		setters =
290 			"  gl_Position = rotate(position_angle) * gl_Vertex;\n"
291 			"  gl_ClipVertex = rotate(clipVertex_angle) * gl_Vertex;\n";
292 		use_clip_vertex = true;
293 	} else if (strcmp(argv[1], "clipvert_pos") == 0) {
294 		use_glsl = true;
295 		setters =
296 			"  gl_ClipVertex = rotate(clipVertex_angle) * gl_Vertex;\n"
297 			"  gl_Position = rotate(position_angle) * gl_Vertex;\n";
298 		use_clip_vertex = true;
299 	} else {
300 		print_usage_and_exit(argv[0]);
301 	}
302 
303 	if (use_arb) {
304 		piglit_require_extension("GL_ARB_vertex_program");
305 		setup_arb_program();
306 	} else if (use_glsl) {
307 		piglit_require_GLSL();
308 		piglit_require_GLSL_version(use_glsl_130 ? 130 : 110);
309 		setup_glsl_programs();
310 	}
311 }
312 
313 void
setup_clip_plane(int plane,float p1,float p2,float p3,float p4)314 setup_clip_plane(int plane, float p1, float p2, float p3, float p4)
315 {
316 	double eqn[4] = { p1, p2, p3, p4 };
317 	glClipPlane(GL_CLIP_PLANE0 + plane, eqn);
318 }
319 
320 bool
measure_effects(char * desc,int mc,int pc,int md,int pd,int expected)321 measure_effects(char *desc, int mc, int pc, int md, int pd, int expected)
322 {
323 	float size = 0.1;
324 	float dist = 1.0 - size/2;
325 
326 	int angle;
327 
328 	printf("Measuring %s: ", desc);
329 
330 	glClear(GL_COLOR_BUFFER_BIT);
331 
332 	glMatrixMode(GL_MODELVIEW);
333 	glLoadIdentity();
334 	glRotatef(mc, 0, 0, 1);
335 
336 	glMatrixMode(GL_PROJECTION);
337 	glLoadIdentity();
338 	glRotatef(pc, 0, 0, 1);
339 
340 	setup_clip_plane(0,  1.0,  0.0, 0.0, size-1.0); /* x > 1.0-size */
341 	setup_clip_plane(1, -1.0,  0.0, 0.0,      1.0); /* x < 1.0 */
342 	setup_clip_plane(2,  0.0,  1.0, 0.0,   size/2); /* y > -size/2 */
343 	setup_clip_plane(3,  0.0, -1.0, 0.0,   size/2); /* y < size/2 */
344 	glEnable(GL_CLIP_PLANE0);
345 	glEnable(GL_CLIP_PLANE1);
346 	glEnable(GL_CLIP_PLANE2);
347 	glEnable(GL_CLIP_PLANE3);
348 
349 	glMatrixMode(GL_MODELVIEW);
350 	glLoadIdentity();
351 	glRotatef(md, 0, 0, 1);
352 
353 	glMatrixMode(GL_PROJECTION);
354 	glLoadIdentity();
355 	glRotatef(pd, 0, 0, 1);
356 
357 	piglit_draw_rect(-2, -2, 4, 4);
358 
359 	for (angle = -180; angle < 180; angle += 10) {
360 		float angle_rad = angle * M_PI / 180.0;
361 		float xf = dist * cos(angle_rad);
362 		float yf = dist * sin(angle_rad);
363 		int x = (int) (0.5 + piglit_width * (xf + 1.0)/2.0);
364 		int y = (int) (0.5 + piglit_width * (yf + 1.0)/2.0);
365 		float found_color[4];
366 		glReadPixels(x, y, 1, 1, GL_RGBA, GL_FLOAT, found_color);
367 		if (found_color[0] > 0.5) {
368 			if (angle == expected) {
369 				printf("OK (angle=%d)\n", angle);
370 				return true;
371 			} else {
372 				printf("FAIL (angle=%d, expected=%d)\n", angle,
373 				       expected);
374 				return false;
375 			}
376 		}
377 	}
378 	printf("FAIL (test rect not found, expected=%d)\n", expected);
379 	return false;
380 }
381 
382 enum piglit_result
piglit_display()383 piglit_display()
384 {
385 	bool pass = true;
386 
387 	if (use_glsl) {
388 		glUniform1f(position_angle_loc, 0.0);
389 		glUniform1f(clipVertex_angle_loc, 0.0);
390 	}
391 
392 	/* Base behavior: no rotations, so the clipping planes should
393 	 * show up on screen at the coordinates where they were
394 	 * defined
395 	 */
396 	pass = measure_effects("base behavior", 0, 0, 0, 0, 0) && pass;
397 
398 	/* A 20 degree rotation in the model-view matrix at the time
399 	 * clip planes are specified should result in a 20 degree
400 	 * rotation of where clipping takes effect.
401 	 */
402 	pass = measure_effects(
403 	    "effect of 20deg ModelView rotation while setting clip plane",
404 	    20, 0, 0, 0, 20) && pass;
405 
406 	/* A 20 degree rotation in the projection matrix at the time
407 	 * clip planes are specified should have no effect.
408 	 */
409 	pass = measure_effects(
410             "effect of 20deg Projection rotation while setting clip plane",
411 	    0, 20, 0, 0, 0) && pass;
412 
413 	/* A 20 degree rotation in the model-view matrix at the time
414 	 * of drawing should have no effect.
415 	 */
416 	pass = measure_effects(
417             "effect of 20deg ModelView rotation while drawing",
418 	    0, 0, 20, 0, 0) && pass;
419 
420 	/* When using fixed functionality or an ARB position invariant
421 	 * program, a 20 degree rotation in the projection matrix at
422 	 * the time of drawing should result in a 20 degree rotation
423 	 * of where clipping takes effect when using fixed
424 	 * functionality.  When using a vertex shader, it should have
425 	 * no effect.
426 	 */
427 	pass = measure_effects(
428             "effect of 20deg Projection rotation while drawing",
429 	    0, 0, 0, 20, use_ff || use_arb ? 20 : 0) && pass;
430 
431 	if (use_glsl) {
432 		/* When a vertex shader sets gl_Position to be 20
433 		 * degrees rotated compared to gl_Vertex, and sets
434 		 * gl_ClipVertex to be equal to gl_Vertex, this should
435 		 * result in a 20 degree rotation of where clipping
436 		 * takes effect, because it causes gl_Position to be
437 		 * rotated 20 degrees with respect to gl_ClipVertex.
438 		 * However, when a vertex shader sets gl_Position and
439 		 * does not set gl_ClipVertex, there should be no
440 		 * effect, because the shader should behave as though
441 		 * it set gl_ClipVertex equal to gl_Position.
442 		 */
443 		glUniform1f(position_angle_loc, 20.0);
444 		pass = measure_effects(
445 		    "effect of 20deg rotation on gl_Position",
446 		    0, 0, 0, 0, use_clip_vertex ? 20 : 0) && pass;
447 		glUniform1f(position_angle_loc, 0.0);
448 	}
449 
450 	if (use_clip_vertex) {
451 		/* When a vertex shader sets gl_Position to be equal
452 		 * to gl_Vertex, and sets gl_ClipVertex to be 20
453 		 * degrees rotated compared to gl_Vertex, this should
454 		 * result in a negative 20 degree rotation of where
455 		 * clipping takes effect, because it causes
456 		 * gl_Position to be rotated negative 20 degrees with
457 		 * respect to gl_ClipVertex.
458 		 */
459 		glUniform1f(clipVertex_angle_loc, 20.0);
460 		pass = measure_effects(
461 		    "effect of 20deg rotation on gl_ClipVertex",
462 		    0, 0, 0, 0, -20) && pass;
463 		glUniform1f(clipVertex_angle_loc, 0.0);
464 	}
465 
466 	piglit_present_results();
467 
468 	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
469 }
470