[Piglit] [PATCH] Test transform feedback of varying structs.

Paul Berry stereotype441 at gmail.com
Tue Feb 5 10:27:15 PST 2013


The test may be run either against GLSL ES 3.00 or against desktop
GLSL 1.50.  When desktop GLSL is tested, the test verifies proper
functioning both with and without the use of interface blocks.

This test is slightly meaner than what's currently necessary to verify
proper functionality in Mesa (in particular, it tests a variety of
different nestings of structs and arrays, even though no extra
implementation effort was necessary to make these tests pass).  I have
two reasons for wanting to write such a mean test:

- Varying structs in Mesa are currently implemented using a naive
  layout that packs structure and array contents in order with no gaps
  (in much the same way that structs are laid out in memory in C).
  It's likely that in the future we will need to replace this with a
  more sophisticated mechanism that "flattens" varying structs to
  their constituent fields.  Such an implementation will have
  important corner cases involving complex nesting of structs and
  arrays.

- During my implementation of this feature in Mesa (and my development
  of this test), I cross-checked against a competing implementation
  and found several bugs, particularly in cases involving complex
  nesting of structs and arrays.  It seemed worthwhile to write a test
  capable of detecting these bugs.
---

Note: I would especially appreciate feedback on the draw_rect()
function (and draw_arrays, which it calls).  I wanted to use
piglit_draw_rect, but I couldn't because this test needs to be able to
run in core contexts, and piglit_draw_rect() isn't compatible with
core contexts.


 tests/all.tests                                    |   13 +
 .../spec/ext_transform_feedback/CMakeLists.gl.txt  |    1 +
 .../ext_transform_feedback/CMakeLists.gles3.txt    |    7 +
 tests/spec/ext_transform_feedback/structs.c        | 1356 ++++++++++++++++++++
 4 files changed, 1377 insertions(+)
 create mode 100644 tests/spec/ext_transform_feedback/CMakeLists.gles3.txt
 create mode 100644 tests/spec/ext_transform_feedback/structs.c

