aboutsummaryrefslogtreecommitdiff
path: root/tests/spec/glsl-1.30/execution/isinf-and-isnan.c
blob: 36a078bc9a10dfc0d5e9c0a03e48d602288e658b (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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
/*
 * Copyright © 2011 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/**
 * \file isinf-and-isnan.c
 *
 * Test that isinf() and isnan() built-in functions behave properly.
 *
 * The GLSL 1.30 spec does not define when an implementation is required to
 * generate infinite or NaN values; in fact, it explicitly allows for
 * implementations that do not even have a representation of infinity or Nan.
 * Therefore, we cannot check that infinities and NaNs are created when we
 * expect them.  However, we can test: (a) that isnan() and isinf() return
 * false for finite values, (b) that isinf() and isnan() behave consistently
 * with each other, and (c) that when a floating-point value is read out the
 * shader (using transform feedback or a floating point framebuffer) the
 * behavior of isnan() and isinf() behave consistently with the value that is
 * read out.
 *
 * This test operates by generating several expressions, some of which are
 * likely to produce infinities, some of which are likely to produce NaN, and
 * some of which are expected to produce finite values.  For each expression,
 * it does the following:
 * - evaluates isinf(value) in the shader
 * - evaluates isnan(value) in the shader
 * - evaluates sign(value) in the shader
 * - evaluates (value > 0) in the shader
 * - reads the value out of the shader (using transform feedback or a floating
 *   point framebuffer)
 * - feeds that value back into the shader (using a uniform); the shader
 *   subtracts this uniform from the originally computed value to produce a
 *   delta.
 *
 * And then it performs the following checks:
 * - If the value was expected to be finite, verifies that isinf() and isnan()
 *   returned false.
 * - If the value was expected to be +Inf or -Inf, verifies that the sign is
 *   correct, using both sign(value) and (value > 0).  This check is skipped
 *   if isnan(value) is true, since it's possible that a conformant
 *   implementation might generate NaN instead of infinity, and NaN does not
 *   have a well-defined sign.
 * - Checks that isinf() and isnan() didn't both return true.
 * - Checks that the C isinf() and isnan() functions give the same result as
 *   the shader's isinf() and isnan() functions.
 * - If the value is finite, checks that the delta is zero (to within
 *   tolerance).
 *
 * The last two checks are only performed when using a floating point
 * framebuffer or transform feedback, because those are the only ways to get
 * infinities and NaNs out of the shader and into C code.
 *
 * Note: the reason for the final check is to verify that a value claimed by
 * the shader to be finite is truly behaving like a finite number.  Without
 * it, an implementation could pass all these tests by simply having isinf()
 * and isnan() return false, and converting infinities and NaNs to finite
 * values when they exit the shader.
 *
 * The output of the test is a table whose columns are:
 * - The expression being tested (this expression may refer to the uniforms
 *   z=0.0, u_inf=+Inf, u_minus_inf=-Inf, and u_nan=NaN).
 * - The expected behavior of the expression ("finite", "+Inf", "-Inf", or
 *   "NaN", indicating how the expression would be expected to evaluate on a
 *   fully IEEE 754 compliant architecture)
 * - isinf(value), as computed by the shader
 * - isnan(value), as computed by the shader
 * - sign(value), as computed by the shader
 * - (value > 0), as computed by the shader
 * - value, as read out of the shader using transform feedback or a
 *   floating-point framebuffer
 * - delta, the difference between the computed value and the value that was
 *   fed back into the shader.
 * - A pass/fail indication.
 *
 * Note: the uniform z=0.0 is present to defeat constant folding and ensure
 * that the expression is evaluated during shader execution rather than during
 * compilation.  For example, "exp(1000.0)" might be evaluated at compile-time,
 * preventing us from exercising this test case in the GPU.  But
 * "exp(1000.0+z)" will definitely be evaluated on the GPU.
 *
 * The test must be invoked with one of the following command-line arguments:
 * - vs_basic: test the VS without reading values out of the shader.
 * - fs_basic: test the FS without reading values out of the shader.
 * - vs_fbo: test the VS, using a floating-point framebuffer to read values
 *   out of the shader.
 * - vs_xfb: test the VS, using transform feedback to read values out of the
 *   shader.
 * - fs_fbo: test the FS, using a floating-point framebuffer to read values
 *   out of the shader.
 */

#include "piglit-util-gl-common.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 10;

	config.window_width = 100;
	config.window_height = 100;
	config.window_visual = PIGLIT_GL_VISUAL_RGB | PIGLIT_GL_VISUAL_ALPHA | PIGLIT_GL_VISUAL_DOUBLE;

PIGLIT_GL_TEST_CONFIG_END

static float gl_version;

static GLint stock_vs;
static GLint stock_fs;
static GLint main_vs;
static GLint main_fs;
static GLint do_test_vs;
static GLint do_test_fs;
static GLuint xfb_buffer;

/**
 * True if we are using a floating-point framebuffer to read data out of the
 * shader.
 */
static bool use_fbo = false;

/**
 * True if we are using transform feedback to read data out of the shader.
 */
static bool use_xfb = false;

/**
 * True if we are testing the fragment shader, false if we are testing the
 * vertex shader.
 */
static bool use_fs;

/**
 * True if we are reading data out of the shader using a mechanism that
 * preserves the full 32-bit floating point value, so we can do additional
 * checks.
 */
static bool precise;

enum modes
{
	/**
	 * Output = vec4(value, isinf(value), isnan(value),
	 *               (sign(value) + 1.0) / 2.0)
	 */
	MODE_VALUE_ISINF_ISNAN_SIGN = 0,

	/**
	 * Output = vec4(value > 0, value - ref, 0.0, 0.0)
	 */
	MODE_GTZERO_DELTA_ZERO_ZERO = 1,
};

static const char stock_vs_text[] =
	"#version 130\n"
	"void main()\n"
	"{\n"
	"  gl_Position = gl_Vertex;\n"
	"}\n";

static const char stock_fs_text[] =
	"#version 130\n"
	"flat in vec4 data;\n"
	"void main()\n"
	"{\n"
	"  gl_FragColor = data;\n"
	"}\n";

static const char main_vs_text[] =
	"#version 130\n"
	"flat out vec4 data;\n"
	"vec4 do_test();\n"
	"void main()\n"
	"{\n"
	"  gl_Position = gl_Vertex;\n"
	"  data = do_test();\n"
	"}\n";

static const char main_fs_text[] =
	"#version 130\n"
	"flat in vec4 data;\n"
	"vec4 do_test();\n"
	"void main()\n"
	"{\n"
	"  gl_FragColor = do_test();\n"
	"}\n";

static const char do_test_text[] =
	"#version 130\n"
	"uniform float ref;\n" /* Value fed back from C */
	"uniform int mode;\n" /* See enum modes */
	"float compute_value();\n"
	"vec4 do_test()\n"
	"{\n"
	"  float value = compute_value();\n"
	"  if (mode == 0) { /* MODE_VALUE_ISINF_ISNAN_SIGN */\n"
	"    return vec4(value,\n"
	"                isinf(value) ? 1 : 0,\n"
	"                isnan(value) ? 1 : 0,\n"
	"                (sign(value) + 1.0) / 2.0);\n"
	"  } else if (mode == 1) { /* MODE_GTZERO_DELTA_ZERO_ZERO */\n"
	"    return vec4(value > 0 ? 1 : 0,\n"
	"                value - ref,\n"
	"                0.0,\n"
	"                0.0);\n"
	"  } else { /* Unrecognized mode */\n"
	"    return vec4(0.0);\n"
	"  }\n"
	"}\n";

static void
setup_fbo()
{
	GLuint fb = 0;
	GLuint color_rb = 0;
	GLenum fb_status;

	glGenFramebuffers(1, &fb);
	glBindFramebuffer(GL_FRAMEBUFFER, fb);

	/* Bind color attachment. */
	glGenRenderbuffers(1, &color_rb);
	glBindRenderbuffer(GL_RENDERBUFFER, color_rb);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA32F,
			      piglit_width, piglit_height);
	glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
				  GL_RENDERBUFFER, color_rb);
	if (!piglit_check_gl_error(0))
		piglit_report_result(PIGLIT_FAIL);

	fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
	if (fb_status != GL_FRAMEBUFFER_COMPLETE) {
		printf("error: FBO incomplete (status = 0x%04x)\n", fb_status);
		piglit_report_result(PIGLIT_SKIP);
	}

	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb);
	glBindFramebuffer(GL_READ_FRAMEBUFFER, fb);
}

static void
setup_xfb()
{
	glGenBuffers(1, &xfb_buffer);
}

static void
print_usage_and_exit(char *prog_name)
{
	printf("Usage: %s <mode>\n"
	       "  where <mode> is one of:\n"
	       "    vs_basic\n"
	       "    fs_basic\n"
	       "    vs_fbo\n"
	       "    vs_xfb\n"
	       "    fs_fbo\n", prog_name);
	exit(1);
}

void
piglit_init(int argc, char **argv)
{
	if (argc != 2)
		print_usage_and_exit(argv[0]);
	if (strcmp(argv[1], "vs_basic") == 0) {
		use_fs = false;
	} else if (strcmp(argv[1], "fs_basic") == 0) {
		use_fs = true;
	} else if (strcmp(argv[1], "vs_fbo") == 0) {
		use_fs = false;
		use_fbo = true;
	} else if (strcmp(argv[1], "vs_xfb") == 0) {
		use_fs = false;
		use_xfb = true;
	} else if (strcmp(argv[1], "fs_fbo") == 0) {
		use_fs = true;
		use_fbo = true;
	} else {
		print_usage_and_exit(argv[0]);
	}
	precise = use_fbo || use_xfb;

	gl_version = strtod((char *) glGetString(GL_VERSION), NULL);

	piglit_require_GLSL();
	piglit_require_GLSL_version(130);
	piglit_require_gl_version(30);

	if (use_fbo) {
		setup_fbo();
	}
	if (use_xfb) {
		setup_xfb();
	}

	stock_vs = piglit_compile_shader_text(GL_VERTEX_SHADER,
					      stock_vs_text);
	stock_fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
					      stock_fs_text);
	main_vs = piglit_compile_shader_text(GL_VERTEX_SHADER,
					     main_vs_text);
	main_fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
					     main_fs_text);
	do_test_vs = piglit_compile_shader_text(GL_VERTEX_SHADER,
						do_test_text);
	do_test_fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
						do_test_text);
}