diff --git a/tests/all.tests b/tests/all.tests
index 2ffc6e1..b3f7219 100644
--- a/tests/all.tests
+++ b/tests/all.tests
@@ -1962,6 +1962,19 @@ for test_case in ['base-shrink', 'base-grow', 'offset-shrink', 'offset-grow',
         test_name = 'change-size {0}'.format(test_case)
         ext_transform_feedback[test_name] = concurrent_test(
                 'ext_transform_feedback-{0}'.format(test_name))
+for api_suffix, possible_options in [('', [[], ['interface']]),
+                                     ('_gles3', [[]])]:
+        for subtest in ['basic-struct', 'struct-whole-array',
+                        'struct-array-elem', 'array-struct',
+                        'array-struct-whole-array', 'array-struct-array-elem',
+                        'struct-struct', 'array-struct-array-struct']:
+                for mode in ['error', 'get', 'run', 'run-no-fs']:
+                        for options in possible_options:
+                                args = [subtest, mode] + options
+                                test_name = 'structs{0} {1}'.format(
+                                        api_suffix, ' '.join(args))
+                                ext_transform_feedback[test_name] = concurrent_test(
+                                        'ext_transform_feedback-{0}'.format(test_name))
 
 arb_transform_feedback2 = Group()
 spec['ARB_transform_feedback2'] = arb_transform_feedback2
diff --git a/tests/spec/ext_transform_feedback/CMakeLists.gl.txt b/tests/spec/ext_transform_feedback/CMakeLists.gl.txt
index 63c3f4b..93b8aae 100644
--- a/tests/spec/ext_transform_feedback/CMakeLists.gl.txt
+++ b/tests/spec/ext_transform_feedback/CMakeLists.gl.txt
@@ -32,6 +32,7 @@ piglit_add_executable (ext_transform_feedback-separate separate.c)
 piglit_add_executable (ext_transform_feedback-output-type output-type.c)
 piglit_add_executable (ext_transform_feedback-order order.c)
 piglit_add_executable (ext_transform_feedback-overflow-edge-cases overflow-edge-cases.c)
+piglit_add_executable (ext_transform_feedback-structs structs.c)
 piglit_add_executable (ext_transform_feedback-tessellation tessellation.c)
 
 # vim: ft=cmake:
diff --git a/tests/spec/ext_transform_feedback/CMakeLists.gles3.txt b/tests/spec/ext_transform_feedback/CMakeLists.gles3.txt
new file mode 100644
index 0000000..32b92bd
--- /dev/null
+++ b/tests/spec/ext_transform_feedback/CMakeLists.gles3.txt
@@ -0,0 +1,7 @@
+link_libraries(
+	piglitutil_${piglit_target_api}
+	)
+
+piglit_add_executable (ext_transform_feedback-structs_${piglit_target_api} structs.c)
+
+# vim: ft=cmake:
diff --git a/tests/spec/ext_transform_feedback/structs.c b/tests/spec/ext_transform_feedback/structs.c
new file mode 100644
index 0000000..1872494
--- /dev/null
+++ b/tests/spec/ext_transform_feedback/structs.c
@@ -0,0 +1,1356 @@
+/*
+ * Copyright © 2013 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 structs.c
+ *
+ * Test proper functioning of transform feedback with varying structs.
+ *
+ * The spec is ambiguous about how transform feedback is supposed to
+ * interact with varying structs.  However, the Khronos board has
+ * clarified that:
+ *
+ * - Whole structures (or array of structures) cannot be bound all at
+ *   once using glTransformFeedbackVaryings().
+ *
+ * - Instead, the caller must apply transform feedback to individual
+ *   elements of structs, by using the "." character in the string
+ *   passed to glTransformFeedbackVaryings().
+ *
+ * - The intention is for the transform feedback API to behave
+ *   similary to glGetUniformLocation() and
+ *   glGetProgramResourceLocation().
+ *
+ * This test verifies proper operation of transform feedback varyings
+ * according to the above clarifications.
+ *
+ * Because of the subtle interactions between structs and arrays, this
+ * test contains several sub-tests, each concerned with verifying a
+ * particular combination of arrays and structs:
+ *
+ * - basic-struct: Each varying is a struct containing simple types
+ *   (e.g. vec4, float, mat3).
+ *
+ * - struct-whole-array: Each varying is a struct containing arrays of
+ *   simple types, and transform feedback is applied to whole arrays.
+ *
+ * - struct-array-elem: Each varying is a struct containing arrays of
+ *   simple types, and transform feedback is applied to individual
+ *   array elements.
+ *
+ * - array-struct: Each varying is an array of structs containing
+ *   simple types.
+ *
+ * - array-struct-whole-array: Each varying is an array of structs
+ *   containing arrays of simple types, and transform feedback is
+ *   applied to whole arrays within each struct.
+ *
+ * - array-struct-array-elem: Each varying is an array of structs
+ *   containing arrays of simple types, and transform feedback is
+ *   applied to individual array elements.
+ *
+ * - struct-struct: Each varying is a struct containing structs.
+ *
+ * - array-struct-array-struct: Each varying is an array of structs
+ *   containing arrays of structs.
+ *
+ * Each of these variants may be run in one of four modes:
+ *
+ * - error: attempt to specify invalid values for
+ *   glTransformFeedbackVaryings() and verify that the shaders fail to
+ *   link.
+ *
+ * - get: link the shaders and verify that the values returned by
+ *   glGetTransformFeedbackVarying() are correct.
+ *
+ * - run: draw using the shaders, and verify that (a) the values
+ *   stored in the transform feedback buffer are correct, and (b) the
+ *   values delivered to the fragment shader are correct.
+ *
+ * - run-no-fs: link with just a vertex shader and no fragment
+ *   shader*, and draw with GL_RASTERIZER_DISCARD enabled.  Verify
+ *   that the values stored in the transform feedback buffer are
+ *   correct.
+ *
+ * (*In GLES3, a fragment shader is required, so "run-no-fs" mode
+ * links to a generic do-nothing fragment shader).
+ *
+ * Furthermore, when testing on desktop GL, the optional command-line
+ * parameter "interface" may be given, to cause the test to be run
+ * using interface blocks.
+ */
+
+#include "piglit-util-gl-common.h"
+
+PIGLIT_GL_TEST_CONFIG_BEGIN
+
+#ifdef PIGLIT_USE_OPENGL
+	config.supports_gl_core_version = 32;
+#else /* PIGLIT_USE_OPENGL_ES3 */
+	config.supports_gl_es_version = 30;
+#endif
+	config.window_visual = PIGLIT_GL_VISUAL_DOUBLE | PIGLIT_GL_VISUAL_RGB | PIGLIT_GL_VISUAL_ALPHA;
+
+PIGLIT_GL_TEST_CONFIG_END
+
+
+#define MAX_VARYINGS 21
+#define NUM_VERTICES 6
+#define MAX_COMPONENTS 20
+#define VERTEX_ATTRIB_POS 0
+
+
+/**
+ * Header attached to the top of each shader when testing GLES 3.0.
+ */
+static const char gles3_header[] =
+	"#version 300 es\n"
+	"#define DECLARE_VARYING(DIR, TYPE, NAME) flat DIR TYPE NAME\n"
+	"#define VARYING(NAME) NAME\n";
+
+
+/**
+ * Header attached to the top of each shader when testing desktop GL
+ * and not using interface blocks.
+ */
+static const char desktop_header[] =
+	"#version 150\n"
+	"#define DECLARE_VARYING(DIR, TYPE, NAME) flat DIR TYPE NAME\n"
+	"#define VARYING(NAME) NAME\n";
+
+
+/**
+ * Header attached to the top of each shader when testing desktop GL
+ * and using interface blocks.
+ */
+static const char desktop_header_interface[] =
+	"#version 150\n"
+	"#define DECLARE_VARYING(DIR, TYPE, NAME) DIR Blk { flat TYPE NAME; } blk\n"
+	"#define VARYING(NAME) blk.NAME\n";
+
+
+/**
+ * Description of each possible sub-test.
+ */
+static struct test_desc {
+	/** Name of the test. */
+	const char *name;
+
+	/**
+	 * Vertex shader source text.  This will be concatenated with
+	 * one of the above "header" strings.  Accordingly, it should
+	 * use the macros DECLARE_VARYING() to declare a varying, and
+	 * VARYING() to access a varying.  (These macros expand
+	 * differently depending whether the test is using interface
+	 * blocks or not).
+	 */
+	const char *vs;
+
+	/**
+	 * Fragment shader source text.  As with the vertex shader,
+	 * this will be concatenated with one of the above "header"
+	 * strings.
+	 */
+	const char *fs;
+
+	/**
+	 * Names which, if passed to glTransformFeedbackVaryings(),
+	 * should result in a link error.  Terminated by a NULL
+	 * pointer.
+	 */
+	const char *bad_varyings[MAX_VARYINGS];
+
+	/**
+	 * Names which, if passed to glTransformFeedbackVaryings(),
+	 * should result in proper operation.  When using interface
+	 * blocks, each of these varying names will be prefixed with
+	 * "Blk.".  Terminated by a NULL pointer.
+	 */
+	const char *good_varyings[MAX_VARYINGS];
+
+	/**
+	 * Expected types which should be returned by
+	 * glGetTransformFeedbackVarying() for each of the varyings in
+	 * \c good_varyings.
+	 */
+	GLenum expected_types[MAX_VARYINGS];
+
+	/**
+	 * Expected sizes which should be returned by
+	 * glGetTransformFeedbackVarying() for each of the varyings in
+	 * \c good_varyings.
+	 */
+	unsigned expected_sizes[MAX_VARYINGS];
+
+	/**
+	 * Expected varying values which transform feedback should
+	 * capture when running this test (floating-point values
+	 * only).
+	 */
+	float expected_floats[MAX_COMPONENTS];
+
+	/**
+	 * Expected varying values which transform feedback should
+	 * capture when running this test (integral values only).
+	 */
+	int expected_ints[MAX_COMPONENTS];
+} tests[] = {
+	{
+		/* name */
+		"basic-struct",
+
+		/* vs */
+		"struct S { float a; vec4 b; mat3 c; ivec2 d; uvec3 e; };\n"
+		"in vec4 pos;\n"
+		"DECLARE_VARYING(out, S, v);\n"
+		"void main()\n"
+		"{\n"
+		"  gl_Position = pos;\n"
+		"  VARYING(v).a = 1.0;\n"
+		"  VARYING(v).b = vec4(2.0, 3.0, 4.0, 5.0);\n"
+		"  VARYING(v).c = mat3(6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0);\n"
+		"  VARYING(v).d = ivec2(15, 16);\n"
+		"  VARYING(v).e = uvec3(17, 18, 19);\n"
+		"}\n",
+
+		/* fs */
+		"struct S { float a; vec4 b; mat3 c; ivec2 d; uvec3 e; };\n"
+		"DECLARE_VARYING(in, S, v);\n"
+		"out vec4 color;\n"
+		"void main()\n"
+		"{\n"
+		"  bool pass = true;\n"
+		"  if (VARYING(v).a != 1.0) pass = false;\n"
+		"  if (VARYING(v).b != vec4(2.0, 3.0, 4.0, 5.0)) pass = false;\n"
+		"  if (VARYING(v).c != mat3(6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0)) pass = false;\n"
+		"  if (VARYING(v).d != ivec2(15, 16)) pass = false;\n"
+		"  if (VARYING(v).e != uvec3(17, 18, 19)) pass = false;\n"
+		"  if (pass)\n"
+		"    color = vec4(0.0, 1.0, 0.0, 1.0);\n"
+		"  else\n"
+		"    color = vec4(1.0, 0.0, 0.0, 1.0);\n"
+		"}\n",
+
+		/* bad_varyings */
+		{ "v", NULL },
+
+		/* good_varyings */
+		{ "v.a", "v.b", "v.c", "v.d", "v.e", NULL },
+
+		/* expected_types */
+		{ GL_FLOAT, GL_FLOAT_VEC4, GL_FLOAT_MAT3, GL_INT_VEC2,
+		  GL_UNSIGNED_INT_VEC3 },
+
+		/* expected_sizes */
+		{ 1, 1, 1, 1, 1 },
+
+		/* expected_floats */
+		{ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0,
+		  12.0, 13.0, 14.0 },
+
+		/* expected_ints */
+		{ 15, 16, 17, 18, 19 }
+	},
+	{
+		/* name */
+		"struct-whole-array",
+
+		/* vs */
+		"struct S { uvec4[4] a; vec2[2] b; int[3] c; };\n"
+		"in vec4 pos;\n"
+		"DECLARE_VARYING(out, S, v);\n"
+		"void main()\n"
+		"{\n"
+		"  gl_Position = pos;\n"
+		"  for (int i = 0; i < 4; i++) {\n"
+		"    if (i < 4) VARYING(v).a[i] = uvec4(100, 200, 300, 400) + uint(i);\n"
+		"    if (i < 2) VARYING(v).b[i] = vec2(500.0, 600.0) + float(i);\n"
+		"    if (i < 3) VARYING(v).c[i] = 700 + i;\n"
+		"  }\n"
+		"}\n",
+
+		/* fs */
+		"struct S { uvec4[4] a; vec2[2] b; int[3] c; };\n"
+		"DECLARE_VARYING(in, S, v);\n"
+		"out vec4 color;\n"
+		"void main()\n"
+		"{\n"
+		"  bool pass = true;\n"
+		"  for (int i = 0; i < 3; i++) {\n"
+		"    if (i < 4 && VARYING(v).a[i] != uvec4(100, 200, 300, 400) + uint(i)) pass = false;\n"
+		"    if (i < 2 && VARYING(v).b[i] != vec2(500.0, 600.0) + float(i)) pass = false;\n"
+		"    if (i < 3 && VARYING(v).c[i] != 700 + i) pass = false;\n"
+		"  }\n"
+		"  if (pass)\n"
+		"    color = vec4(0.0, 1.0, 0.0, 1.0);\n"
+		"  else\n"
+		"    color = vec4(1.0, 0.0, 0.0, 1.0);\n"
+		"}\n",
+
+		/* bad_varyings */
+		{ "v", NULL },
+
+		/* good_varyings */
+		{ "v.a", "v.b", "v.c", NULL },
+
+		/* expected_types */
+		{ GL_UNSIGNED_INT_VEC4, GL_FLOAT_VEC2, GL_INT },
+
+		/* expected_sizes */
+		{ 4, 2, 3 },
+
+		/* expected_floats */
+		{ 500.0, 600.0, 501.0, 601.0 },
+
+		/* expected_ints */
+		{ 100, 200, 300, 400, 101, 201, 301, 401, 102, 202, 302, 402,
+		  103, 203, 303, 403, 700, 701, 702 }
+	},
+	{
+		/* name */
+		"struct-array-elem",
+
+		/* vs */
+		"struct S { ivec4[2] a; uint[4] b; vec3[3] c; };\n"
+		"in vec4 pos;\n"
+		"DECLARE_VARYING(out, S, v);\n"
+		"void main()\n"
+		"{\n"
+		"  gl_Position = pos;\n"
+		"  for (int i = 0; i < 4; i++) {\n"
+		"    if (i < 2) VARYING(v).a[i] = ivec4(100, 200, 300, 400) + i;\n"
+		"    if (i < 4) VARYING(v).b[i] = 500u + uint(i);\n"
+		"    if (i < 3) VARYING(v).c[i] = vec3(600.0, 700.0, 800.0) + float(i);\n"
+		"  }\n"
+		"}\n",
+
+		/* fs */
+		"struct S { ivec4[2] a; uint[4] b; vec3[3] c; };\n"
+		"DECLARE_VARYING(in, S, v);\n"
+		"out vec4 color;\n"
+		"void main()\n"
+		"{\n"
+		"  bool pass = true;\n"
+		"  for (int i = 0; i < 3; i++) {\n"
+		"    if (i < 2 && VARYING(v).a[i] != ivec4(100, 200, 300, 400) + i) pass = false;\n"
+		"    if (i < 4 && VARYING(v).b[i] != 500u + uint(i)) pass = false;\n"
+		"    if (i < 3 && VARYING(v).c[i] != vec3(600.0, 700.0, 800.0) + float(i)) pass = false;\n"
+		"  }\n"
+		"  if (pass)\n"
+		"    color = vec4(0.0, 1.0, 0.0, 1.0);\n"
+		"  else\n"
+		"    color = vec4(1.0, 0.0, 0.0, 1.0);\n"
+		"}\n",
+
+		/* bad_varyings */
+		{ "v", NULL },
+
+		/* good_varyings */
+		{ "v.a[0]", "v.a[1]", "v.b[0]", "v.b[1]", "v.b[2]", "v.b[3]",
+		  "v.c[0]", "v.c[1]", "v.c[2]", NULL },
+
+		/* expected_types */
+		{ GL_INT_VEC4, GL_INT_VEC4, GL_UNSIGNED_INT, GL_UNSIGNED_INT,
+		  GL_UNSIGNED_INT, GL_UNSIGNED_INT, GL_FLOAT_VEC3,
+		  GL_FLOAT_VEC3, GL_FLOAT_VEC3 },
+
+		/* expected_sizes */
+		{ 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+
+		/* expected_floats */
+		{ 600.0, 700.0, 800.0, 601.0, 701.0, 801.0, 602.0, 702.0, 802.0 },
+
+		/* expected_ints */
+		{ 100, 200, 300, 400, 101, 201, 301, 401, 500, 501, 502, 503 }
+	},
+	{
+		/* name */
+		"array-struct",
+
+		/* vs */
+		"struct S { mat2 a; ivec3 b; uvec2 c; };\n"
+		"in vec4 pos;\n"
+		"DECLARE_VARYING(out, S[3], v);\n"
+		"void main()\n"
+		"{\n"
+		"  gl_Position = pos;\n"
+		"  for (int i = 0; i < 3; i++) {\n"
+		"    VARYING(v)[i].a = mat2(100.0, 200.0, 300.0, 400.0) + float(i);\n"
+		"    VARYING(v)[i].b = ivec3(500, 600, 700) + i;\n"
+		"    VARYING(v)[i].c = uvec2(800, 900) + uint(i);\n"
+		"  }\n"
+		"}\n",
+
+		/* fs */
+		"struct S { mat2 a; ivec3 b; uvec2 c; };\n"
+		"DECLARE_VARYING(in, S[3], v);\n"
+		"out vec4 color;\n"
+		"void main()\n"
+		"{\n"
+		"  bool pass = true;\n"
+		"  for (int i = 0; i < 3; i++) {\n"
+		"    if (VARYING(v)[i].a != mat2(100.0, 200.0, 300.0, 400.0) + float(i)) pass = false;\n"
+		"    if (VARYING(v)[i].b != ivec3(500, 600, 700) + i) pass = false;\n"
+		"    if (VARYING(v)[i].c != uvec2(800, 900) + uint(i)) pass = false;\n"
+		"  }\n"
+		"  if (pass)\n"
+		"    color = vec4(0.0, 1.0, 0.0, 1.0);\n"
+		"  else\n"
+		"    color = vec4(1.0, 0.0, 0.0, 1.0);\n"
+		"}\n",
+
+		/* bad_varyings */
+		{ "v", "v[0]", "v[1]", "v[2]", "v.a", "v.b", "v.c", NULL },
+
+		/* good_varyings */
+		{ "v[0].a", "v[0].b", "v[0].c", "v[1].a", "v[1].b", "v[1].c",
+		  "v[2].a", "v[2].b", "v[2].c", NULL },
+
+		/* expected_types */
+		{ GL_FLOAT_MAT2, GL_INT_VEC3, GL_UNSIGNED_INT_VEC2,
+		  GL_FLOAT_MAT2, GL_INT_VEC3, GL_UNSIGNED_INT_VEC2,
+		  GL_FLOAT_MAT2, GL_INT_VEC3, GL_UNSIGNED_INT_VEC2 },
+
+		/* expected_sizes */
+		{ 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+
+		/* expected_floats */
+		{ 100.0, 200.0, 300.0, 400.0, 101.0, 201.0, 301.0, 401.0,
+		  102.0, 202.0, 302.0, 402.0 },
+
+		/* expected_ints */
+		{ 500, 600, 700, 800, 900, 501, 601, 701, 801, 901, 502, 602,
+		  702, 802, 902 }
+	},
+	{
+		/* name */
+		"array-struct-whole-array",
+
+		/* vs */
+		"struct S { int[2] a; int[3] b; };\n"
+		"in vec4 pos;\n"
+		"DECLARE_VARYING(out, S[4], v);\n"
+		"void main()\n"
+		"{\n"
+		"  gl_Position = pos;\n"
+		"  for (int i = 0; i < 4; i++) {\n"
+		"    for (int j = 0; j < 3; j++) {\n"
+		"      if (j < 2) VARYING(v)[i].a[j] = 100 * i + 10 * j + 1;\n"
+		"      if (j < 3) VARYING(v)[i].b[j] = 100 * i + 10 * j + 2;\n"
+		"    }\n"
+		"  }\n"
+		"}\n",
+
+		/* fs */
+		"struct S { int[2] a; int[3] b; };\n"
+		"DECLARE_VARYING(in, S[4], v);\n"
+		"out vec4 color;\n"
+		"void main()\n"
+		"{\n"
+		"  bool pass = true;\n"
+		"  for (int i = 0; i < 4; i++) {\n"
+		"    for (int j = 0; j < 3; j++) {\n"
+		"      if (j < 2 && VARYING(v)[i].a[j] != 100 * i + 10 * j + 1) pass = false;\n"
+		"      if (j < 3 && VARYING(v)[i].b[j] != 100 * i + 10 * j + 2) pass = false;\n"
+		"    }\n"
+		"  }\n"
+		"  if (pass)\n"
+		"    color = vec4(0.0, 1.0, 0.0, 1.0);\n"
+		"  else\n"
+		"    color = vec4(1.0, 0.0, 0.0, 1.0);\n"
+		"}\n",
+
+		/* bad_varyings */
+		{ "v", "v[0]", "v[1]", "v[2]", "v[3]", "v.a", "v.b", NULL },
+
+		/* good_varyings */
+		{ "v[0].a", "v[0].b", "v[1].a", "v[1].b", "v[2].a", "v[2].b",
+		  "v[3].a", "v[3].b", NULL },
+
+		/* expected_types */
+		{ GL_INT, GL_INT, GL_INT, GL_INT, GL_INT, GL_INT, GL_INT,
+		  GL_INT },
+
+		/* expected_sizes */
+		{ 2, 3, 2, 3, 2, 3, 2, 3 },
+
+		/* expected_floats */
+		{ },
+
+		/* expected_ints */
+		{ 1, 11, 2, 12, 22, 101, 111, 102, 112, 122, 201, 211, 202,
+		  212, 222, 301, 311, 302, 312, 322 }
+	},
+	{
+		/* name */
+		"array-struct-array-elem",
+
+		/* vs */
+		"struct S { int[2] a; int[3] b; };\n"
+		"in vec4 pos;\n"
+		"DECLARE_VARYING(out, S[4], v);\n"
+		"void main()\n"
+		"{\n"
+		"  gl_Position = pos;\n"
+		"  for (int i = 0; i < 4; i++) {\n"
+		"    for (int j = 0; j < 3; j++) {\n"
+		"      if (j < 2) VARYING(v)[i].a[j] = 100 * i + 10 * j + 1;\n"
+		"      if (j < 3) VARYING(v)[i].b[j] = 100 * i + 10 * j + 2;\n"
+		"    }\n"
+		"  }\n"
+		"}\n",
+
+		/* fs */
+		"struct S { int[2] a; int[3] b; };\n"
+		"DECLARE_VARYING(in, S[4], v);\n"
+		"out vec4 color;\n"
+		"void main()\n"
+		"{\n"
+		"  bool pass = true;\n"
+		"  for (int i = 0; i < 4; i++) {\n"
+		"    for (int j = 0; j < 3; j++) {\n"
+		"      if (j < 2 && VARYING(v)[i].a[j] != 100 * i + 10 * j + 1) pass = false;\n"
+		"      if (j < 3 && VARYING(v)[i].b[j] != 100 * i + 10 * j + 2) pass = false;\n"
+		"    }\n"
+		"  }\n"
+		"  if (pass)\n"
+		"    color = vec4(0.0, 1.0, 0.0, 1.0);\n"
+		"  else\n"
+		"    color = vec4(1.0, 0.0, 0.0, 1.0);\n"
+		"}\n",
+
+		/* bad_varyings */
+		{ "v", "v[0]", "v[1]", "v[2]", "v[3]", "v.a", "v.b", NULL },
+
+		/* good_varyings */
+		{ "v[0].a[0]", "v[0].a[1]", "v[0].b[0]", "v[0].b[1]",
+		  "v[0].b[2]", "v[1].a[0]", "v[1].a[1]", "v[1].b[0]",
+		  "v[1].b[1]", "v[1].b[2]", "v[2].a[0]", "v[2].a[1]",
+		  "v[2].b[0]", "v[2].b[1]", "v[2].b[2]", "v[3].a[0]",
+		  "v[3].a[1]", "v[3].b[0]", "v[3].b[1]", "v[3].b[2]", NULL },
+
+		/* expected_types */
+		{ GL_INT, GL_INT, GL_INT, GL_INT, GL_INT, GL_INT, GL_INT,
+		  GL_INT, GL_INT, GL_INT, GL_INT, GL_INT, GL_INT, GL_INT,
+		  GL_INT, GL_INT, GL_INT, GL_INT, GL_INT, GL_INT },
+
+		/* expected_sizes */
+		{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+
+		/* expected_floats */
+		{ },
+
+		/* expected_ints */
+		{ 1, 11, 2, 12, 22, 101, 111, 102, 112, 122, 201, 211, 202,
+		  212, 222, 301, 311, 302, 312, 322 }
+	},
+	{
+		/* name */
+		"struct-struct",
+
+		/* vs */
+		"struct S { int a; float b; };\n"
+		"struct T { float c; int d; };\n"
+		"struct U { S e; T f; };\n"
+		"in vec4 pos;\n"
+		"DECLARE_VARYING(out, U, v);\n"
+		"void main()\n"
+		"{\n"
+		"  gl_Position = pos;\n"
+		"  VARYING(v).e.a = 1;\n"
+		"  VARYING(v).e.b = 2.0;\n"
+		"  VARYING(v).f.c = 3.0;\n"
+		"  VARYING(v).f.d = 4;\n"
+		"}\n",
+
+		/* fs */
+		"struct S { int a; float b; };\n"
+		"struct T { float c; int d; };\n"
+		"struct U { S e; T f; };\n"
+		"DECLARE_VARYING(in, U, v);\n"
+		"out vec4 color;\n"
+		"void main()\n"
+		"{\n"
+		"  bool pass = true;\n"
+		"  if (VARYING(v).e.a != 1) pass = false;\n"
+		"  if (VARYING(v).e.b != 2.0) pass = false;\n"
+		"  if (VARYING(v).f.c != 3.0) pass = false;\n"
+		"  if (VARYING(v).f.d != 4) pass = false;\n"
+		"  if (pass)\n"
+		"    color = vec4(0.0, 1.0, 0.0, 1.0);\n"
+		"  else\n"
+		"    color = vec4(1.0, 0.0, 0.0, 1.0);\n"
+		"}\n",
+
+		/* bad_varyings */
+		{ "v", "v.e", "v.f", NULL },
+
+		/* good_varyings */
+		{ "v.e.a", "v.e.b", "v.f.c", "v.f.d", NULL },
+
+		/* expected_types */
+		{ GL_INT, GL_FLOAT, GL_FLOAT, GL_INT },
+
+		/* expected_sizes */
+		{ 1, 1, 1, 1 },
+
+		/* expected_floats */
+		{ 2.0, 3.0 },
+
+		/* expected_ints */
+		{ 1, 4 }
+	},
+	{
+		/* name */
+		"array-struct-array-struct",
+
+		/* vs */
+		"struct S { int a; float b; };\n"
+		"struct T { float c; int d; };\n"
+		"struct U { S[2] e; T[2] f; };\n"
+		"in vec4 pos;\n"
+		"DECLARE_VARYING(out, U[2], v);\n"
+		"void main()\n"
+		"{\n"
+		"  gl_Position = pos;\n"
+		"  for (int i = 0; i < 2; i++) {\n"
+		"    for (int j = 0; j < 2; j++) {\n"
+		"      VARYING(v)[i].e[j].a = 100 * i + 10 * j + 1;\n"
+		"      VARYING(v)[i].e[j].b = float(100 * i + 10 * j + 2);\n"
+		"      VARYING(v)[i].f[j].c = float(100 * i + 10 * j + 3);\n"
+		"      VARYING(v)[i].f[j].d = 100 * i + 10 * j + 4;\n"
+		"    }\n"
+		"  }\n"
+		"}\n",
+
+		/* fs */
+		"struct S { int a; float b; };\n"
+		"struct T { float c; int d; };\n"
+		"struct U { S[2] e; T[2] f; };\n"
+		"DECLARE_VARYING(in, U[2], v);\n"
+		"out vec4 color;\n"
+		"void main()\n"
+		"{\n"
+		"  bool pass = true;\n"
+		"  for (int i = 0; i < 2; i++) {\n"
+		"    for (int j = 0; j < 2; j++) {\n"
+		"      if (VARYING(v)[i].e[j].a != 100 * i + 10 * j + 1) pass = false;\n"
+		"      if (VARYING(v)[i].e[j].b != float(100 * i + 10 * j + 2)) pass = false;\n"
+		"      if (VARYING(v)[i].f[j].c != float(100 * i + 10 * j + 3)) pass = false;\n"
+		"      if (VARYING(v)[i].f[j].d != 100 * i + 10 * j + 4) pass = false;\n"
+		"    }\n"
+		"  }\n"
+		"  if (pass)\n"
+		"    color = vec4(0.0, 1.0, 0.0, 1.0);\n"
+		"  else\n"
+		"    color = vec4(1.0, 0.0, 0.0, 1.0);\n"
+		"}\n",
+
+		/* bad_varyings */
+		{ "v", "v[0]", "v[1]", "v[0].e", "v[0].f", "v[1].e",
+		  "v[1].f", "v[0].e[0]", "v[0].e[1]", "v[0].f[0]", "v[0].f[1]",
+		  "v[1].e[0]", "v[1].e[1]", "v[1].f[0]", "v[1].f[1]", "v.e.a",
+		  "v.e.b", "v.f.c", "v.f.d", NULL },
+
+		/* good_varyings */
+		{ "v[0].e[0].a", "v[0].e[1].a", "v[1].e[0].a", "v[1].e[1].a",
+		  "v[0].e[0].b", "v[0].e[1].b", "v[1].e[0].b", "v[1].e[1].b",
+		  "v[0].f[0].c", "v[0].f[1].c", "v[1].f[0].c", "v[1].f[1].c",
+		  "v[0].f[0].d", "v[0].f[1].d", "v[1].f[0].d", "v[1].f[1].d",
+		  NULL },
+
+		/* expected_types */
+		{ GL_INT, GL_INT, GL_INT, GL_INT, GL_FLOAT, GL_FLOAT, GL_FLOAT,
+		  GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_INT,
+		  GL_INT, GL_INT, GL_INT },
+
+		/* expected_sizes */
+		{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+
+		/* expected_floats */
+		{ 2.0, 12.0, 102.0, 112.0, 3.0, 13.0, 103.0, 113.0 },
+
+		/* expected_ints */
+		{ 1, 11, 101, 111, 4, 14, 104, 114 }
+	},
+};
+
+
+#ifdef PIGLIT_USE_OPENGL_ES3
+/**
+ * Generic do-nothing fragment shader used when running tests in
+ * "run-no-fs" mode on GLES3, since GLES3 always requires a fragment
+ * shader to be present.
+ */
+static const char *generic_gles3_fs_text =
+	"out vec4 color;\n"
+	"void main()\n"
+	"{\n"
+	"  color = vec4(0.5);\n"
+	"}\n";
+#endif
+
+
+static struct test_desc *test = NULL;
+static GLuint prog;
+static bool use_interface_blocks = false;
+
+
+/**
+ * Count the number of strings in the given NULL-terminated array of
+ * strings.
+ */
+static unsigned
+count_strings(const char * const *strings)
+{
+	unsigned i;
+	for (i = 0; ; i++) {
+		if (strings[i] == NULL)
+			return i;
+	}
+}
+
+
+/**
+ * Choose which header should be prepended to each of the shaders
+ * being tested, based on whether GL or GLSL is being used, and based
+ * on whether the test uses interface blocks.
+ */
+static const char *
+choose_header()
+{
+#ifdef PIGLIT_USE_OPENGL
+	(void) gles3_header;
+	if (use_interface_blocks)
+		return desktop_header_interface;
+	else
+		return desktop_header;
+#else /* PIGLIT_USE_OPENGL_ES3 */
+	(void) desktop_header_interface;
+	(void) desktop_header;
+	return gles3_header;
+#endif
+}
+
+
+/**
+ * Attach the appropriate header to the shader and compile it.
+ */
+static GLuint
+compile_shader(GLenum target, const char *shader_text)
+{
+	const char *header = choose_header();
+	unsigned len = strlen(header) + strlen(shader_text) + 1;
+	char *concatenated_text = malloc(len);
+	GLuint shader;
+	strcpy(concatenated_text, header);
+	strcat(concatenated_text, shader_text);
+	shader = piglit_compile_shader_text(target, concatenated_text);
+	free(concatenated_text);
+	return shader;
+}
+
+
+/**
+ * Given an array of strings representing the names of varyings,
+ * return a newly allocated array with \c prefix prepended to each
+ * varying name.
+ */
+static const char **
+prepend_varyings(const char *prefix, const char * const *varyings)
+{
+	unsigned prefix_len = strlen(prefix);
+	unsigned num_varyings = count_strings(varyings);
+	char **result = calloc(num_varyings + 1, sizeof(char *));
+	unsigned i;
+	for (i = 0; i < num_varyings; i++) {
+		unsigned varying_len = strlen(varyings[i]);
+		result[i] = malloc(prefix_len + varying_len + 1);
+		strcpy(result[i], prefix);
+		strcat(result[i], varyings[i]);
+	}
+	result[num_varyings] = NULL;
+	return (const char **) result;
+}
+
+
+/**
+ * Free an array allocated by \c prepend_varyings.
+ */
+static void
+free_varyings(const char **varyings)
+{
+	unsigned i;
+	for (i = 0; varyings[i] != NULL; i++)
+		free((char *) varyings[i]);
+	free(varyings);
+}
+
+
+/**
+ * Link the appropriate set of shaders for running a positive test,
+ * calling glTransformFeedbackVaryings() to set up transform feedback.
+ */
+static void
+link_shaders(bool use_fs)
+{
+	GLuint vs;
+	const char **varyings;
+	prog = glCreateProgram();
+	vs = compile_shader(GL_VERTEX_SHADER, test->vs);
+	glAttachShader(prog, vs);
+	glDeleteShader(vs);
+	if (use_fs) {
+		GLuint fs = compile_shader(GL_FRAGMENT_SHADER, test->fs);
+		glAttachShader(prog, fs);
+		glDeleteShader(fs);
+	} else {
+#ifdef PIGLIT_USE_OPENGL_ES3
+		GLuint fs = compile_shader(GL_FRAGMENT_SHADER, generic_gles3_fs_text);
+		glAttachShader(prog, fs);
+		glDeleteShader(fs);
+#endif
+	}
+	if (use_interface_blocks)
+		varyings = prepend_varyings("Blk.", test->good_varyings);
+	else
+		varyings = test->good_varyings;
+	glTransformFeedbackVaryings(prog, count_strings(test->good_varyings),
+				    (const GLchar **) varyings,
+				    GL_INTERLEAVED_ATTRIBS);
+	if (use_interface_blocks)
+		free_varyings(varyings);
+	if (!piglit_check_gl_error(GL_NO_ERROR)) {
+		glDeleteProgram(prog);
+		piglit_report_result(PIGLIT_FAIL);
+	}
+	glBindAttribLocation(prog, VERTEX_ATTRIB_POS, "pos");
+	glLinkProgram(prog);
+	if (!piglit_link_check_status(prog)) {
+		glDeleteProgram(prog);
+		piglit_report_result(PIGLIT_FAIL);
+	}
+}
+
+
+/**
+ * Verify that passing the name \c varying to
+ * glTransformFeedbackVaryings() produces a link error.
+ *
+ * If it does not, a description of the problem is printed and false
+ * is returned.
+ */
+static bool
+test_bad_varying(GLuint vs, GLuint fs, const char *varying)
+{
+	GLint ok;
+
+	prog = glCreateProgram();
+	glAttachShader(prog, vs);
+	glAttachShader(prog, fs);
+	glTransformFeedbackVaryings(prog, 1, &varying, GL_INTERLEAVED_ATTRIBS);
+	if (!piglit_check_gl_error(GL_NO_ERROR))
+		goto fail;
+	glLinkProgram(prog);
+	glGetProgramiv(prog, GL_LINK_STATUS, &ok);
+	if (ok) {
+		printf("Varying %s linked successfully, should have failed.\n",
+		       varying);
+		goto fail;
+	}
+	/* Link failure shouldn't produce a GL error */
+	if (!piglit_check_gl_error(GL_NO_ERROR))
+		goto fail;
+	glDeleteProgram(prog);
+	prog = 0;
+	if (!piglit_check_gl_error(GL_NO_ERROR))
+		goto fail;
+	return true;
+
+ fail:
+	if (prog != 0)
+		glDeleteProgram(prog);
+	return false;
+}
+
+
+/**
+ * Verify that "bad" varying names produces the expected link error.
+ *
+ * When using interface blocks, this function also verifies that (a)
+ * "bad" varying names produce a link error if they are prepended with
+ * "Blk.", and (b) "good" varying names produce a link error if they
+ * are *not* prepended with "Blk.".
+ */
+static enum piglit_result
+test_errors()
+{
+	unsigned i;
+	unsigned num_bad_varyings = count_strings(test->bad_varyings);
+	unsigned num_good_varyings = count_strings(test->good_varyings);
+	GLuint vs, fs;
+	bool pass = true;
+
+	prog = 0;
+	vs = compile_shader(GL_VERTEX_SHADER, test->vs);
+	fs = compile_shader(GL_FRAGMENT_SHADER, test->fs);
+
+	/* Test one bad varying at a time to make sure they all
+	 * produce the proper error.
+	 */
+	for (i = 0; i < num_bad_varyings; i++)
+		pass = test_bad_varying(vs, fs, test->bad_varyings[i]) && pass;
+
+	if (use_interface_blocks) {
+		/* Test that the "bad" varyings fail if prepended with
+		 * "Blk."
+		 */
+		const char **varyings
+			= prepend_varyings("Blk.", test->bad_varyings);
+		for (i = 0; i < num_bad_varyings; i++)
+			pass = test_bad_varying(vs, fs, varyings[i]) && pass;
+		free_varyings(varyings);
+
+		/* Test that the "good" varyings fail if *not*
+		 * prepended with "Blk."
+		 */
+		for (i = 0; i < num_good_varyings; i++) {
+			pass = test_bad_varying(vs, fs, test->good_varyings[i])
+				&& pass;
+		}
+	}
+
+	glDeleteShader(vs);
+	glDeleteShader(fs);
+
+	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
+}
+
+
+/**
+ * Verify that glGetTransformFeedbackVarying() returns the proper
+ * information for all "good" varying names.
+ *
+ * The program should already be linked and stored in the global \c
+ * prog.
+ */
+static enum piglit_result
+test_gets()
+{
+	unsigned i;
+	unsigned num_good_varyings = count_strings(test->good_varyings);
+	const char **varyings;
+	bool pass = true;
+
+	if (use_interface_blocks)
+		varyings = prepend_varyings("Blk.", test->good_varyings);
+	else
+		varyings = test->good_varyings;
+
+	for (i = 0; i < num_good_varyings; i++) {
+		const char *exp_name = varyings[i];
+		GLsizei exp_length = strlen(exp_name);
+		GLsizei exp_size = test->expected_sizes[i];
+		GLenum exp_type = test->expected_types[i];
+		GLsizei length;
+		GLsizei size;
+		GLenum type;
+		char name[100];
+		glGetTransformFeedbackVarying(prog, i, sizeof(name), &length,
+					      &size, &type, name);
+		if (length != exp_length || size != exp_size
+		    || type != exp_type || strcmp(name, exp_name) != 0) {
+			pass = false;
+			printf("glGetTransformFeedbackVarying() returned "
+			       "unexpected data for varying %u:\n", i);
+			printf("  length: expected %u, got %u\n",
+			       exp_length, length);
+			printf("  size: expected %u, got %u\n",
+			       exp_size, size);
+			printf("  type: expected %u (%s), got %u (%s)\n",
+			       exp_type, piglit_get_gl_enum_name(exp_type),
+			       type, piglit_get_gl_enum_name(type));
+			printf("  name: expected %s, got %s\n",
+			       exp_name, name);
+		}
+	}
+
+	if (use_interface_blocks)
+		free_varyings(varyings);
+
+	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
+}
+
+
+/**
+ * Compute the number of varying slots occupied by a given type.
+ */
+static unsigned
+size_of_type(GLenum type)
+{
+	switch (type) {
+	case GL_FLOAT:             return  1;
+	case GL_FLOAT_VEC2:        return  2;
+	case GL_FLOAT_VEC3:        return  3;
+	case GL_FLOAT_VEC4:        return  4;
+	case GL_FLOAT_MAT2:        return  4;
+	case GL_FLOAT_MAT2x3:      return  6;
+	case GL_FLOAT_MAT2x4:      return  8;
+	case GL_FLOAT_MAT3x2:      return  6;
+	case GL_FLOAT_MAT3:        return  9;
+	case GL_FLOAT_MAT3x4:      return 12;
+	case GL_FLOAT_MAT4x2:      return  8;
+	case GL_FLOAT_MAT4x3:      return 12;
+	case GL_FLOAT_MAT4:        return 16;
+	case GL_INT:               return  1;
+	case GL_INT_VEC2:          return  2;
+	case GL_INT_VEC3:          return  3;
+	case GL_INT_VEC4:          return  4;
+	case GL_UNSIGNED_INT:      return  1;
+	case GL_UNSIGNED_INT_VEC2: return  2;
+	case GL_UNSIGNED_INT_VEC3: return  3;
+	case GL_UNSIGNED_INT_VEC4: return  4;
+	default:
+		printf("Unexpected type: %u (%s)\n", type,
+		       piglit_get_gl_enum_name(type));
+		piglit_report_result(PIGLIT_FAIL);
+		return 0;
+	}
+}
+
+
+/**
+ * Determine whether the given type contains floating-point values.
+ */
+static bool
+is_floating_type(GLenum type)
+{
+	switch (type) {
+	case GL_FLOAT:
+	case GL_FLOAT_VEC2:
+	case GL_FLOAT_VEC3:
+	case GL_FLOAT_VEC4:
+	case GL_FLOAT_MAT2:
+	case GL_FLOAT_MAT2x3:
+	case GL_FLOAT_MAT2x4:
+	case GL_FLOAT_MAT3x2:
+	case GL_FLOAT_MAT3:
+	case GL_FLOAT_MAT3x4:
+	case GL_FLOAT_MAT4x2:
+	case GL_FLOAT_MAT4x3:
+	case GL_FLOAT_MAT4:
+		return true;
+	case GL_INT:
+	case GL_INT_VEC2:
+	case GL_INT_VEC3:
+	case GL_INT_VEC4:
+	case GL_UNSIGNED_INT:
+	case GL_UNSIGNED_INT_VEC2:
+	case GL_UNSIGNED_INT_VEC3:
+	case GL_UNSIGNED_INT_VEC4:
+		return false;
+	default:
+		printf("Unexpected type: %u (%s)\n", type,
+		       piglit_get_gl_enum_name(type));
+		piglit_report_result(PIGLIT_FAIL);
+		return false;
+	}
+}
+
+
+/**
+ * Compute the expected number of transform feedback outputs for the
+ * current test.  This is used to size the transform feedback buffer.
+ */
+static unsigned
+count_outputs()
+{
+	unsigned num_good_varyings = count_strings(test->good_varyings);
+	unsigned i;
+	unsigned num_outputs = 0;
+
+	for (i = 0; i < num_good_varyings; i++) {
+		num_outputs += size_of_type(test->expected_types[i])
+			* test->expected_sizes[i];
+	}
+	return num_outputs;
+}
+
+
+/**
+ * Check that the buffer pointed to by \c readback contains the
+ * expected transform feedback output data.
+ */
+static bool
+check_outputs(const void *readback)
+{
+	const float *readback_f = readback;
+	const int *readback_i = readback;
+	unsigned num_good_varyings = count_strings(test->good_varyings);
+	unsigned i;
+	unsigned output_component = 0;
+	unsigned float_index = 0;
+	unsigned int_index = 0;
+	bool pass = true;
+
+	for (i = 0; i < num_good_varyings; i++) {
+		const char *varying_name = test->good_varyings[i];
+		unsigned varying_size = test->expected_sizes[i]
+			* size_of_type(test->expected_types[i]);
+		if (is_floating_type(test->expected_types[i])) {
+			unsigned j;
+			for (j = 0; j < varying_size; j++) {
+				float actual = readback_f[output_component];
+				float expected
+					= test->expected_floats[float_index];
+				if (actual != expected) {
+					printf("Output %s element %u: "
+					       "expected %f, got %f\n",
+					       varying_name, j, expected,
+					       actual);
+					pass = false;
+				}
+				output_component++;
+				float_index++;
+			}
+		} else {
+			unsigned j;
+			for (j = 0; j < varying_size; j++) {
+				int actual = readback_i[output_component];
+				int expected = test->expected_ints[int_index];
+				if (actual != expected) {
+					printf("Output %s element %u: "
+					       "expected %i, got %i\n",
+					       varying_name, j, expected,
+					       actual);
+					pass = false;
+				}
+				output_component++;
+				int_index++;
+			}
+		}
+	}
+
+	return pass;
+}
+
+
+/**
+ * Call glDrawArrays, passing the given vertex data using a VAO and a
+ * VBO.
+ */
+static void
+draw_arrays(const void *verts, unsigned verts_size)
+{
+	GLuint vao, vbo;
+	glGenVertexArrays(1, &vao);
+	glBindVertexArray(vao);
+	glGenBuffers(1, &vbo);
+	glBindBuffer(GL_ARRAY_BUFFER, vbo);
+	glBufferData(GL_ARRAY_BUFFER, verts_size, verts, GL_STREAM_DRAW);
+
+	glVertexAttribPointer(VERTEX_ATTRIB_POS, 4, GL_FLOAT, GL_FALSE, 0,
+			      NULL);
+	glEnableVertexAttribArray(VERTEX_ATTRIB_POS);
+
+	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+	glDisableVertexAttribArray(VERTEX_ATTRIB_POS);
+
+	glBindBuffer(GL_ARRAY_BUFFER, 0);
+	glDeleteBuffers(1, &vbo);
+	glBindVertexArray(0);
+	glDeleteVertexArrays(1, &vao);
+}
+
+
+/**
+ * Draw a rectangle using the given coordinates.
+ *
+ * In an ideal world, instead of using this function we would do the
+ * drawing using piglit_draw_rect(), however that function doesn't use
+ * VBO's or VAO's, and hence isn't compatible with core contexts.
+ */
+static void
+draw_rect(float x, float y, float w, float h)
+{
+	float verts[4][4];
+
+	verts[0][0] = x;
+	verts[0][1] = y;
+	verts[0][2] = 0.0;
+	verts[0][3] = 1.0;
+	verts[1][0] = x + w;
+	verts[1][1] = y;
+	verts[1][2] = 0.0;
+	verts[1][3] = 1.0;
+	verts[2][0] = x;
+	verts[2][1] = y + h;
+	verts[2][2] = 0.0;
+	verts[2][3] = 1.0;
+	verts[3][0] = x + w;
+	verts[3][1] = y + h;
+	verts[3][2] = 0.0;
+	verts[3][3] = 1.0;
+
+	draw_arrays(verts, sizeof(verts));
+}
+
+
+/**
+ * Render using the program and verify that it outputs the proper data
+ * to the transform feedback buffer.
+ *
+ * The program should already be linked and stored in the global \c
+ * prog.
+ */
+static enum piglit_result
+test_xfb(bool use_rasterizer_discard)
+{
+	GLuint buf;
+	void *initial_data;
+	const void *readback;
+	unsigned expected_num_outputs = count_outputs();
+	unsigned buf_size
+		= expected_num_outputs * NUM_VERTICES * sizeof(float);
+	bool pass = true;
+
+	/* Create transform feedback buffer and pre-load it with
+	 * garbage.
+	 */
+	glGenBuffers(1, &buf);
+	initial_data = malloc(buf_size);
+	memset(initial_data, 0xcc, buf_size);
+	glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+	glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, buf_size, initial_data,
+		     GL_STREAM_READ);
+	free(initial_data);
+
+	/* Draw a quad filling the window, with transform feedback
+	 * enabled.
+	 */
+	glUseProgram(prog);
+	glBeginTransformFeedback(GL_TRIANGLES);
+	if (use_rasterizer_discard)
+		glEnable(GL_RASTERIZER_DISCARD);
+	draw_rect(-1, -1, 2, 2);
+	if (use_rasterizer_discard)
+		glDisable(GL_RASTERIZER_DISCARD);
+	glEndTransformFeedback();
+	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;
+
+	/* Inspect transform feedback output. */
+	readback = glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buf_size,
+				    GL_MAP_READ_BIT);
+	pass = check_outputs(readback) && pass;
+	glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
+
+	glDeleteBuffers(1, &buf);
+
+	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
+}
+
+
+static void
+print_usage_and_exit(const char *prog_name)
+{
+	unsigned i;
+	printf("Usage: %s <subtest> <mode> {options}\n"
+	       "  where <subtest> is one of the following:\n", prog_name);
+	for (i = 0; i < ARRAY_SIZE(tests); i++)
+		printf("    %s\n", tests[i].name);
+	printf("  <mode> is one of the following:\n"
+	       "    error\n"
+	       "    get\n"
+	       "    run\n"
+	       "    run-no-fs\n"
+	       "  and possible options are:\n"
+	       "    interface - use interface blocks\n");
+	piglit_report_result(PIGLIT_FAIL);
+}
+
+
+void
+piglit_init(int argc, char **argv)
+{
+	unsigned i;
+
+	/* Parse first param. */
+	if (argc < 3)
+		print_usage_and_exit(argv[0]);
+	for (i = 0; i < ARRAY_SIZE(tests); i++) {
+		if (strcmp(argv[1], tests[i].name) == 0) {
+			test = &tests[i];
+			break;
+		}
+	}
+	if (test == NULL)
+		print_usage_and_exit(argv[0]);
+
+	/* Parse options. */
+	for (i = 3; i < argc; i++) {
+		if (strcmp(argv[i], "interface") == 0)
+			use_interface_blocks = true;
+		else
+			print_usage_and_exit(argv[0]);
+	}
+
+	/* Parse second param and setup test */
+	if (strcmp(argv[2], "error") == 0) {
+		piglit_report_result(test_errors());
+	} else if (strcmp(argv[2], "get") == 0) {
+		link_shaders(true);
+		piglit_report_result(test_gets());
+	} else if (strcmp(argv[2], "run") == 0) {
+		link_shaders(true);
+		/* Testing will occur in piglit_display */
+	} else if (strcmp(argv[2], "run-no-fs") == 0) {
+		link_shaders(false);
+		piglit_report_result(test_xfb(true));
+	} else {
+		print_usage_and_exit(argv[0]);
+	}
+}
+
+
+enum piglit_result
+piglit_display(void)
+{
+	static const float green[4] = {0.0, 1.0, 0.0, 1.0};
+	enum piglit_result result;
+
+	glClear(GL_COLOR_BUFFER_BIT);
+	result = test_xfb(false);
+
+	/* test_xfb() sends a set of vertices down the pipeline that
+	 * should cause the entire window to be drawn, so all we need
+	 * to do to make sure that the correct data got to the
+	 * fragment shader is verify that it painted a green window.
+	 */
+	if (!piglit_probe_rect_rgba(0, 0, piglit_width, piglit_height, green))
+		result = PIGLIT_FAIL;
+
+	piglit_present_results();
+
+	return result;
+}
-- 
1.8.1.2



More information about the Piglit mailing list