/**
 * enum indicating how the expression would be expected to behave on a fully
 * IEEE 754 compliant architecture.  Note: since OpenGL implementations are
 * not required to respect all of IEEE 754's rules for infinities and NaN's,
 * we don't necessarily check all of these behaviors.
 */
enum behavior
{
	B_NAN    = 0, /* Expected to evaluate to NaN */
	B_FINITE = 1, /* Expected to evaluate to a finite value */
	B_POSINF = 2, /* Expected to evaluate to +Infinity */
	B_NEGINF = 3, /* Expected to evaluate to -Infinity */
};

struct expression_table_element
{
	char *expression;
	int expected_behavior;
};

static struct expression_table_element expressions[] = {
	{ "1000.0", B_FINITE },
	{ "1000.0+z", B_FINITE },
	{ "-1000.0", B_FINITE },
	{ "-1000.0+z", B_FINITE },
	{ "u_inf", B_POSINF },
	{ "exp(1000.0)", B_POSINF },
	{ "exp(1000.0+z)", B_POSINF },
	{ "u_minus_inf", B_NEGINF },
	{ "-exp(1000.0)", B_NEGINF },
	{ "-exp(1000.0+z)", B_NEGINF },
	{ "u_nan", B_NAN },
	{ "0.0/0.0", B_NAN },
	{ "z/z", B_NAN },
	{ "u_inf/u_minus_inf", B_NAN },
	{ "z*u_inf", B_NAN },
	{ "u_inf+u_minus_inf", B_NAN },
	{ "log(-1.0)", B_NAN },
	{ "log(-1.0+z)", B_NAN },
	{ "sqrt(-1.0)", B_NAN },
	{ "sqrt(-1.0+z)", B_NAN },
};

/**
 * Draw using the shader, and then read back values using either (a) the
 * floating-point framebuffer, (b) transform feedback, or (c) pixel reads from
 * the window.  Note that pixel reads from the window are only accurate to one
 * part in 255, so the caller must be careful not to rely on high precision in
 * case (c).
 */
static void
draw_and_readback(float *readback)
{
	if (use_xfb) {
		glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 4096, NULL,
			     GL_DYNAMIC_COPY);
		glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfb_buffer);
		glEnable(GL_RASTERIZER_DISCARD);
		glBeginTransformFeedback(GL_TRIANGLES);
	}

	piglit_draw_rect(-1, -1, 2, 2);

	if (use_xfb) {
		glEndTransformFeedback();
		memcpy(readback,
		       glMapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, GL_READ_ONLY),
		       4*sizeof(float));
		glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
	} else {
		glReadPixels(0, 0, 1, 1, GL_RGBA, GL_FLOAT, readback);
	}
}

static void
set_uniform_float_if_present(GLint program, char *name, float value)
{
	GLint loc = glGetUniformLocation(program, name);
	if (loc != -1)
		glUniform1f(loc, value);
}

static void
set_uniform_int_if_present(GLint program, char *name, int value)
{
	GLint loc = glGetUniformLocation(program, name);
	if (loc != -1)
		glUniform1i(loc, value);
}

/**
 * Test the given expression, to make sure its behavior is self-consistent and
 * consistent with the expected behavior.
 */
static bool
test_expr(char *expression, int expected_behavior)
{
	char compute_value_text[4096];
	GLint shader;
	GLint prog;
	float readback[4];
	float value;
	bool isinf_in_shader;
	bool isnan_in_shader;
	int sign_in_shader;
	float delta;
	bool greater_than_zero;
	bool pass = true;
	char *expected_behavior_string;

	/* Create and link a program specifically to test this expression */
	prog = glCreateProgram();
	sprintf(compute_value_text,
		"#version 130\n"
		"uniform float z = 0.0;\n" /* To defeat constant folding */
		"uniform float u_inf;\n" /* Always == +infinity */
		"uniform float u_minus_inf;\n" /* Always == -infinity */
		"uniform float u_nan;\n" /* Always == NaN */
		"float compute_value() {\n"
		"  return %s;\n"
		"}\n",
		expression);
	if (use_fs) {
		glAttachShader(prog, stock_vs);
		glAttachShader(prog, main_fs);
		glAttachShader(prog, do_test_fs);
		shader = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
						    compute_value_text);
		glAttachShader(prog, shader);
	} else {
		glAttachShader(prog, stock_fs);
		glAttachShader(prog, main_vs);
		glAttachShader(prog, do_test_vs);
		shader = piglit_compile_shader_text(GL_VERTEX_SHADER,
						    compute_value_text);
		glAttachShader(prog, shader);
	}
	if (use_xfb) {
		static const char *var_name = "data";
		glTransformFeedbackVaryings(prog, 1, &var_name,
					    GL_SEPARATE_ATTRIBS);
		glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfb_buffer);
	}
	glLinkProgram(prog);
	glDeleteShader(shader);
	glUseProgram(prog);

	/* Set up uniforms */
	set_uniform_float_if_present(prog, "u_inf", INFINITY);
	set_uniform_float_if_present(prog, "u_minus_inf", -INFINITY);
	set_uniform_float_if_present(prog, "u_nan", NAN);

	/* Use one draw call to read out value, isinf(value), isnan(value),
	 * and sign(value).
	 */
	set_uniform_float_if_present(prog, "ref", 0.0);
	set_uniform_int_if_present(prog, "mode", MODE_VALUE_ISINF_ISNAN_SIGN);
	draw_and_readback(readback);
	value = readback[0];
	isinf_in_shader = readback[1] > 0.5;
	isnan_in_shader = readback[2] > 0.5;
	sign_in_shader = (int) (2.0*readback[3] + 0.5) - 1;

	/* Use a second draw call to feed value back into the shader, and read
	 * out (value > 0) and delta.
	 */
	set_uniform_float_if_present(prog, "ref", value);
	set_uniform_int_if_present(prog, "mode", MODE_GTZERO_DELTA_ZERO_ZERO);
	draw_and_readback(readback);
	greater_than_zero = readback[0] > 0.5;
	delta = readback[1];

	/* Check that the behavior was as expected */
	switch (expected_behavior) {
	case B_FINITE:
		expected_behavior_string = "finite";
		if (isinf_in_shader || isnan_in_shader) {
			/* Expected finite, got Inf or NaN */
			pass = false;
		}
		break;
	case B_POSINF:
		expected_behavior_string = "+Inf";
		if (!isnan_in_shader && sign_in_shader != 1.0) {
			/* Expected positive or NaN, got <= 0 */
			pass = false;
		}
		break;
	case B_NEGINF:
		expected_behavior_string = "-Inf";
		if (!isnan_in_shader && sign_in_shader != -1.0) {
			/* Expected negative or NaN, got >= 0 */
			pass = false;
		}
		break;
	default:
		expected_behavior_string = "NaN";
		break;
	}

	/* Do other sanity checks */
	if (isnan_in_shader && isinf_in_shader) {
		/* No value can be simultaneously Inf and NaN */
		pass = false;
	}
	if (!isnan_in_shader) {
		if (sign_in_shader == -1 || sign_in_shader == 0) {
			if (greater_than_zero) {
				/* sign(value) inconsistent with (value>0) */
				pass = false;
			}
		} else if (sign_in_shader == 1) {
			if (!greater_than_zero) {
				/* sign(value) inconsistent with (value>0) */
				pass = false;
			}
		} else {
			/* Illegal return value for sign() */
			pass = false;
		}
	}

	/* If we are using a high-precision technique to read data out of the
	 * shader (fbo or xfb), check the behavior of isinf and isnan against
	 * their C counterparts, and verify that delta ~= 0 for finite values.
	 */
	if (precise) {
		bool isinf_in_c = !!isinf(value);
		bool isnan_in_c = !!isnan(value);
		if (isinf_in_shader != isinf_in_c ||
		    isnan_in_shader != isnan_in_c) {
			/* Result of isinf() and isnan() in the shader did not
			 * match the result in C code.
			 */
			pass = false;
		}
		if (!isinf_in_shader && !isnan_in_shader) {
			float threshold = fabs(value * 1e-6);
			if (isinf(delta) || isnan(delta) ||
			    fabs(delta) > threshold) {
				/* The shader and C code agree that the value
				 * was finite, but it isn't behaving as a nice
				 * finite value should.
				 */
				pass = false;
			}
		}
	}

	/* Output a line for the results table */
	printf("%17s %6s %5s %5s %4d %5s ",
	       expression,
	       expected_behavior_string,
	       isinf_in_shader ? "true" : "false",
	       isnan_in_shader ? "true" : "false",
	       sign_in_shader,
	       greater_than_zero ? "true" : "false");
	if (precise) {
		printf("%12g %12g ", value, delta);
	}
	printf("%s\n", pass ? "OK" : "FAIL");

	glUseProgram(0);
	glDeleteProgram(prog);

	return pass;
}

enum piglit_result
piglit_display()
{
	int i;
	bool pass = true;

	printf("    expression    expect isinf isnan sign  >0?");
	if (precise)
		printf("      value        delta");
	printf("\n");

	for (i = 0; i < sizeof(expressions)/sizeof(*expressions); ++i) {
		pass = test_expr(expressions[i].expression,
				 expressions[i].expected_behavior) && pass;
	}

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}