/*
 * Copyright © 2010 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.
 */

#include <stdbool.h>
#include <string.h>
#include <ctype.h>

#include "piglit-util.h"
#include "piglit-util-gl.h"
#include "piglit-vbo.h"
#include "piglit-framework-gl/piglit_gl_framework.h"
#include "piglit-subprocess.h"

#include "shader_runner_gles_workarounds.h"
#include "parser_utils.h"

#include "shader_runner_vs_passthrough_spv.h"

#define DEFAULT_WINDOW_WIDTH 250
#define DEFAULT_WINDOW_HEIGHT 250

static bool spirv_replaces_glsl = false;
static struct piglit_gl_test_config current_config;

static void
get_required_config(const char *script_name, bool spirv,
		    struct piglit_gl_test_config *config);
static GLenum
decode_drawing_mode(const char *mode_str);

PIGLIT_GL_TEST_CONFIG_BEGIN

	if (!piglit_gl_test_config_override_size(&config)) {
		config.window_width = DEFAULT_WINDOW_WIDTH;
		config.window_height = DEFAULT_WINDOW_HEIGHT;
	}
	config.window_visual = PIGLIT_GL_VISUAL_RGBA | PIGLIT_GL_VISUAL_DOUBLE;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;

	/* By default SPIR-V mode is false. It will not be enabled
	 * unless the script includes SPIRV YES or SPIRV ONLY lines at
	 * [require] section, so it will be handled later.
	 */
	if (argc > 1) {
		get_required_config(argv[1], spirv_replaces_glsl, &config);
	} else {
		config.supports_gl_compat_version = 10;
	}

	current_config = config;

PIGLIT_GL_TEST_CONFIG_END

static const char passthrough_vertex_shader_source[] =
	"#if __VERSION__ >= 130\n"
	"in vec4 piglit_vertex;\n"
	"#else\n"
	"attribute vec4 piglit_vertex;\n"
	"#endif\n"
	"void main() { gl_Position = piglit_vertex; }\n"
	;

struct component_version {
	enum version_tag {
		VERSION_GL,
		VERSION_GLSL,
	} _tag;

	bool core;
	bool compat;
	bool es;
	unsigned num;
	char _string[100];
};

struct block_info {
	int array_index;
	int binding;
	int offset;
	int matrix_stride;
	int row_major; /* int as we don't have a parse_bool */
};

#define ENUM_STRING(e) { #e, e }

extern float piglit_tolerance[4];

static int test_num = 1;
static struct component_version gl_version;
static struct component_version glsl_version;
static struct component_version glsl_req_version;
static int gl_max_vertex_output_components;
static int gl_max_fragment_uniform_components;
static int gl_max_vertex_uniform_components;
static int gl_max_vertex_attribs;
static int gl_max_varying_components;
static int gl_max_clip_planes;
static int gl_num_program_binary_formats = 0;

static const char *test_start = NULL;
static unsigned test_start_line_num = 0;

static GLuint vertex_shaders[256];
static unsigned num_vertex_shaders = 0;
static GLuint tess_ctrl_shaders[256];
static unsigned num_tess_ctrl_shaders = 0;
static GLuint tess_eval_shaders[256];
static unsigned num_tess_eval_shaders = 0;
static GLuint geometry_shaders[256];
static unsigned num_geometry_shaders = 0;
static GLuint fragment_shaders[256];
static unsigned num_fragment_shaders = 0;
static GLuint compute_shaders[256];
static unsigned num_compute_shaders = 0;
static int num_uniform_blocks;
static GLuint *uniform_block_bos;
static int *uniform_block_indexes; /* ubo block index, indexed by ubo binding */
static GLenum geometry_layout_input_type = GL_TRIANGLES;
static GLenum geometry_layout_output_type = GL_TRIANGLE_STRIP;
static GLint geometry_layout_vertices_out = 0;
static GLuint atomics_bos[8];
static GLuint ssbo[32];
static unsigned num_shader_include_paths = 0;
static char **shader_include_path;
static char *shader_include_names[256];
static unsigned num_shader_includes = 0;

#define MAX_XFB_BUFFERS 4 /* Same value used at nir_xfb_info */
static GLuint xfb[MAX_XFB_BUFFERS];

#define MAX_QUERIES 2
static GLuint queries[MAX_QUERIES];

#define SHADER_TYPES 6
static GLuint *subuniform_locations[SHADER_TYPES];
static int num_subuniform_locations[SHADER_TYPES];
static char *shader_string;
static GLint shader_string_size;
static const char *vertex_data_start = NULL;
static const char *vertex_data_end = NULL;
static GLuint prog;
static GLuint sso_vertex_prog;
static GLuint sso_tess_control_prog;
static GLuint sso_tess_eval_prog;
static GLuint sso_geometry_prog;
static GLuint sso_fragment_prog;
static GLuint sso_compute_prog;
static GLuint pipeline = 0;
static size_t num_vbo_rows = 0;
static bool vbo_present = false;
static bool link_ok = false;
static bool prog_in_use = false;
static bool sso_in_use = false;
static bool separable_program = false;
static bool glsl_in_use = false;
static bool force_glsl = false;
static bool spirv_in_use = false;
static bool force_no_names = false;
static GLchar *prog_err_info = NULL;
static GLuint vao = 0;
static GLuint draw_fbo, read_fbo;
static GLint render_width, render_height;
static GLint read_width, read_height;

static bool use_get_program_binary = false;

static bool ignore_missing_uniforms = false;

static bool report_subtests = false;

struct specialization_list {
	size_t buffer_size;
	size_t n_entries;
	GLuint *indices;
	union { GLuint u; GLfloat f; } *values;
};

static struct specialization_list
specializations[SHADER_TYPES];

static struct texture_binding {
	GLuint obj;
	unsigned width;
	unsigned height;
	unsigned layers;
} texture_bindings[32];

static struct resident_handle {
	GLuint64 handle;
	bool is_tex;
} resident_handles[32];

static void
clear_texture_binding(unsigned idx)
{
	REQUIRE(idx < ARRAY_SIZE(texture_bindings),
		"Invalid texture index %d\n", idx);

	if (texture_bindings[idx].obj) {
		glDeleteTextures(1, &texture_bindings[idx].obj);
		texture_bindings[idx].obj = 0;
	}
}

static void
set_texture_binding(unsigned idx, GLuint obj, unsigned w, unsigned h, unsigned l)
{
	clear_texture_binding(idx);

	REQUIRE(idx < ARRAY_SIZE(texture_bindings),
		"Invalid texture index %d\n", idx);
	texture_bindings[idx].obj = obj;
	texture_bindings[idx].width = w;
	texture_bindings[idx].height = h;
	texture_bindings[idx].layers = l;
}

static const struct texture_binding *
get_texture_binding(unsigned idx)
{
	REQUIRE(idx < ARRAY_SIZE(texture_bindings),
		"Invalid texture index %d\n", idx);
	REQUIRE(texture_bindings[idx].obj,
		"No texture bound at %d\n", idx);
	return &texture_bindings[idx];
}

static void
clear_resident_handle(unsigned idx)
{
	REQUIRE(idx < ARRAY_SIZE(resident_handles),
		"Invalid resident handle index %d\n", idx);

	if (resident_handles[idx].handle) {
		GLuint64 handle = resident_handles[idx].handle;
		if (resident_handles[idx].is_tex) {
			if (glIsTextureHandleResidentARB(handle))
				glMakeTextureHandleNonResidentARB(handle);
		} else {
			if (glIsImageHandleResidentARB(handle))
				glMakeImageHandleNonResidentARB(handle);
		}
		resident_handles[idx].handle = 0;
	}
}

static void
set_resident_handle(unsigned idx, GLuint64 handle, bool is_tex)
{
	clear_resident_handle(idx);

	REQUIRE(idx < ARRAY_SIZE(resident_handles),
		"Invalid resident handle index %d\n", idx);
	resident_handles[idx].handle = handle;
	resident_handles[idx].is_tex = is_tex;
}

static const struct resident_handle *
get_resident_handle(unsigned idx)
{
	REQUIRE(idx < ARRAY_SIZE(resident_handles),
		"Invalid resident handle index %d\n", idx);
	REQUIRE(resident_handles[idx].handle,
		"No resident handle at %d\n", idx);
	return &resident_handles[idx];
}

enum states {
	none = 0,
	requirements,
	vertex_shader,
	vertex_shader_passthrough,
	vertex_shader_spirv,
	vertex_shader_specializations,
	vertex_program,
	tess_ctrl_shader,
	tess_ctrl_shader_spirv,
	tess_ctrl_shader_specializations,
	tess_eval_shader,
	tess_eval_shader_spirv,
	tess_eval_shader_specializations,
	geometry_shader,
	geometry_shader_spirv,
	geometry_shader_specializations,
	geometry_layout,
	fragment_shader,
	fragment_shader_spirv,
	fragment_shader_specializations,
	fragment_program,
	compute_shader,
	compute_shader_spirv,
	compute_shader_specializations,
	vertex_data,
	shader_include,
	shader_include_paths,
	test,
};

static const struct string_to_enum all_types[] = {
	ENUM_STRING(GL_FLOAT),
	ENUM_STRING(GL_FLOAT_VEC2),
	ENUM_STRING(GL_FLOAT_VEC3),
	ENUM_STRING(GL_FLOAT_VEC4),
	ENUM_STRING(GL_DOUBLE),
	ENUM_STRING(GL_DOUBLE_VEC2),
	ENUM_STRING(GL_DOUBLE_VEC3),
	ENUM_STRING(GL_DOUBLE_VEC4),
	ENUM_STRING(GL_INT),
	ENUM_STRING(GL_INT_VEC2),
	ENUM_STRING(GL_INT_VEC3),
	ENUM_STRING(GL_INT_VEC4),
	ENUM_STRING(GL_UNSIGNED_INT),
	ENUM_STRING(GL_UNSIGNED_INT_VEC2),
	ENUM_STRING(GL_UNSIGNED_INT_VEC3),
	ENUM_STRING(GL_UNSIGNED_INT_VEC4),
	ENUM_STRING(GL_INT64_ARB),
	ENUM_STRING(GL_INT64_VEC2_ARB),
	ENUM_STRING(GL_INT64_VEC3_ARB),
	ENUM_STRING(GL_INT64_VEC4_ARB),
	ENUM_STRING(GL_UNSIGNED_INT64_ARB),
	ENUM_STRING(GL_UNSIGNED_INT64_VEC2_ARB),
	ENUM_STRING(GL_UNSIGNED_INT64_VEC3_ARB),
	ENUM_STRING(GL_UNSIGNED_INT64_VEC4_ARB),
	ENUM_STRING(GL_BOOL),
	ENUM_STRING(GL_BOOL_VEC2),
	ENUM_STRING(GL_BOOL_VEC3),
	ENUM_STRING(GL_BOOL_VEC4),
	ENUM_STRING(GL_FLOAT_MAT2),
	ENUM_STRING(GL_FLOAT_MAT3),
	ENUM_STRING(GL_FLOAT_MAT4),
	ENUM_STRING(GL_FLOAT_MAT2x3),
	ENUM_STRING(GL_FLOAT_MAT2x4),
	ENUM_STRING(GL_FLOAT_MAT3x2),
	ENUM_STRING(GL_FLOAT_MAT3x4),
	ENUM_STRING(GL_FLOAT_MAT4x2),
	ENUM_STRING(GL_FLOAT_MAT4x3),
	ENUM_STRING(GL_DOUBLE_MAT2),
	ENUM_STRING(GL_DOUBLE_MAT3),
	ENUM_STRING(GL_DOUBLE_MAT4),
	ENUM_STRING(GL_DOUBLE_MAT2x3),
	ENUM_STRING(GL_DOUBLE_MAT2x4),
	ENUM_STRING(GL_DOUBLE_MAT3x2),
	ENUM_STRING(GL_DOUBLE_MAT3x4),
	ENUM_STRING(GL_DOUBLE_MAT4x2),
	ENUM_STRING(GL_DOUBLE_MAT4x3),
	ENUM_STRING(GL_SAMPLER_1D),
	ENUM_STRING(GL_SAMPLER_2D),
	ENUM_STRING(GL_SAMPLER_3D),
	ENUM_STRING(GL_SAMPLER_CUBE),
	ENUM_STRING(GL_SAMPLER_1D_SHADOW),
	ENUM_STRING(GL_SAMPLER_2D_SHADOW),
	ENUM_STRING(GL_SAMPLER_1D_ARRAY),
	ENUM_STRING(GL_SAMPLER_2D_ARRAY),
	ENUM_STRING(GL_SAMPLER_1D_ARRAY_SHADOW),
	ENUM_STRING(GL_SAMPLER_2D_ARRAY_SHADOW),
	ENUM_STRING(GL_SAMPLER_2D_MULTISAMPLE),
	ENUM_STRING(GL_SAMPLER_2D_MULTISAMPLE_ARRAY),
	ENUM_STRING(GL_SAMPLER_CUBE_SHADOW),
	ENUM_STRING(GL_SAMPLER_BUFFER),
	ENUM_STRING(GL_SAMPLER_2D_RECT),
	ENUM_STRING(GL_SAMPLER_2D_RECT_SHADOW),
	ENUM_STRING(GL_INT_SAMPLER_1D),
	ENUM_STRING(GL_INT_SAMPLER_2D),
	ENUM_STRING(GL_INT_SAMPLER_3D),
	ENUM_STRING(GL_INT_SAMPLER_CUBE),
	ENUM_STRING(GL_INT_SAMPLER_1D_ARRAY),
	ENUM_STRING(GL_INT_SAMPLER_2D_ARRAY),
	ENUM_STRING(GL_INT_SAMPLER_2D_MULTISAMPLE),
	ENUM_STRING(GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY),
	ENUM_STRING(GL_INT_SAMPLER_BUFFER),
	ENUM_STRING(GL_INT_SAMPLER_2D_RECT),
	ENUM_STRING(GL_UNSIGNED_INT_SAMPLER_1D),
	ENUM_STRING(GL_UNSIGNED_INT_SAMPLER_2D),
	ENUM_STRING(GL_UNSIGNED_INT_SAMPLER_3D),
	ENUM_STRING(GL_UNSIGNED_INT_SAMPLER_CUBE),
	ENUM_STRING(GL_UNSIGNED_INT_SAMPLER_1D_ARRAY),
	ENUM_STRING(GL_UNSIGNED_INT_SAMPLER_2D_ARRAY),
	ENUM_STRING(GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE),
	ENUM_STRING(GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY),
	ENUM_STRING(GL_UNSIGNED_INT_SAMPLER_BUFFER),
	ENUM_STRING(GL_UNSIGNED_INT_SAMPLER_2D_RECT),
	{ NULL, 0 }
};

static bool
compare(float ref, float value, enum comparison cmp);

static bool
compare_uint(GLuint ref, GLuint value, enum comparison cmp);

static bool
compare_int(GLint ref, GLint value, enum comparison cmp);

static void
version_init(struct component_version *v, enum version_tag tag, bool core, bool compat, bool es, unsigned num)
{
	assert(tag == VERSION_GL || tag == VERSION_GLSL);

	v->_tag = tag;
	v->core = core;
	v->compat = compat;
	v->es = es;
	v->num = num;
	v->_string[0] = 0;
}

static void
version_copy(struct component_version *dest, struct component_version *src)
{
	memcpy(dest, src, sizeof(*dest));
}

static bool
version_compare(struct component_version *a, struct component_version *b, enum comparison cmp)
{
	assert(a->_tag == b->_tag);

	if (a->es != b->es)
		return false;

	return compare(a->num, b->num, cmp);
}

/**
 * Get the version string.
 */
static const char*
version_string(struct component_version *v)
{
	if (v->_string[0])
		return v->_string;

	switch (v->_tag) {
	case VERSION_GL:
		snprintf(v->_string, sizeof(v->_string) - 1, "GL%s %d.%d",
		         v->es ? " ES" : "",
		         v->num / 10, v->num % 10);
		break;
	case VERSION_GLSL:
		snprintf(v->_string, sizeof(v->_string) - 1, "GLSL%s %d.%d",
		         v->es ? " ES" : "",
		         v->num / 100, v->num % 100);
		break;
	default:
		assert(false);
		break;
	}

	return v->_string;
}


static const char *
target_to_short_name(GLenum target)
{
	switch (target) {
	case GL_VERTEX_SHADER:
		return "VS";
	case GL_FRAGMENT_SHADER:
		return "FS";
	case GL_TESS_CONTROL_SHADER:
		return "TCS";
	case GL_TESS_EVALUATION_SHADER:
		return "TES";
	case GL_GEOMETRY_SHADER:
		return "GS";
	case GL_COMPUTE_SHADER:
		return "CS";
	default:
		return "???";
	}
}


static enum piglit_result
compile_glsl(GLenum target)
{
	GLuint shader = glCreateShader(target);
	GLint ok;

	if (spirv_in_use) {
		printf("Cannot mix SPIRV and non-SPIRV shaders\n");
		return PIGLIT_FAIL;
	}

	glsl_in_use = true;

	switch (target) {
	case GL_VERTEX_SHADER:
		if (piglit_get_gl_version() < 20 &&
		    !(piglit_is_extension_supported("GL_ARB_shader_objects") &&
		      piglit_is_extension_supported("GL_ARB_vertex_shader")))
			return PIGLIT_SKIP;
		break;
	case GL_FRAGMENT_SHADER:
		if (piglit_get_gl_version() < 20 &&
		    !(piglit_is_extension_supported("GL_ARB_shader_objects") &&
		      piglit_is_extension_supported("GL_ARB_fragment_shader")))
			return PIGLIT_SKIP;
		break;
	case GL_TESS_CONTROL_SHADER:
	case GL_TESS_EVALUATION_SHADER:
		if (gl_version.num < (gl_version.es ? 32 : 40))
			if (!piglit_is_extension_supported(gl_version.es ?
							   "GL_OES_tessellation_shader" :
							   "GL_ARB_tessellation_shader"))
				return PIGLIT_SKIP;
		break;
	case GL_GEOMETRY_SHADER:
		if (gl_version.num < 32)
			if (!piglit_is_extension_supported(gl_version.es ?
							   "GL_OES_geometry_shader" :
							   "GL_ARB_geometry_shader4"))
				return PIGLIT_SKIP;
		break;
	case GL_COMPUTE_SHADER:
		if (gl_version.num < (gl_version.es ? 31 : 43))
			if (!piglit_is_extension_supported("GL_ARB_compute_shader"))
				return PIGLIT_SKIP;
		break;
	}

	if (!glsl_req_version.num) {
		printf("GLSL version requirement missing\n");
		return PIGLIT_FAIL;
	}

	if (!strstr(shader_string, "#version ")) {
		char *shader_strings[2];
		char version_string[100];
		GLint shader_string_sizes[2];

		/* Add a #version directive based on the GLSL requirement. */
		sprintf(version_string, "#version %d", glsl_req_version.num);
		if (glsl_req_version.es && glsl_req_version.num != 100) {
			strcat(version_string, " es");
		}
		strcat(version_string, "\n");
		shader_strings[0] = version_string;
		shader_string_sizes[0] = strlen(version_string);
		shader_strings[1] = shader_string;
		shader_string_sizes[1] = shader_string_size;

		glShaderSource(shader, 2,
				    (const GLchar **) shader_strings,
				    shader_string_sizes);

	} else {
		glShaderSource(shader, 1,
				    (const GLchar **) &shader_string,
				    &shader_string_size);
	}

	if (num_shader_include_paths) {
		glCompileShaderIncludeARB(shader, num_shader_include_paths,
					  (const char **) shader_include_path, NULL);
	} else
		glCompileShader(shader);

	glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);

	if (!ok) {
		GLchar *info;
		GLint size;

		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &size);
		info = malloc(size);

		glGetShaderInfoLog(shader, size, NULL, info);

		fprintf(stderr, "Failed to compile %s: %s\n",
			target_to_short_name(target),
			info);

		free(info);
		return PIGLIT_FAIL;
	}

	switch (target) {
	case GL_VERTEX_SHADER:
		vertex_shaders[num_vertex_shaders] = shader;
		num_vertex_shaders++;
		break;
	case GL_TESS_CONTROL_SHADER:
		tess_ctrl_shaders[num_tess_ctrl_shaders] = shader;
		num_tess_ctrl_shaders++;
		break;
	case GL_TESS_EVALUATION_SHADER:
		tess_eval_shaders[num_tess_eval_shaders] = shader;
		num_tess_eval_shaders++;
		break;
	case GL_GEOMETRY_SHADER:
		geometry_shaders[num_geometry_shaders] = shader;
		num_geometry_shaders++;
		break;
	case GL_FRAGMENT_SHADER:
		fragment_shaders[num_fragment_shaders] = shader;
		num_fragment_shaders++;
		break;
	case GL_COMPUTE_SHADER:
		compute_shaders[num_compute_shaders] = shader;
		num_compute_shaders++;
		break;
	}
	return PIGLIT_PASS;
}


static enum piglit_result
compile_and_bind_program(GLenum target, const char *start, int len)
{
	GLuint prog;
	char *source;

	switch (target) {
	case GL_VERTEX_PROGRAM_ARB:
		if (!piglit_is_extension_supported("GL_ARB_vertex_program"))
			return PIGLIT_SKIP;
		break;
	case GL_FRAGMENT_PROGRAM_ARB:
		if (!piglit_is_extension_supported("GL_ARB_fragment_program"))
			return PIGLIT_SKIP;
		break;
	}

	source = malloc(len + 1);
	memcpy(source, start, len);
	source[len] = 0;
	prog = piglit_compile_program(target, source);

	glEnable(target);
	glBindProgramARB(target, prog);
	link_ok = true;
	prog_in_use = true;

	return PIGLIT_PASS;
}

static bool
program_binary_save_restore(bool script_command)
{
	GLint binary_length;
	void *binary;
	GLenum binary_format;
	GLint ok;
	GLuint new_prog;

	if (!script_command && !use_get_program_binary)
		return true;

	if (script_command && gl_num_program_binary_formats == 0)
		piglit_report_result(PIGLIT_SKIP);

	glGetProgramiv(prog, GL_LINK_STATUS, &ok);
	if (!ok) {
		if (script_command) {
			fprintf(stderr, "Can't save/restore program that is "
				"not linked!\n");
			piglit_report_result(PIGLIT_FAIL);
		} else {
			return true;
		}
	}

#ifdef PIGLIT_USE_OPENGL
	glGetProgramiv(prog, GL_PROGRAM_BINARY_LENGTH, &binary_length);
#else
	glGetProgramiv(prog, GL_PROGRAM_BINARY_LENGTH_OES, &binary_length);
#endif
	if (!piglit_check_gl_error(GL_NO_ERROR)) {
		fprintf(stderr, "glGetProgramiv GL_PROGRAM_BINARY_LENGTH "
		        "error\n");
		piglit_report_result(PIGLIT_FAIL);
	}

	binary = malloc(binary_length);
	if (!binary) {
		fprintf(stderr, "Failed to allocate buffer for "
		        "GetProgramBinary\n");
		piglit_report_result(PIGLIT_FAIL);
	}

#ifdef PIGLIT_USE_OPENGL
	glGetProgramBinary(prog, binary_length, &binary_length, &binary_format,
	                   binary);
#else
	glGetProgramBinaryOES(prog, binary_length, &binary_length,
	                      &binary_format, binary);
#endif
	if (!piglit_check_gl_error(GL_NO_ERROR)) {
		fprintf(stderr, "glGetProgramBinary error\n");
		free(binary);
		piglit_report_result(PIGLIT_FAIL);
	}

	new_prog = glCreateProgram();
	if (!piglit_check_gl_error(GL_NO_ERROR)) {
		free(binary);
		piglit_report_result(PIGLIT_FAIL);
	}

#ifdef PIGLIT_USE_OPENGL
	glProgramBinary(new_prog, binary_format, binary, binary_length);
#else
	glProgramBinaryOES(new_prog, binary_format, binary, binary_length);
#endif
	free(binary);
	if (!piglit_check_gl_error(GL_NO_ERROR)) {
		fprintf(stderr, "glProgramBinary error "
			"(should not happend according to spec.)\n");
		piglit_report_result(PIGLIT_FAIL);
	}

	glGetProgramiv(prog, GL_LINK_STATUS, &ok);
	if (!ok) {
		fprintf(stderr, "link failure after glProgramBinary\n");
		piglit_report_result(PIGLIT_FAIL);
	}

	if (prog_in_use) {
		glUseProgram(new_prog);
		if (!piglit_check_gl_error(GL_NO_ERROR))
			piglit_report_result(PIGLIT_FAIL);
	}

	glDeleteProgram(prog);
	if (!piglit_check_gl_error(GL_NO_ERROR))
		piglit_report_result(PIGLIT_FAIL);
	prog = new_prog;

	return true;
}

static enum piglit_result
specialize_spirv(GLenum target,
		 GLuint shader)
{
	if (glsl_in_use) {
		printf("Cannot mix SPIR-V and non-SPIR-V shaders\n");
		return PIGLIT_FAIL;
	}

	spirv_in_use = true;

	const struct specialization_list *specs;

	switch (target) {
	case GL_VERTEX_SHADER:
		specs = specializations + 0;
		break;
	case GL_TESS_CONTROL_SHADER:
		specs = specializations + 1;
		break;
	case GL_TESS_EVALUATION_SHADER:
		specs = specializations + 2;
		break;
	case GL_GEOMETRY_SHADER:
		specs = specializations + 3;
		break;
	case GL_FRAGMENT_SHADER:
		specs = specializations + 4;
		break;
	case GL_COMPUTE_SHADER:
		specs = specializations + 5;
		break;
	default:
		assert(!"Should not get here.");
	}

	glSpecializeShaderARB(shader,
			      "main",
			      specs->n_entries,
			      specs->indices,
			      &specs->values[0].u);

	GLint ok;
	glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);

	if (!ok) {
		GLchar *info;
		GLint size;

		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &size);
		info = malloc(MAX2(size, 1));
		info[0] = 0;

		glGetShaderInfoLog(shader, size, NULL, info);

		printf("Failed to specialize %s: %s\n",
		       target_to_short_name(target), info);

		free(info);
		return PIGLIT_FAIL;
	}

	switch (target) {
	case GL_VERTEX_SHADER:
		vertex_shaders[num_vertex_shaders] = shader;
		num_vertex_shaders++;
		break;
	case GL_TESS_CONTROL_SHADER:
		tess_ctrl_shaders[num_tess_ctrl_shaders] = shader;
		num_tess_ctrl_shaders++;
		break;
	case GL_TESS_EVALUATION_SHADER:
		tess_eval_shaders[num_tess_eval_shaders] = shader;
		num_tess_eval_shaders++;
		break;
	case GL_GEOMETRY_SHADER:
		geometry_shaders[num_geometry_shaders] = shader;
		num_geometry_shaders++;
		break;
	case GL_FRAGMENT_SHADER:
		fragment_shaders[num_fragment_shaders] = shader;
		num_fragment_shaders++;
		break;
	case GL_COMPUTE_SHADER:
		compute_shaders[num_compute_shaders] = shader;
		num_compute_shaders++;
		break;
	}

	return PIGLIT_PASS;
}

static enum piglit_result
assemble_spirv(GLenum target)
{
	if (!piglit_is_extension_supported("GL_ARB_gl_spirv")) {
		return PIGLIT_SKIP;
	}

	/* Strip comments from the source */
	char *stripped_source = malloc(shader_string_size);
	char *p = stripped_source;
	bool at_start_of_line = true;

	for (const char *in = shader_string;
	     in < shader_string + shader_string_size;
	     in++) {
		if (*in == '#' && at_start_of_line) {
			const char *end;
			end = memchr(in,
				     '\n',
				     shader_string + shader_string_size - in);
			if (end == NULL)
				break;
			in = end;
		} else {
			at_start_of_line = *in == '\n';
			*(p++) = *in;
		}
	}

	GLuint shader = piglit_assemble_spirv(target,
					      p - stripped_source,
					      stripped_source);

	free(stripped_source);

	return specialize_spirv(target, shader);
}

static enum piglit_result
link_sso(GLenum target)
{
	GLint ok;

	glLinkProgram(prog);

	glGetProgramiv(prog, GL_LINK_STATUS, &ok);
	if (ok) {
		link_ok = true;
	} else {
		GLint size;

		glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &size);
		prog_err_info = malloc(MAX2(size, 1));
		prog_err_info[0] = 0;

		glGetProgramInfoLog(prog, size, NULL, prog_err_info);

		fprintf(stderr, "SSO glLinkProgram(%s) failed: %s\n",
			target_to_short_name(target),
			prog_err_info);

		free(prog_err_info);
		return PIGLIT_FAIL;
	}

	if (!program_binary_save_restore(false))
		return PIGLIT_FAIL;

	switch (target) {
	case GL_VERTEX_SHADER:
		sso_vertex_prog = prog;
		glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, prog);
		break;
	case GL_TESS_CONTROL_SHADER:
		sso_tess_control_prog = prog;
		glUseProgramStages(pipeline, GL_TESS_CONTROL_SHADER_BIT, prog);
		break;
	case GL_TESS_EVALUATION_SHADER:
		sso_tess_eval_prog = prog;
		glUseProgramStages(pipeline, GL_TESS_EVALUATION_SHADER_BIT, prog);
		break;
	case GL_GEOMETRY_SHADER:
		sso_geometry_prog = prog;
		glUseProgramStages(pipeline, GL_GEOMETRY_SHADER_BIT, prog);
		break;
	case GL_FRAGMENT_SHADER:
		sso_fragment_prog = prog;
		glUseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, prog);
		break;
	case GL_COMPUTE_SHADER:
		sso_compute_prog = prog;
		glUseProgramStages(pipeline, GL_COMPUTE_SHADER_BIT, prog);
		break;
	}
	return PIGLIT_PASS;
}

/**
 * Compare two values given a specified comparison operator
 */
static bool
compare(float ref, float value, enum comparison cmp)
{
	switch (cmp) {
	case equal:         return value == ref;
	case not_equal:     return value != ref;
	case less:          return value <  ref;
	case greater_equal: return value >= ref;
	case greater:       return value >  ref;
	case less_equal:    return value <= ref;
	}

	assert(!"Should not get here.");
	return false;
}

static bool
compare_uint(GLuint ref, GLuint value, enum comparison cmp)
{
	switch (cmp) {
	case equal:         return value == ref;
	case not_equal:     return value != ref;
	case less:          return value <  ref;
	case greater_equal: return value >= ref;
	case greater:       return value >  ref;
	case less_equal:    return value <= ref;
	}

	assert(!"Should not get here.");
	return false;
}

static bool
compare_int(GLint ref, GLint value, enum comparison cmp)
{
	switch (cmp) {
	case equal:         return value == ref;
	case not_equal:     return value != ref;
	case less:          return value <  ref;
	case greater_equal: return value >= ref;
	case greater:       return value >  ref;
	case less_equal:    return value <= ref;
	}

	assert(!"Should not get here.");
	return false;
}

/**
 * Get the string representation of a comparison operator
 */
static const char *
comparison_string(enum comparison cmp)
{
	switch (cmp) {
	case equal:         return "==";
	case not_equal:     return "!=";
	case less:          return "<";
	case greater_equal: return ">=";
	case greater:       return ">";
	case less_equal:    return "<=";
	}

	assert(!"Should not get here.");
	return false;
}

/**
 * " ES" before the comparison operator indicates the version
 * pertains to GL ES.
 */
static void
parse_version_comparison(const char *line, enum comparison *cmp,
			 struct component_version *v, enum version_tag tag)
{
	unsigned major;
	unsigned minor;
	unsigned full_num;
	const bool core = parse_str(line, "CORE", &line);
	const bool compat = parse_str(line, "COMPAT", &line);
	const bool es = parse_str(line, "ES", &line);

	REQUIRE(parse_comparison_op(line, cmp, &line),
		"Invalid comparison operation at: %s\n", line);

	REQUIRE(parse_uint(line, &major, &line) &&
		parse_str(line, ".", &line) &&
		parse_uint(line, &minor, &line),
		"Invalid version string: %s\n", line);

	parse_whitespace(line, &line);
	if (*line != '\n') {
		printf("Unexpected characters following version comparison\n");
		piglit_report_result(PIGLIT_FAIL);
	}

	/* This hack is so that we can tell the difference between GL versions
	 * and GLSL versions.  All GL versions look like 3.2, and we want the
	 * integer to be 32.  All GLSL versions look like 1.40, and we want
	 * the integer to be 140.
	 */
	if (tag == VERSION_GLSL) {
		full_num = (major * 100) + minor;
	} else {
		full_num = (major * 10) + minor;
	}

	version_init(v, tag, core, compat, es, full_num);
}

/**
 * Parse and check a line from the requirement section of the test
 */
static enum piglit_result
process_requirement(const char *line)
{
	char buffer[4096];
	static const struct {
		const char *name;
		int *val;
		const char *desc;
	} getint_limits[] = {
		{
			"GL_MAX_VERTEX_OUTPUT_COMPONENTS",
			&gl_max_vertex_output_components,
			"vertex output components",
		},
		{
			"GL_MAX_FRAGMENT_UNIFORM_COMPONENTS",
			&gl_max_fragment_uniform_components,
			"fragment uniform components",
		},
		{
			"GL_MAX_VERTEX_UNIFORM_COMPONENTS",
			&gl_max_vertex_uniform_components,
			"vertex uniform components",
		},
		{
			"GL_MAX_VERTEX_ATTRIBS",
			&gl_max_vertex_attribs,
			"vertex attribs",
		},
		{
			"GL_MAX_VARYING_COMPONENTS",
			&gl_max_varying_components,
			"varying components",
		},
		{
			"GL_NUM_PROGRAM_BINARY_FORMATS",
			&gl_num_program_binary_formats,
			"num program binary formats",
		},
	};
	unsigned i;

	/* The INT keyword in the requirements section causes
	 * shader_runner to read the specified integer value and
	 * processes the given requirement.
	 */
	if (parse_str(line, "INT ", &line)) {
		enum comparison cmp;
		int comparison_value, gl_int_value;
		unsigned int_enum;

		REQUIRE(parse_enum_gl(line, &int_enum, &line),
			"Invalid comparison enum at: %s\n", line);
		REQUIRE(parse_comparison_op(line, &cmp, &line),
			"Invalid comparison operation at: %s\n", line);
		REQUIRE(parse_int(line, &comparison_value, &line),
			"Invalid comparison value at: %s\n", line);

		glGetIntegerv(int_enum, &gl_int_value);
		if (!piglit_check_gl_error(GL_NO_ERROR)) {
			fprintf(stderr, "Error reading %s\n",
				piglit_get_gl_enum_name(int_enum));
			return PIGLIT_FAIL;
		}

		if (!compare(comparison_value, gl_int_value, cmp)) {
			printf("Test requires %s %s %i.  "
			       "The driver supports %i.\n",
			       piglit_get_gl_enum_name(int_enum),
			       comparison_string(cmp),
			       comparison_value,
			       gl_int_value);
			return PIGLIT_SKIP;
		}

		return PIGLIT_PASS;
	}

	/* There are five types of requirements that a test can currently
	 * have:
	 *
	 *    * Require that some GL extension be supported
	 *    * Require some particular versions of GL
	 *    * Require some particular versions of GLSL
	 *    * Require some particular number of uniform components
	 *    * Require shaders be built as separate shader objects
	 *
	 * The tests for GL and GLSL versions can be equal, not equal,
	 * less, less-or-equal, greater, or greater-or-equal.  Extension tests
	 * can also require that a particular extension not be supported by
	 * prepending ! to the extension name.
	 */
	for (i = 0; i < ARRAY_SIZE(getint_limits); i++) {
		enum comparison cmp;
		int maxcomp;

		if (!parse_str(line, getint_limits[i].name, &line))
			continue;

		REQUIRE(parse_comparison_op(line, &cmp, &line),
			"Invalid comparison operation at: %s\n", line);

		maxcomp = atoi(line);
		if (!compare(maxcomp, *getint_limits[i].val, cmp)) {
			printf("Test requires %s %s %i.  "
			       "The driver supports %i.\n",
			       getint_limits[i].desc,
			       comparison_string(cmp),
			       maxcomp,
			       *getint_limits[i].val);
			return PIGLIT_SKIP;
		}
		return PIGLIT_PASS;
	}

	if (parse_str(line, "GL_", NULL) &&
	    parse_word_copy(line, buffer, sizeof(buffer), &line)) {
		if (!piglit_is_extension_supported(buffer)) {
			printf("Test requires unsupported extension %s\n", buffer);
			return PIGLIT_SKIP;
		}
	} else if (parse_str(line, "!", &line) &&
		   parse_str(line, "GL_", NULL) &&
		   parse_word_copy(line, buffer, sizeof(buffer), &line)) {
		if (piglit_is_extension_supported(buffer)) {
			printf("Test requires unsupported extension %s\n", buffer);
			return PIGLIT_SKIP;
		}
	} else if (parse_str(line, "GLSL", &line)) {
		enum comparison cmp;

		parse_version_comparison(line, &cmp, &glsl_req_version,
		                         VERSION_GLSL);

		/* We only allow >= because we potentially use the
		 * version number to insert a #version directive. */
		if (cmp != greater_equal) {
			printf("Unsupported GLSL version comparison\n");
			return PIGLIT_FAIL;
		}

		if (!version_compare(&glsl_req_version, &glsl_version, cmp)) {
			printf("Test requires %s %s.  "
			       "Actual version %s.\n",
			       comparison_string(cmp),
			       version_string(&glsl_req_version),
			       version_string(&glsl_version));
			return PIGLIT_SKIP;
		}
	} else if (parse_str(line, "GL", &line)) {
		enum comparison cmp;
		struct component_version gl_req_version;

		parse_version_comparison(line, &cmp, &gl_req_version,
		                         VERSION_GL);

		if (!version_compare(&gl_req_version, &gl_version, cmp)) {
			printf("Test requires %s %s.  "
			       "Actual version is %s.\n",
			       comparison_string(cmp),
			       version_string(&gl_req_version),
			       version_string(&gl_version));
			return PIGLIT_SKIP;
		}
	} else if (parse_str(line, "rlimit", &line)) {
		unsigned lim;

		REQUIRE(parse_uint(line, &lim, &line),
			"Invalid rlimit argument at: %s\n", line);

		piglit_set_rlimit(lim);
	}  else if (parse_str(line, "SSO", &line) &&
		    parse_str(line, "ENABLED", NULL)) {
		if (separable_program) {
			printf("SSO and SEPARABLE PROGRAM directives are incompatible.\n");
			return PIGLIT_FAIL;
		}

		const char *const ext_name = gl_version.es
			? "GL_EXT_separate_shader_objects"
			: "GL_ARB_separate_shader_objects";
		const unsigned min_version = gl_version.es
			? 31 : 41;

		if (gl_version.num < min_version)
			piglit_require_extension(ext_name);

		sso_in_use = true;
		glGenProgramPipelines(1, &pipeline);
	}  else if (parse_str(line, "SEPARABLE PROGRAM", &line) &&
		    parse_str(line, "ENABLED", NULL)) {
		if (sso_in_use) {
			printf("SSO and SEPARABLE PROGRAM directives are incompatible.\n");
			return PIGLIT_FAIL;
		}

		const char *const ext_name = gl_version.es
			? "GL_EXT_separate_shader_objects"
			: "GL_ARB_separate_shader_objects";
		const unsigned min_version = gl_version.es
			? 31 : 41;

		if (gl_version.num < min_version)
			piglit_require_extension(ext_name);

		separable_program = true;
	} else if (parse_str(line, "SPIRV", &line)) {
		spirv_replaces_glsl = !force_glsl;

		if (parse_str(line, "ONLY", NULL)) {
			if (force_glsl) {
				printf("This shader is not compatible with GLSL\n");
				return PIGLIT_SKIP;
			}
		} else if (parse_str(line, "YES", NULL)) {
			/* Empty. Everything already set. Just parsing
			 * correct options
			 */
		} else {
			printf("Unknown SPIRV line in [require]\n");
			return PIGLIT_FAIL;
		}

		if (spirv_replaces_glsl)
			force_no_names = true;
	}
	return PIGLIT_PASS;
}


/**
 * Process a line from the [geometry layout] section of a test
 */
static void
process_geometry_layout(const char *line)
{
	char s[32];
	int x;

	parse_whitespace(line, &line);

	if (line[0] == '\0' || line[0] == '\n') {
		return;
	} else if (sscanf(line, "input type %31s", s) == 1) {
		geometry_layout_input_type = decode_drawing_mode(s);
	} else if (sscanf(line, "output type %31s", s) == 1) {
		geometry_layout_output_type = decode_drawing_mode(s);
	} else if (sscanf(line, "vertices out %d", &x) == 1) {
		geometry_layout_vertices_out = x;
	} else {
		printf("Could not parse geometry layout line: %s\n", line);
		piglit_report_result(PIGLIT_FAIL);
	}
}

static enum piglit_result
leave_state(enum states state, const char *line, const char *script_name)
{
	switch (state) {
	case none:
		break;

	case requirements:
		if (spirv_replaces_glsl) {
			printf("Running on SPIR-V mode\n");
		}
		if (force_no_names && !spirv_replaces_glsl) {
			printf("Running on GLSL mode, forcing not using "
				"uniform/uniform block names\n");
		}
		break;

	case vertex_shader:
		if (spirv_replaces_glsl)
			break;
		shader_string_size = line - shader_string;
		return compile_glsl(GL_VERTEX_SHADER);

	case vertex_shader_passthrough:
		if (spirv_replaces_glsl) {
			shader_string = (char *) passthrough_vertex_shader_source_spv;
			shader_string_size = strlen(passthrough_vertex_shader_source_spv);

			return assemble_spirv(GL_VERTEX_SHADER);
		}
		return compile_glsl(GL_VERTEX_SHADER);

	case vertex_program:
		return compile_and_bind_program(GL_VERTEX_PROGRAM_ARB,
						shader_string,
						line - shader_string);

	case vertex_shader_spirv:
		if (!spirv_replaces_glsl)
			break;
		shader_string_size = line - shader_string;
		return assemble_spirv(GL_VERTEX_SHADER);

	case vertex_shader_specializations:
		break;

	case tess_ctrl_shader:
		if (spirv_replaces_glsl)
			break;
		shader_string_size = line - shader_string;
		return compile_glsl(GL_TESS_CONTROL_SHADER);

	case tess_ctrl_shader_spirv:
		if (!spirv_replaces_glsl)
			break;
		shader_string_size = line - shader_string;
		return assemble_spirv(GL_TESS_CONTROL_SHADER);

	case tess_ctrl_shader_specializations:
		break;

	case tess_eval_shader:
		if (spirv_replaces_glsl)
			break;
		shader_string_size = line - shader_string;
		return compile_glsl(GL_TESS_EVALUATION_SHADER);

	case tess_eval_shader_spirv:
		if (!spirv_replaces_glsl)
			break;
		shader_string_size = line - shader_string;
		return assemble_spirv(GL_TESS_EVALUATION_SHADER);

	case tess_eval_shader_specializations:
		break;

	case geometry_shader:
		if (spirv_replaces_glsl)
			break;
		shader_string_size = line - shader_string;
		return compile_glsl(GL_GEOMETRY_SHADER);

	case geometry_shader_spirv:
		if (!spirv_replaces_glsl)
			break;
		shader_string_size = line - shader_string;
		return assemble_spirv(GL_GEOMETRY_SHADER);

	case geometry_shader_specializations:
		break;

	case geometry_layout:
		break;

	case fragment_shader:
		if (spirv_replaces_glsl)
			break;
		shader_string_size = line - shader_string;
		return compile_glsl(GL_FRAGMENT_SHADER);

	case fragment_program:
		return compile_and_bind_program(GL_FRAGMENT_PROGRAM_ARB,
						shader_string,
						line - shader_string);
		break;

	case fragment_shader_spirv:
		if (!spirv_replaces_glsl)
			break;
		shader_string_size = line - shader_string;
		return assemble_spirv(GL_FRAGMENT_SHADER);

	case fragment_shader_specializations:
		break;

	case compute_shader:
		if (spirv_replaces_glsl)
			break;
		shader_string_size = line - shader_string;
		return compile_glsl(GL_COMPUTE_SHADER);

	case compute_shader_spirv:
		if (!spirv_replaces_glsl)
			break;
		shader_string_size = line - shader_string;
		return assemble_spirv(GL_COMPUTE_SHADER);

	case compute_shader_specializations:
		break;

	case vertex_data:
		vertex_data_end = line;
		break;

	case shader_include: {
		const char *path_end = strchrnul(shader_string, '\n');

		if (shader_string == path_end) {
			fprintf(stderr, "No shader include path provided\n");
			return PIGLIT_FAIL;
		}

		if (!piglit_is_extension_supported("GL_ARB_shading_language_include"))
			return PIGLIT_SKIP;

		int path_name_len = path_end - shader_string;
		glNamedStringARB(GL_SHADER_INCLUDE_ARB, path_name_len, shader_string,
				 line - shader_string - path_name_len, shader_string + path_name_len);

		shader_include_names[num_shader_includes] = strndup(shader_string, path_name_len);
		num_shader_includes++;
		assert(num_shader_includes <= 256);

		if (!piglit_check_gl_error(GL_NO_ERROR)) {
			fprintf(stderr, "glNamedStringARB error\n");
			return PIGLIT_FAIL;
		}

		break;
	}

	case shader_include_paths: {
		num_shader_include_paths = 0;
		const char *cursor = shader_string;
		do {
			const char *line_start = cursor;
			cursor = strchr(cursor, '\n');

			if (cursor == line_start) {
				cursor++;
				continue;
			}

			cursor++;
			num_shader_include_paths++;
		} while (cursor && cursor < line);

		shader_include_path = calloc(num_shader_include_paths, sizeof(char *));

		cursor = shader_string;
		for (unsigned i = 0; i < num_shader_include_paths; i++) {
			char *line_end = strchr(cursor, '\n');
			unsigned path_len = line_end - cursor - 1;
			char *path = malloc(path_len + 1);
			path[path_len] = '\0';

			memcpy(path, cursor, path_len);
			shader_include_path[i] = path;
			cursor = line_end + 1;
		}

		break;
	}

	case test:
		break;

	default:
		assert(!"Not yet supported.");
	}
	return PIGLIT_PASS;
}


static enum piglit_result
process_shader(GLenum target, unsigned num_shaders, GLuint *shaders)
{
	if (num_shaders == 0)
		return PIGLIT_PASS;

	if (sso_in_use) {
		prog = glCreateProgram();
		glProgramParameteri(prog, GL_PROGRAM_SEPARABLE, GL_TRUE);
	}

	for (unsigned i = 0; i < num_shaders; i++) {
		glAttachShader(prog, shaders[i]);
	}

#ifdef PIGLIT_USE_OPENGL
	if (geometry_layout_input_type != GL_TRIANGLES) {
		glProgramParameteriARB(prog, GL_GEOMETRY_INPUT_TYPE_ARB,
				       geometry_layout_input_type);
	}
	if (geometry_layout_output_type != GL_TRIANGLE_STRIP) {
		glProgramParameteriARB(prog, GL_GEOMETRY_OUTPUT_TYPE_ARB,
				       geometry_layout_output_type);
	}
	if (geometry_layout_vertices_out != 0) {
		glProgramParameteriARB(prog, GL_GEOMETRY_VERTICES_OUT_ARB,
				       geometry_layout_vertices_out);
	}
#endif

	/* If the shaders reference piglit_vertex or piglit_tex, bind
	 * them to some fixed attribute locations so they can be used
	 * with piglit_draw_rect_tex() in GLES.
	 */
	glBindAttribLocation(prog, PIGLIT_ATTRIB_POS, "piglit_vertex");
	glBindAttribLocation(prog, PIGLIT_ATTRIB_TEX, "piglit_texcoord");

	if (sso_in_use) {
		return link_sso(target);
	}
	return PIGLIT_PASS;
}


static enum piglit_result
link_and_use_shaders(void)
{
	enum piglit_result result;
	unsigned i;
	GLenum err;
	GLint ok;

	if ((num_vertex_shaders == 0)
	    && (num_fragment_shaders == 0)
	    && (num_tess_ctrl_shaders == 0)
	    && (num_tess_eval_shaders == 0)
	    && (num_geometry_shaders == 0)
	    && (num_compute_shaders == 0))
		return PIGLIT_PASS;

	if (!sso_in_use)
		prog = glCreateProgram();

	result = process_shader(GL_VERTEX_SHADER, num_vertex_shaders, vertex_shaders);
	if (result != PIGLIT_PASS)
		goto cleanup;
	result = process_shader(GL_TESS_CONTROL_SHADER, num_tess_ctrl_shaders, tess_ctrl_shaders);
	if (result != PIGLIT_PASS)
		goto cleanup;
	result = process_shader(GL_TESS_EVALUATION_SHADER, num_tess_eval_shaders, tess_eval_shaders);
	if (result != PIGLIT_PASS)
		goto cleanup;
	result = process_shader(GL_GEOMETRY_SHADER, num_geometry_shaders, geometry_shaders);
	if (result != PIGLIT_PASS)
		goto cleanup;
	result = process_shader(GL_FRAGMENT_SHADER, num_fragment_shaders, fragment_shaders);
	if (result != PIGLIT_PASS)
		goto cleanup;
	result = process_shader(GL_COMPUTE_SHADER, num_compute_shaders, compute_shaders);
	if (result != PIGLIT_PASS)
		goto cleanup;

	if (!sso_in_use) {
		if (separable_program)
			glProgramParameteri(prog, GL_PROGRAM_SEPARABLE, GL_TRUE);

		glLinkProgram(prog);
	}

	if (!sso_in_use) {
		if (!program_binary_save_restore(false))
			return PIGLIT_FAIL;
		glGetProgramiv(prog, GL_LINK_STATUS, &ok);
		if (ok) {
			link_ok = true;
		} else {
			GLint size;

			glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &size);
			prog_err_info = malloc(MAX2(size, 1));
			prog_err_info[0] = 0;

			glGetProgramInfoLog(prog, size, NULL, prog_err_info);

			result = PIGLIT_PASS;
			goto cleanup;
		}

		glUseProgram(prog);
	}

	err = glGetError();
	if (!err) {
		prog_in_use = true;
	} else {
		GLint size;

		glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &size);
		prog_err_info = malloc(MAX2(size, 1));
		prog_err_info[0] = 0;

		glGetProgramInfoLog(prog, size, NULL, prog_err_info);
	}

cleanup:
	for (i = 0; i < num_vertex_shaders; i++) {
		glDeleteShader(vertex_shaders[i]);
	}
	num_vertex_shaders = 0;

	for (i = 0; i < num_tess_ctrl_shaders; i++) {
		glDeleteShader(tess_ctrl_shaders[i]);
	}
	num_tess_ctrl_shaders = 0;

	for (i = 0; i < num_tess_eval_shaders; i++) {
		glDeleteShader(tess_eval_shaders[i]);
	}
	num_tess_eval_shaders = 0;

	for (i = 0; i < num_geometry_shaders; i++) {
		glDeleteShader(geometry_shaders[i]);
	}
	num_geometry_shaders = 0;

	for (i = 0; i < num_fragment_shaders; i++) {
		glDeleteShader(fragment_shaders[i]);
	}
	num_fragment_shaders = 0;

	for (i = 0; i < num_compute_shaders; i++) {
		glDeleteShader(compute_shaders[i]);
	}
	num_compute_shaders = 0;

	return result;
}

static enum piglit_result
process_specialization(enum states state, const char *line)
{
	const char *end = strchrnul(line, '\n');
	const char *next;
	enum { TYPE_FLOAT, TYPE_UINT } type;

	while (line < end && isspace(*line))
		line++;

	if (line >= end || *line == '#')
		return PIGLIT_PASS;

	if (parse_str(line, "uint", &next))
		type = TYPE_UINT;
	else if (parse_str(line, "float", &next))
		type = TYPE_FLOAT;
	else
		goto invalid;

	struct specialization_list *list;

	switch (state) {
	case vertex_shader_specializations:
		list = specializations + 0;
		break;
	case tess_ctrl_shader_specializations:
		list = specializations + 1;
		break;
	case tess_eval_shader_specializations:
		list = specializations + 2;
		break;
	case geometry_shader_specializations:
		list = specializations + 3;
		break;
	case fragment_shader_specializations:
		list = specializations + 4;
		break;
	case compute_shader_specializations:
		list = specializations + 5;
		break;
	default:
		assert(!"Should not get here.");
	}

	if (list->n_entries >= list->buffer_size) {
		if (list->buffer_size == 0)
			list->buffer_size = 1;
		else
			list->buffer_size *= 2;
		list->indices = realloc(list->indices,
					(sizeof list->indices[0]) *
					list->buffer_size);
		list->values = realloc(list->values,
				       (sizeof list->values[0]) *
				       list->buffer_size);
	}

	if (parse_uints(next, list->indices + list->n_entries, 1, &next) != 1)
		goto invalid;

	switch (type) {
	case TYPE_UINT:
		if (parse_uints(next,
				&list->values[list->n_entries].u,
				1,
				&next) != 1)
			goto invalid;
		break;
	case TYPE_FLOAT:
		if (parse_floats(next,
				 &list->values[list->n_entries].f,
				 1,
				 &next) != 1)
			goto invalid;
		break;
	}

	list->n_entries++;

	return PIGLIT_PASS;

 invalid:
	fprintf(stderr, "Invalid specialization line\n");
	return PIGLIT_FAIL;
}

static enum piglit_result
process_test_script(const char *script_name)
{
	unsigned text_size;
	unsigned line_num;
	char *text = piglit_load_text_file(script_name, &text_size);
	enum states state = none;
	const char *line = text;
	enum piglit_result result;

	if (line == NULL) {
		printf("could not read file \"%s\"\n", script_name);
		return PIGLIT_FAIL;
	}

	line_num = 1;

	while (line[0] != '\0') {
		if (line[0] == '[') {
			result = leave_state(state, line, script_name);
			if (result != PIGLIT_PASS)
				return result;

			if (parse_str(line, "[require]", NULL)) {
				state = requirements;
			} else if (parse_str(line, "[vertex shader]", NULL)) {
				state = vertex_shader;
				shader_string = NULL;
			} else if (parse_str(line, "[vertex program]", NULL)) {
				state = vertex_program;
				shader_string = NULL;
			} else if (parse_str(line, "[vertex shader passthrough]", NULL)) {
				state = vertex_shader_passthrough;
				shader_string =
					(char *) passthrough_vertex_shader_source;
				shader_string_size = strlen(shader_string);
			} else if (parse_str(line, "[vertex shader spirv]", NULL)) {
				state = vertex_shader_spirv;
				shader_string = NULL;
			} else if (parse_str(line, "[vertex shader specializations]", NULL)) {
				state = vertex_shader_specializations;
			} else if (parse_str(line, "[tessellation control shader]", NULL)) {
				state = tess_ctrl_shader;
				shader_string = NULL;
			} else if (parse_str(line, "[tessellation control shader spirv]", NULL)) {
				state = tess_ctrl_shader_spirv;
				shader_string = NULL;
			} else if (parse_str(line, "[tessellation control shader specializations]", NULL)) {
				state = tess_ctrl_shader_specializations;
			} else if (parse_str(line, "[tessellation evaluation shader]", NULL)) {
				state = tess_eval_shader;
				shader_string = NULL;
			} else if (parse_str(line, "[tessellation evaluation shader spirv]", NULL)) {
				state = tess_eval_shader_spirv;
				shader_string = NULL;
			} else if (parse_str(line, "[tessellation evaluation shader specializations]", NULL)) {
				state = tess_eval_shader_specializations;
			} else if (parse_str(line, "[geometry shader]", NULL)) {
				state = geometry_shader;
				shader_string = NULL;
			} else if (parse_str(line, "[geometry shader specializations]", NULL)) {
				state = geometry_shader_specializations;
			} else if (parse_str(line, "[geometry shader spirv]", NULL)) {
				state = geometry_shader_spirv;
				shader_string = NULL;
			} else if (parse_str(line, "[geometry shader specializations]", NULL)) {
				state = geometry_shader_specializations;
			} else if (parse_str(line, "[geometry layout]", NULL)) {
				state = geometry_layout;
				shader_string = NULL;
			} else if (parse_str(line, "[fragment shader]", NULL)) {
				state = fragment_shader;
				shader_string = NULL;
			} else if (parse_str(line, "[fragment program]", NULL)) {
				state = fragment_program;
				shader_string = NULL;
			} else if (parse_str(line, "[fragment shader specializations]", NULL)) {
				state = fragment_shader_specializations;
			} else if (parse_str(line, "[fragment shader spirv]", NULL)) {
				state = fragment_shader_spirv;
				shader_string = NULL;
			} else if (parse_str(line, "[fragment shader specializations]", NULL)) {
				state = fragment_shader_specializations;
			} else if (parse_str(line, "[compute shader]", NULL)) {
				state = compute_shader;
				shader_string = NULL;
			} else if (parse_str(line, "[compute shader spirv]", NULL)) {
				state = compute_shader_spirv;
				shader_string = NULL;
			} else if (parse_str(line, "[compute shader specializations]", NULL)) {
				state = compute_shader_specializations;
			} else if (parse_str(line, "[vertex data]", NULL)) {
				state = vertex_data;
				vertex_data_start = NULL;
			} else if (parse_str(line, "[shader include]", NULL)) {
				state = shader_include;
				shader_string = NULL;
			} else if (parse_str(line, "[shader include paths]", NULL)) {
				state = shader_include_paths;
				shader_string = NULL;
			} else if (parse_str(line, "[test]", NULL)) {
				test_start = strchrnul(line, '\n');
				test_start_line_num = line_num + 1;
				if (test_start[0] != '\0')
					test_start++;
				return PIGLIT_PASS;
			} else {
				fprintf(stderr,
					"Unknown section in test script.  "
					"Perhaps missing closing ']'?\n");
				return PIGLIT_FAIL;
			}
		} else {
			switch (state) {
			case none:
			case vertex_shader_passthrough:
				break;

			case requirements:
				result = process_requirement(line);
				if (result != PIGLIT_PASS)
					return result;
				break;

			case geometry_layout:
				process_geometry_layout(line);
				break;

			case vertex_shader:
			case vertex_program:
			case tess_ctrl_shader:
			case tess_eval_shader:
			case geometry_shader:
			case fragment_shader:
			case fragment_program:
			case compute_shader:
			case vertex_shader_spirv:
			case tess_ctrl_shader_spirv:
			case tess_eval_shader_spirv:
			case geometry_shader_spirv:
			case fragment_shader_spirv:
			case compute_shader_spirv:
			case shader_include:
			case shader_include_paths:
				if (shader_string == NULL)
					shader_string = (char *) line;
				break;

			case vertex_shader_specializations:
			case tess_ctrl_shader_specializations:
			case tess_eval_shader_specializations:
			case geometry_shader_specializations:
			case fragment_shader_specializations:
			case compute_shader_specializations: {
				enum piglit_result result =
					process_specialization(state, line);
				if (result != PIGLIT_PASS)
					return result;
				break;
			}

			case vertex_data:
				if (vertex_data_start == NULL)
					vertex_data_start = line;
				break;

			case test:
				break;
			}
		}

		line = strchrnul(line, '\n');
		if (line[0] != '\0')
			line++;

		line_num++;
	}

	return leave_state(state, line, script_name);
}

struct requirement_parse_results {
	bool found_gl;
	bool found_glsl;
	bool found_size;
	bool found_depthbuffer;
	struct component_version gl_version;
	struct component_version glsl_version;
	unsigned size[2];
};

static void
parse_required_config(struct requirement_parse_results *results,
		      const char *script_name)
{
	unsigned text_size;
	char *text = piglit_load_text_file(script_name, &text_size);
	const char *line = text;
	bool in_requirement_section = false;

	results->found_gl = false;
	results->found_glsl = false;
	results->found_size = false;
	results->found_depthbuffer = false;

	if (line == NULL) {
		printf("could not read file \"%s\"\n", script_name);
		piglit_report_result(PIGLIT_FAIL);
	}

	while (line[0] != '\0') {
		if (line[0] == '[') {
			if (in_requirement_section)
				break;
			else
				in_requirement_section = false;
		}

		if (!in_requirement_section) {
			if (parse_str(line, "[require]", NULL)) {
				in_requirement_section = true;
			}
		} else {
			if (parse_str(line, "GL_", NULL)
			    || parse_str(line, "!GL_", NULL)) {
				/* empty */
			} else if (parse_str(line, "GLSL", &line)) {
				enum comparison cmp;
				struct component_version version;

				parse_version_comparison(line, &cmp,
							 &version, VERSION_GLSL);
				if (cmp == greater_equal) {
					results->found_glsl = true;
					version_copy(&results->glsl_version, &version);
				}
			} else if (parse_str(line, "GL", &line)) {
				enum comparison cmp;
				struct component_version version;

				parse_version_comparison(line, &cmp,
							 &version, VERSION_GL);
				if (cmp == greater_equal
				    || cmp == greater
				    || cmp == equal) {
					results->found_gl = true;
					version_copy(&results->gl_version, &version);
				}
			} else if (parse_str(line, "SIZE", &line)) {
				results->found_size = true;
				parse_uints(line, results->size, 2, NULL);
			} else if (parse_str(line, "depthbuffer", NULL)) {
				results->found_depthbuffer = true;
			}
		}

		line = strchrnul(line, '\n');
		if (line[0] != '\0')
			line++;
	}

	free(text);

	if (!in_requirement_section) {
		printf("[require] section missing\n");
		piglit_report_result(PIGLIT_FAIL);
	}

	if (results->found_glsl && results->glsl_version.es && !results->found_gl) {
		printf("%s", "The test specifies a requirement for GLSL ES, "
		       "but specifies no GL requirement\n.");
		piglit_report_result(PIGLIT_FAIL);
	}
}


static void
choose_required_gl_version(struct requirement_parse_results *parse_results,
                           struct component_version *gl_version)
{
	if (parse_results->found_gl) {
		version_copy(gl_version, &parse_results->gl_version);
	} else {
		assert(!parse_results->found_glsl || !parse_results->glsl_version.es);
		version_init(gl_version, VERSION_GL, false, false, false, 10);
	}

	if (gl_version->es)
		return;

	/* Possibly promote the GL version. */
	if (gl_version->num < required_gl_version_from_glsl_version(
			parse_results->glsl_version.num)) {
		gl_version->num = required_gl_version_from_glsl_version(
			parse_results->glsl_version.num);
	}
}

/**
 * Just determine the GLSL version required by the shader script.
 *
 * This function is a bit of a hack that is, unfortunately necessary.  A test
 * script can require a specific GLSL version or a specific GL version.  To
 * satisfy this requirement, the piglit framework code needs to know about the
 * requirement before creating the context.  However, the requirements section
 * can contain other requirements, such as minimum number of uniforms.
 *
 * The requirements section can't be fully processed until after the context
 * is created, but the context can't be created until after the requirements
 * section is processed.  Do a quick scan over the requirements section to find
 * the GL and GLSL version requirements.  Use these to guide context creation.
 */
static void
get_required_config(const char *script_name, bool spirv,
		    struct piglit_gl_test_config *config)
{
	struct requirement_parse_results parse_results;
	struct component_version required_gl_version;

	parse_required_config(&parse_results, script_name);
	choose_required_gl_version(&parse_results, &required_gl_version);

	if (spirv) {
		required_gl_version.es = false;
		required_gl_version.core = true;
		required_gl_version.num = MAX2(required_gl_version.num, 33);
	}

	if (parse_results.found_size) {
		config->window_width = parse_results.size[0];
		config->window_height = parse_results.size[1];
	}

	if (required_gl_version.es) {
		config->supports_gl_es_version = required_gl_version.num;
	} else if (required_gl_version.num >= 31) {
		if (!required_gl_version.compat)
			config->supports_gl_core_version = required_gl_version.num;
		if (!required_gl_version.core)
			config->supports_gl_compat_version = required_gl_version.num;
	} else {
		config->supports_gl_compat_version = 10;
	}

	if (parse_results.found_depthbuffer) {
		config->window_visual |= PIGLIT_GL_VISUAL_DEPTH;
	}
}

/**
 * Check that the GL implementation supports unsigned uniforms
 * (e.g. through glUniform1ui).  If not, terminate the test with a
 * SKIP.
 */
static void
check_unsigned_support(void)
{
	if (gl_version.num < 30 && !piglit_is_extension_supported("GL_EXT_gpu_shader4"))
		piglit_report_result(PIGLIT_SKIP);
}

/**
 * Check that the GL implementation supports double uniforms
 * (e.g. through glUniform1d).  If not, terminate the test with a
 * SKIP.
 */
static void
check_double_support(void)
{
	if (gl_version.num < 40 && !piglit_is_extension_supported("GL_ARB_gpu_shader_fp64"))
		piglit_report_result(PIGLIT_SKIP);
}

/**
 * Check that the GL implementation supports double uniforms
 * (e.g. through glUniform1d).  If not, terminate the test with a
 * SKIP.
 */
static void
check_int64_support(void)
{
	if (!piglit_is_extension_supported("GL_ARB_gpu_shader_int64"))
		piglit_report_result(PIGLIT_SKIP);
}

/**
 * Check that the GL implementation supports shader subroutines
 * If not, terminate the test with a SKIP.
 */
static void
check_shader_subroutine_support(void)
{
	if (gl_version.num < 40 && !piglit_is_extension_supported("GL_ARB_shader_subroutine"))
		piglit_report_result(PIGLIT_SKIP);
}

/**
 * Check that the GL implementation supports texture handles.
 * If not, terminate the test with a SKIP.
 */
static void
check_texture_handle_support(void)
{
	if (!piglit_is_extension_supported("GL_ARB_bindless_texture"))
		piglit_report_result(PIGLIT_SKIP);
}

static bool
get_indexes_and_offset_from_ubo(char *name, struct block_info block_data,
				GLuint *uniform_index_out,
				GLint *block_index_out,
				GLint *offset_out);

/**
 * Handles uploads of UBO uniforms by mapping the buffer and storing
 * the data.  If the uniform is not in a uniform block, returns false.
 */
static bool
set_ubo_uniform(char *name, const char *type,
		const char *line,
		struct block_info block_data)
{
	/* Note: on SPIR-V we can't access to uniform_index as we
	 * could lack the name. We force that with force_no_names on
	 * GLSL
	 */
	GLuint uniform_index;
	GLint block_index;
	GLint offset;
	char *data;
	float f[16];
	double d[16];
	int ints[16];
	unsigned uints[16];
	uint64_t uint64s[16];
	int64_t int64s[16];

	if (!get_indexes_and_offset_from_ubo(name, block_data, &uniform_index,
					     &block_index, &offset)) {
		return false;
	}

	glBindBuffer(GL_UNIFORM_BUFFER,
		     uniform_block_bos[block_index]);
	data = glMapBuffer(GL_UNIFORM_BUFFER, GL_WRITE_ONLY);
	data += offset;

	if (parse_str(type, "float", NULL)) {
		parse_floats(line, f, 1, NULL);
		memcpy(data, f, sizeof(float));
	} else if (parse_str(type, "int64_t", NULL)) {
		parse_int64s(line, int64s, 1, NULL);
		memcpy(data, int64s, sizeof(int64_t));
	} else if (parse_str(type, "uint64_t", NULL)) {
		parse_uint64s(line, uint64s, 1, NULL);
		memcpy(data, uint64s, sizeof(uint64_t));
	} else if (parse_str(type, "int", NULL)) {
		parse_ints(line, ints, 1, NULL);
		memcpy(data, ints, sizeof(int));
	} else if (parse_str(type, "uint", NULL)) {
		parse_uints(line, uints, 1, NULL);
		memcpy(data, uints, sizeof(int));
	} else if (parse_str(type, "double", NULL)) {
		parse_doubles(line, d, 1, NULL);
		memcpy(data, d, sizeof(double));
	} else if (parse_str(type, "vec", NULL)) {
		int elements = type[3] - '0';
		parse_floats(line, f, elements, NULL);
		memcpy(data, f, elements * sizeof(float));
	} else if (parse_str(type, "ivec", NULL)) {
		int elements = type[4] - '0';
		parse_ints(line, ints, elements, NULL);
		memcpy(data, ints, elements * sizeof(int));
	} else if (parse_str(type, "uvec", NULL)) {
		int elements = type[4] - '0';
		parse_uints(line, uints, elements, NULL);
		memcpy(data, uints, elements * sizeof(unsigned));
	} else if (parse_str(type, "i64vec", NULL)) {
		int elements = type[6] - '0';
		parse_int64s(line, int64s, elements, NULL);
		memcpy(data, int64s, elements * sizeof(int64_t));
	} else if (parse_str(type, "u64vec", NULL)) {
		int elements = type[6] - '0';
		parse_uint64s(line, uint64s, elements, NULL);
		memcpy(data, uint64s, elements * sizeof(uint64_t));
	} else if (parse_str(type, "dvec", NULL)) {
		int elements = type[4] - '0';
		parse_doubles(line, d, elements, NULL);
		memcpy(data, d, elements * sizeof(double));
	} else if (parse_str(type, "mat", NULL)) {
		GLint matrix_stride, row_major;
		int cols = type[3] - '0';
		int rows = type[4] == 'x' ? type[5] - '0' : cols;
		int r, c;
		float *matrixdata = (float *)data;

		assert(cols >= 2 && cols <= 4);
		assert(rows >= 2 && rows <= 4);

		parse_floats(line, f, rows * cols, NULL);

		if (!force_no_names) {
			glGetActiveUniformsiv(prog, 1, &uniform_index,
					      GL_UNIFORM_MATRIX_STRIDE,
					      &matrix_stride);
			glGetActiveUniformsiv(prog, 1, &uniform_index,
					      GL_UNIFORM_IS_ROW_MAJOR,
					      &row_major);
		} else {
			matrix_stride = block_data.matrix_stride;
			row_major = block_data.row_major;
		}

		matrix_stride /= sizeof(float);

		/* Expect the data in the .shader_test file to be listed in
		 * column-major order no matter what the layout of the data in
		 * the UBO will be.
		 */
		for (c = 0; c < cols; c++) {
			for (r = 0; r < rows; r++) {
				if (row_major) {
					matrixdata[matrix_stride * r + c] =
						f[c * rows + r];
				} else {
					matrixdata[matrix_stride * c + r] =
						f[c * rows + r];
				}
			}
		}
	} else if (parse_str(type, "dmat", NULL)) {
		GLint matrix_stride, row_major;
		int cols = type[4] - '0';
		int rows = type[5] == 'x' ? type[6] - '0' : cols;
		int r, c;
		double *matrixdata = (double *)data;

		assert(cols >= 2 && cols <= 4);
		assert(rows >= 2 && rows <= 4);

		parse_doubles(line, d, rows * cols, NULL);

		if (!force_no_names) {
			glGetActiveUniformsiv(prog, 1, &uniform_index,
					      GL_UNIFORM_MATRIX_STRIDE,
					      &matrix_stride);
			glGetActiveUniformsiv(prog, 1, &uniform_index,
					      GL_UNIFORM_IS_ROW_MAJOR,
					      &row_major);
		} else {
			matrix_stride = block_data.matrix_stride;
			row_major = block_data.row_major;
		}

		matrix_stride /= sizeof(double);

		/* Expect the data in the .shader_test file to be listed in
		 * column-major order no matter what the layout of the data in
		 * the UBO will be.
		 */
		for (c = 0; c < cols; c++) {
			for (r = 0; r < rows; r++) {
				if (row_major) {
					matrixdata[matrix_stride * r + c] =
						d[c * rows + r];
				} else {
					matrixdata[matrix_stride * c + r] =
						d[c * rows + r];
				}
			}
		}
	} else if (parse_str(type, "handle", NULL)) {
		check_unsigned_support();
		check_texture_handle_support();
		parse_uints(line, uints, 1, NULL);
		GLuint64 handle = get_resident_handle(uints[0])->handle;
		memcpy(data, &handle, sizeof(uint64_t));
	} else {
		printf("unknown uniform type \"%s\" for \"%s\"\n", type, name);
		piglit_report_result(PIGLIT_FAIL);
	}

	glUnmapBuffer(GL_UNIFORM_BUFFER);

	return true;
}

/*
 * Get the uniform_index, block_index and offset of a given
 * uniform. By default it gets those values using the uniform name. If
 * force_no_names mode is active, it uses the current values stored at
 * @block_data. On the latter, uniform index is not filled up.
 */
static bool
get_indexes_and_offset_from_ubo(char *name, struct block_info block_data,
				GLuint *uniform_index_out,
				GLint *block_index_out,
				GLint *offset_out)
{
	GLuint uniform_index = 0;
	GLint block_index;
	GLint offset;
	int name_len = strlen(name);
	GLint array_index = 0;

	if (!num_uniform_blocks)
		return false;

	if (!force_no_names) {
		/* if the uniform is an array, strip the index, as GL
		   prevents non-zero indexes from matching a name */
		if (name[name_len - 1] == ']') {
			int i;

			for (i = name_len - 1; (i > 0) && isdigit(name[i-1]); --i)
				/* empty */;

			array_index = strtol(&name[i], NULL, 0);

			if (i) {
				i--;
				if (name[i] != '[') {
					printf("cannot parse uniform \"%s\"\n", name);
					piglit_report_result(PIGLIT_FAIL);
				}
				name[i] = 0;
			}

		}

		glGetUniformIndices(prog, 1, (const char **)&name, &uniform_index);
		if (uniform_index == GL_INVALID_INDEX) {
			printf("cannot get index of uniform \"%s\"\n", name);
			piglit_report_result(PIGLIT_FAIL);
		}

		glGetActiveUniformsiv(prog, 1, &uniform_index,
				      GL_UNIFORM_BLOCK_INDEX, &block_index);

		if (block_index == -1)
			return false;

		/* if the uniform block is an array, then GetActiveUniformsiv with
		 * UNIFORM_BLOCK_INDEX will have given us the index of the first
		 * element in the array.
		 */
		block_index += block_data.array_index;

		glGetActiveUniformsiv(prog, 1, &uniform_index,
				      GL_UNIFORM_OFFSET, &offset);

		if (name[name_len - 1] == ']') {
			GLint stride;

			glGetActiveUniformsiv(prog, 1, &uniform_index,
					      GL_UNIFORM_ARRAY_STRIDE, &stride);
			offset += stride * array_index;
		}
	} else {
		if (block_data.binding < 0) {
			printf("if you force to use a explicit ubo binding, you "
			       "need to provide it when filling the data with "
			       "\"ubo binding\"\n");
			piglit_report_result(PIGLIT_FAIL);
		}

		/* The mapping could be improved using a hash
		* table. For now, this is enough.
		*/
		block_index = uniform_block_indexes[block_data.binding];

		/* if the uniform block is an array, then GetActiveUniformsiv with
		 * UNIFORM_BLOCK_INDEX will have given us the index of the first
		 * element in the array.
		 */
		block_index += block_data.array_index;

		if (block_data.offset < 0) {
			printf("if you force to use a explicit ubo binding, you "
				"need to provide offset when filling the data with "
				"\"ubo offset\"\n");
			piglit_report_result(PIGLIT_FAIL);
		}
		offset = block_data.offset;
	}

	*offset_out = offset;
	*block_index_out = block_index;
	*uniform_index_out = uniform_index;

	return true;
}

static void
set_uniform(const char *line, struct block_info block_data)
{
	char name[512], type[512];
	float f[16];
	double d[16];
	int ints[16];
	unsigned uints[16];
	int64_t int64s[16];
	uint64_t uint64s[16];
	GLint loc;

	REQUIRE(parse_word_copy(line, type, sizeof(type), &line) &&
		parse_word_copy(line, name, sizeof(name), &line),
		"Invalid set uniform command at: %s\n", line);

	if (isdigit(name[0])) {
		loc = strtol(name, NULL, 0);
	} else {
		GLuint prog;

		if (set_ubo_uniform(name, type, line, block_data))
			return;

		glGetIntegerv(GL_CURRENT_PROGRAM, (GLint *) &prog);
		loc = glGetUniformLocation(prog, name);
		if (loc < 0) {
			if (ignore_missing_uniforms)
				return;
			printf("cannot get location of uniform \"%s\"\n",
			       name);
			piglit_report_result(PIGLIT_FAIL);
		}
        }

	if (parse_str(type, "float", NULL)) {
		parse_floats(line, f, 1, NULL);
		glUniform1fv(loc, 1, f);
		return;
	} else if (parse_str(type, "int64_t", NULL)) {
		check_int64_support();
		parse_int64s(line, int64s, 1, NULL);
		glUniform1i64vARB(loc, 1, int64s);
		return;
	} else if (parse_str(type, "uint64_t", NULL)) {
		check_int64_support();
		parse_uint64s(line, uint64s, 1, NULL);
		glUniform1ui64vARB(loc, 1, uint64s);
		return;
	} else if (parse_str(type, "int", NULL)) {
		parse_ints(line, ints, 1, NULL);
		glUniform1iv(loc, 1, ints);
		return;
	} else if (parse_str(type, "uint", NULL)) {
		check_unsigned_support();
		parse_uints(line, uints, 1, NULL);
		glUniform1uiv(loc, 1, uints);
		return;
	} else if (parse_str(type, "double", NULL)) {
		check_double_support();
		parse_doubles(line, d, 1, NULL);
		glUniform1dv(loc, 1, d);
		return;
	} else if (parse_str(type, "vec", NULL)) {
		switch (type[3]) {
		case '2':
			parse_floats(line, f, 2, NULL);
			glUniform2fv(loc, 1, f);
			return;
		case '3':
			parse_floats(line, f, 3, NULL);
			glUniform3fv(loc, 1, f);
			return;
		case '4':
			parse_floats(line, f, 4, NULL);
			glUniform4fv(loc, 1, f);
			return;
		}
	} else if (parse_str(type, "ivec", NULL)) {
		switch (type[4]) {
		case '2':
			parse_ints(line, ints, 2, NULL);
			glUniform2iv(loc, 1, ints);
			return;
		case '3':
			parse_ints(line, ints, 3, NULL);
			glUniform3iv(loc, 1, ints);
			return;
		case '4':
			parse_ints(line, ints, 4, NULL);
			glUniform4iv(loc, 1, ints);
			return;
		}
	} else if (parse_str(type, "uvec", NULL)) {
		check_unsigned_support();
		switch (type[4]) {
		case '2':
			parse_uints(line, uints, 2, NULL);
			glUniform2uiv(loc, 1, uints);
			return;
		case '3':
			parse_uints(line, uints, 3, NULL);
			glUniform3uiv(loc, 1, uints);
			return;
		case '4':
			parse_uints(line, uints, 4, NULL);
			glUniform4uiv(loc, 1, uints);
			return;
		}
	} else if (parse_str(type, "dvec", NULL)) {
		check_double_support();
		switch (type[4]) {
		case '2':
			parse_doubles(line, d, 2, NULL);
			glUniform2dv(loc, 1, d);
			return;
		case '3':
			parse_doubles(line, d, 3, NULL);
			glUniform3dv(loc, 1, d);
			return;
		case '4':
			parse_doubles(line, d, 4, NULL);
			glUniform4dv(loc, 1, d);
			return;
		}
	} else if (parse_str(type, "i64vec", NULL)) {
		check_int64_support();
		switch (type[6]) {
		case '2':
			parse_int64s(line, int64s, 2, NULL);
			glUniform2i64vARB(loc, 1, int64s);
			return;
		case '3':
			parse_int64s(line, int64s, 3, NULL);
			glUniform3i64vARB(loc, 1, int64s);
			return;
		case '4':
			parse_int64s(line, int64s, 4, NULL);
			glUniform4i64vARB(loc, 1, int64s);
			return;
		}
	} else if (parse_str(type, "u64vec", NULL)) {
		check_int64_support();
		switch (type[6]) {
		case '2':
			parse_uint64s(line, uint64s, 2, NULL);
			glUniform2ui64vARB(loc, 1, uint64s);
			return;
		case '3':
			parse_uint64s(line, uint64s, 3, NULL);
			glUniform3ui64vARB(loc, 1, uint64s);
			return;
		case '4':
			parse_uint64s(line, uint64s, 4, NULL);
			glUniform4ui64vARB(loc, 1, uint64s);
			return;
		}
	} else if (parse_str(type, "mat", NULL) && type[3] != '\0') {
		char cols = type[3];
		char rows = type[4] == 'x' ? type[5] : cols;
		switch (cols) {
		case '2':
			switch (rows) {
			case '2':
				parse_floats(line, f, 4, NULL);
				glUniformMatrix2fv(loc, 1, GL_FALSE, f);
				return;
			case '3':
				parse_floats(line, f, 6, NULL);
				glUniformMatrix2x3fv(loc, 1, GL_FALSE, f);
				return;
			case '4':
				parse_floats(line, f, 8, NULL);
				glUniformMatrix2x4fv(loc, 1, GL_FALSE, f);
				return;
			}
		case '3':
			switch (rows) {
			case '2':
				parse_floats(line, f, 6, NULL);
				glUniformMatrix3x2fv(loc, 1, GL_FALSE, f);
				return;
			case '3':
				parse_floats(line, f, 9, NULL);
				glUniformMatrix3fv(loc, 1, GL_FALSE, f);
				return;
			case '4':
				parse_floats(line, f, 12, NULL);
				glUniformMatrix3x4fv(loc, 1, GL_FALSE, f);
				return;
			}
		case '4':
			switch (rows) {
			case '2':
				parse_floats(line, f, 8, NULL);
				glUniformMatrix4x2fv(loc, 1, GL_FALSE, f);
				return;
			case '3':
				parse_floats(line, f, 12, NULL);
				glUniformMatrix4x3fv(loc, 1, GL_FALSE, f);
				return;
			case '4':
				parse_floats(line, f, 16, NULL);
				glUniformMatrix4fv(loc, 1, GL_FALSE, f);
				return;
			}
		}
	} else if (parse_str(type, "dmat", NULL) && type[4] != '\0') {
		char cols = type[4];
		char rows = type[5] == 'x' ? type[6] : cols;
		switch (cols) {
		case '2':
			switch (rows) {
			case '2':
				parse_doubles(line, d, 4, NULL);
				glUniformMatrix2dv(loc, 1, GL_FALSE, d);
				return;
			case '3':
				parse_doubles(line, d, 6, NULL);
				glUniformMatrix2x3dv(loc, 1, GL_FALSE, d);
				return;
			case '4':
				parse_doubles(line, d, 8, NULL);
				glUniformMatrix2x4dv(loc, 1, GL_FALSE, d);
				return;
			}
		case '3':
			switch (rows) {
			case '2':
				parse_doubles(line, d, 6, NULL);
				glUniformMatrix3x2dv(loc, 1, GL_FALSE, d);
				return;
			case '3':
				parse_doubles(line, d, 9, NULL);
				glUniformMatrix3dv(loc, 1, GL_FALSE, d);
				return;
			case '4':
				parse_doubles(line, d, 12, NULL);
				glUniformMatrix3x4dv(loc, 1, GL_FALSE, d);
				return;
			}
		case '4':
			switch (rows) {
			case '2':
				parse_doubles(line, d, 8, NULL);
				glUniformMatrix4x2dv(loc, 1, GL_FALSE, d);
				return;
			case '3':
				parse_doubles(line, d, 12, NULL);
				glUniformMatrix4x3dv(loc, 1, GL_FALSE, d);
				return;
			case '4':
				parse_doubles(line, d, 16, NULL);
				glUniformMatrix4dv(loc, 1, GL_FALSE, d);
				return;
			}
		}
	} else if (parse_str(type, "handle", NULL)) {
		check_unsigned_support();
		check_texture_handle_support();
		parse_uints(line, uints, 1, NULL);
		glUniformHandleui64ARB(loc, get_resident_handle(uints[0])->handle);
		return;
	}

	printf("unknown uniform type \"%s\"\n", type);
	piglit_report_result(PIGLIT_FAIL);

	return;
}

static void
set_vertex_attrib(const char *line)
{
	char name[512], type[512];
	uint32_t uints[16];
	GLint loc;

	REQUIRE(parse_word_copy(line, type, sizeof(type), &line) &&
		parse_word_copy(line, name, sizeof(name), &line),
		"Invalid set vertex attrib command at: %s\n", line);

	if (isdigit(name[0])) {
		loc = strtol(name, NULL, 0);
	} else {
		GLuint prog;

		glGetIntegerv(GL_CURRENT_PROGRAM, (GLint *) &prog);
		loc = glGetAttribLocation(prog, name);
		if (loc < 0) {
			if (ignore_missing_uniforms)
				return;
			printf("cannot get location of vertex attrib \"%s\"\n",
			       name);
			piglit_report_result(PIGLIT_FAIL);
		}
        }

	if (parse_str(type, "handle", NULL)) {
		check_unsigned_support();
		check_texture_handle_support();
		parse_uints(line, uints, 1, NULL);
		glVertexAttribL1ui64ARB(loc, get_resident_handle(uints[0])->handle);
		return;
	}

	printf("unknown vertex attrib type \"%s\"\n", type);
	printf("use [vertex data] instead if possible\n");
	piglit_report_result(PIGLIT_FAIL);

	return;
}

static GLenum lookup_shader_type(GLuint idx)
{
	switch (idx) {
	case 0:
		return GL_VERTEX_SHADER;
	case 1:
		return GL_FRAGMENT_SHADER;
	case 2:
		return GL_GEOMETRY_SHADER;
	case 3:
		return GL_TESS_CONTROL_SHADER;
	case 4:
		return GL_TESS_EVALUATION_SHADER;
	case 5:
		return GL_COMPUTE_SHADER;
	default:
		return 0;
	}
}

static GLenum get_shader_from_string(const char *name, int *idx)
{
	if (parse_str(name, "GL_VERTEX_SHADER", NULL)) {
		*idx = 0;
		return GL_VERTEX_SHADER;
	}
	if (parse_str(name, "GL_FRAGMENT_SHADER", NULL)) {
		*idx = 1;
		return GL_FRAGMENT_SHADER;
	}
	if (parse_str(name, "GL_GEOMETRY_SHADER", NULL)) {
		*idx = 2;
		return GL_GEOMETRY_SHADER;
	}
	if (parse_str(name, "GL_TESS_CONTROL_SHADER", NULL)) {
		*idx = 3;
		return GL_TESS_CONTROL_SHADER;
	}
	if (parse_str(name, "GL_TESS_EVALUATION_SHADER", NULL)) {
		*idx = 4;
		return GL_TESS_EVALUATION_SHADER;
	}
	if (parse_str(name, "GL_COMPUTE_SHADER", NULL)) {
		*idx = 5;
		return GL_COMPUTE_SHADER;
	}
	return 0;
}

static void
free_subroutine_uniforms(void)
{
	int sidx;
	for (sidx = 0; sidx < 4; sidx++) {
		free(subuniform_locations[sidx]);
		subuniform_locations[sidx] = NULL;
	}
}

static void
program_subroutine_uniforms(void)
{
	int sidx;
	int stype;

	for (sidx = 0; sidx < 4; sidx++) {

		if (num_subuniform_locations[sidx] == 0)
			continue;

		stype = lookup_shader_type(sidx);
		if (!stype)
			continue;

		glUniformSubroutinesuiv(stype, num_subuniform_locations[sidx], subuniform_locations[sidx]);
	}
}

static void
set_subroutine_uniform(const char *line)
{
	GLuint prog;
	char type[512];
	char name[512];
	char subname[512];
	GLint loc;
	GLuint idx;
	GLenum ptype = 0;
	int sidx = 0;

	REQUIRE(parse_word_copy(line, type, sizeof(type), &line) &&
		parse_word_copy(line, name, sizeof(name), &line) &&
		parse_word_copy(line, subname, sizeof(subname), &line),
		"Invalid set subroutine uniform command at: %s\n", line);

	ptype = get_shader_from_string(type, &sidx);
	if (ptype == 0) {
		printf("illegal type in subroutine uniform\n");
		piglit_report_result(PIGLIT_FAIL);
	}

	glGetIntegerv(GL_CURRENT_PROGRAM, (GLint *) &prog);

	if (num_subuniform_locations[sidx] == 0) {
		glGetProgramStageiv(prog, ptype, GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS,
				    &num_subuniform_locations[sidx]);

		if (num_subuniform_locations[sidx] == 0) {
			printf("illegal subroutine uniform specified\n");
			piglit_report_result(PIGLIT_FAIL);
		}

		subuniform_locations[sidx] = calloc(num_subuniform_locations[sidx], sizeof(GLuint));
		if (!subuniform_locations[sidx])
			piglit_report_result(PIGLIT_FAIL);
	}

	loc = glGetSubroutineUniformLocation(prog, ptype, name);
	if (loc < 0) {
		if (ignore_missing_uniforms)
			return;
		printf("cannot get location of subroutine uniform \"%s\"\n",
		       name);
		piglit_report_result(PIGLIT_FAIL);
	}

	idx = glGetSubroutineIndex(prog, ptype, subname);
	if (idx == GL_INVALID_INDEX) {
		printf("cannot get index of subroutine uniform \"%s\"\n",
		       subname);
		piglit_report_result(PIGLIT_FAIL);
	}

	subuniform_locations[sidx][loc] = idx;
	return;
}

/**
 * Query values from current program using glGetProgram.
 *
 * Format of the command:
 *   verify program_query GL_PNAME_ENUM integer
 *
 * or
 *
 *   verify program_query GL_PNAME_ENUM <enum>
 *
 * Note: GL_COMPUTE_WORK_GROUP_SIZE is not supported, as is the only
 * query that requires a params with more than one component, and we
 * want to keep things simple.
 *
 */
static void
verify_program_query(const char *line)
{
	static const struct string_to_enum all_pnames[] = {
		ENUM_STRING(GL_DELETE_STATUS),
		ENUM_STRING(GL_LINK_STATUS),
		ENUM_STRING(GL_VALIDATE_STATUS),
		ENUM_STRING(GL_INFO_LOG_LENGTH),
		ENUM_STRING(GL_ATTACHED_SHADERS),
		ENUM_STRING(GL_ACTIVE_ATOMIC_COUNTER_BUFFERS),
		ENUM_STRING(GL_ACTIVE_ATTRIBUTES),
		ENUM_STRING(GL_ACTIVE_ATTRIBUTE_MAX_LENGTH),
		ENUM_STRING(GL_ACTIVE_UNIFORMS),
		ENUM_STRING(GL_ACTIVE_UNIFORM_BLOCKS),
		ENUM_STRING(GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH),
		ENUM_STRING(GL_ACTIVE_UNIFORM_MAX_LENGTH),
		ENUM_STRING(GL_COMPUTE_WORK_GROUP_SIZE),
		ENUM_STRING(GL_PROGRAM_BINARY_LENGTH),
		ENUM_STRING(GL_TRANSFORM_FEEDBACK_BUFFER_MODE),
		ENUM_STRING(GL_TRANSFORM_FEEDBACK_VARYINGS),
		ENUM_STRING(GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH),
		ENUM_STRING(GL_GEOMETRY_VERTICES_OUT),
		ENUM_STRING(GL_GEOMETRY_INPUT_TYPE),
		ENUM_STRING(GL_GEOMETRY_OUTPUT_TYPE),
		{ NULL, 0 }
	};

	static const struct string_to_enum all_possible_param_enums[] = {
		ENUM_STRING(GL_TRUE),
		ENUM_STRING(GL_FALSE),
		ENUM_STRING(GL_SEPARATE_ATTRIBS),
		ENUM_STRING(GL_INTERLEAVED_ATTRIBS),
		ENUM_STRING(GL_POINTS),
		ENUM_STRING(GL_LINES),
		ENUM_STRING(GL_LINES_ADJACENCY),
		ENUM_STRING(GL_TRIANGLES),
		ENUM_STRING(GL_TRIANGLES_ADJACENCY),
		ENUM_STRING(GL_LINE_STRIP),
		ENUM_STRING(GL_TRIANGLE_STRIP),
		ENUM_STRING(GL_COMPUTE_WORK_GROUP_SIZE),
		{ NULL, 0 }
	};

	unsigned pname;
	int expected;
	int value;

	REQUIRE(parse_enum_tab(all_pnames, line,
			       &pname, &line),
		"Bad glGetProgram pname at: %s\n", line);

	REQUIRE(parse_enum_tab(all_possible_param_enums, line, (unsigned *)&expected, &line) ||
		parse_int(line, &expected, &line),
		"Bad expected value at: %s\n", line);

	if (pname == GL_COMPUTE_WORK_GROUP_SIZE) {
	   /* TODO: eventually it would be good to add support for
	    * this parameter. What it makes different to the rest is
	    * that it requires a param with more than one component
	    */
	   fprintf(stderr, "glGetProgram queries with GL_COMPUTE_WORK_GROUP_SIZE "
		   "not supported yet\n");
	   piglit_report_result(PIGLIT_FAIL);
	}

	glGetProgramiv(prog, pname, &value);

	if (expected != value) {
		fprintf(stderr, "glGetProgram(%s): expected %d, got %d\n",
			piglit_get_gl_enum_name(pname), expected, value);
		piglit_report_result(PIGLIT_FAIL);
	}
}

/**
 * Query a uniform using glGetActiveUniformsiv
 *
 * Format of the command:
 *
 *     active uniform uniform_name GL_PNAME_ENUM integer
 *
 * or
 *
 *     active uniform uniform_name GL_PNAME_ENUM GL_TYPE_ENUM
 */
static void
active_uniform(const char *line)
{
	static const struct string_to_enum all_pnames[] = {
		ENUM_STRING(GL_UNIFORM_TYPE),
		ENUM_STRING(GL_UNIFORM_SIZE),
		ENUM_STRING(GL_UNIFORM_NAME_LENGTH),
		ENUM_STRING(GL_UNIFORM_BLOCK_INDEX),
		ENUM_STRING(GL_UNIFORM_OFFSET),
		ENUM_STRING(GL_UNIFORM_ARRAY_STRIDE),
		ENUM_STRING(GL_UNIFORM_MATRIX_STRIDE),
		ENUM_STRING(GL_UNIFORM_IS_ROW_MAJOR),
		ENUM_STRING(GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX),
		{ NULL, 0 }
	};

	const char *rest = line;
	char name[512];
	char name_buf[512];
	unsigned pname;
	int expected;
	int i;
	int num_active_uniforms;

	REQUIRE(parse_word_copy(rest, name, sizeof(name), &rest),
		"Bad uniform name at: %s\n", line);

	REQUIRE(parse_enum_tab(all_pnames, rest, &pname, &rest),
		"Bad glGetUniformsiv pname at: %s\n", line);

	REQUIRE(parse_enum_tab(all_types, rest, (unsigned *)&expected, &rest) ||
		parse_int(rest, &expected, &rest),
		"Bad expected value at: %s\n", line);

	glGetProgramiv(prog, GL_ACTIVE_UNIFORMS, &num_active_uniforms);
	for (i = 0; i < num_active_uniforms; i++) {
		GLint got;
		GLint size;
		GLenum type;
		GLsizei name_len;
		bool pass = true;

		glGetActiveUniform(prog, i, sizeof(name_buf), &name_len,
				   &size, &type, name_buf);

		if (!piglit_check_gl_error(GL_NO_ERROR)) {
			fprintf(stderr, "glGetActiveUniform error\n");
			piglit_report_result(PIGLIT_FAIL);
		}

		if (strcmp(name, name_buf) != 0)
			continue;

		/* If the requested pname is one of the values that
		 * glGetActiveUniform happens to return, check the value
		 * returned by that function too.
		 */
		switch (pname) {
		case GL_UNIFORM_TYPE:
			got = (GLint) type;
			break;

		case GL_UNIFORM_SIZE:
			got = size;
			break;

		case GL_UNIFORM_NAME_LENGTH:
			got = name_len;
			break;

		default:
			/* This ensures the check below will pass when the
			 * requested enum is not one of the values already
			 * returned by glGetActiveUniform.
			 */
			got = expected;
			break;
		}

		if (got != expected) {
			fprintf(stderr,
				"glGetActiveUniform(%s, %s): "
				"expected %d (0x%04x), got %d (0x%04x)\n",
				name, piglit_get_gl_enum_name(pname),
				expected, expected, got, got);
			pass = false;
		}

		/* Set 'got' to some value in case glGetActiveUniformsiv
		 * doesn't write to it.  That should only be able to occur
		 * when the function raises a GL error, but "should" is kind
		 * of a funny word.
		 */
		got = ~expected;
		glGetActiveUniformsiv(prog, 1, (GLuint *) &i, pname, &got);

		if (!piglit_check_gl_error(GL_NO_ERROR)) {
			fprintf(stderr, "glGetActiveUniformsiv error\n");
			piglit_report_result(PIGLIT_FAIL);
		}

		if (got != expected) {
			fprintf(stderr,
				"glGetActiveUniformsiv(%s, %s): "
				"expected %d, got %d\n",
				name, piglit_get_gl_enum_name(pname),
				expected, got);
			pass = false;
		}

		if (!pass)
			piglit_report_result(PIGLIT_FAIL);

		return;
	}

	fprintf(stderr, "No active uniform named \"%s\"\n", name);
	piglit_report_result(PIGLIT_FAIL);
	return;
}


/*
 * Confirms if @resource_index is a given resource, within @interface_type.
 *
 * In order to identify it, it uses by default @resource_name. If
 * force_no_names mode is activated it uses the binding contained at
 * @block_data. Note that for the latter, only ubos or ssbos are
 * supported as @interface_type.
 *
 * @resource_name_buf, @prop and @value_expected are only used for
 * some extra checks.
 *
 * Return true if @resource_index is the resource within
 * @interface_type we search for. false otherwise.
 */
static bool
confirm_program_resource(GLenum interface_type, GLuint resource_index,
			 const char *resource_name, char *resource_name_buf,
			 struct block_info block_data, unsigned prop,
			 int value_expected)
{
	if (!force_no_names) {
		GLsizei resource_name_len;

		glGetProgramResourceName(prog, interface_type, resource_index,
					 512, &resource_name_len,
					 resource_name_buf);

		if (!piglit_check_gl_error(GL_NO_ERROR)) {
			fprintf(stderr, "glGetProgramResourceName error\n");
			piglit_report_result(PIGLIT_FAIL);
		}

		if (strcmp(resource_name, resource_name_buf) != 0)
			return false;

		 /* glGetProgramResourceName does not consider the NULL
		  * terminator when returning the name length, however,
		  * glGetProgramResourceiv does. We take that into account
		  * when doing the comparison.
		  */
		if (prop == GL_NAME_LENGTH &&
		    resource_name_len != (value_expected - 1)) {
			fprintf(stderr,
				"glGetProgramResourceName(%s, %s, %s): "
				"expected length: %d (0x%04x), "
				"got length: %d (0x%04x)\n",
				piglit_get_gl_enum_name(interface_type),
				resource_name, piglit_get_gl_enum_name(prop),
				value_expected - 1, value_expected - 1,
				resource_name_len, resource_name_len);
			piglit_report_result(PIGLIT_FAIL);
		}
	} else {
		unsigned binding_prop = GL_BUFFER_BINDING;
		int current_binding = 0;
		GLint length;

		if (interface_type != GL_SHADER_STORAGE_BLOCK &&
		    interface_type != GL_UNIFORM_BLOCK) {
			fprintf(stderr,
				"active_program_interface queries under force_no_names "
				"mode are only supported for GL_SHADER_STORAGE_BLOCK "
				"or GL_UNIFORM_BLOCK interfaces\n");
			piglit_report_result(PIGLIT_FAIL);
		}

		glGetProgramResourceiv(prog, interface_type,
				       resource_index, 1, &binding_prop, 1,
				       &length, &current_binding);
		if (!piglit_check_gl_error(GL_NO_ERROR)) {
			fprintf(stderr, "glGetProgramResourceiv error\n");
			piglit_report_result(PIGLIT_FAIL);
		}

		if (block_data.binding != current_binding)
			return false;
	}

	return true;
}

/**
 * Query an active resource using ARB_program_interface_query functions
 *
 * Format of the command:
 *
 *  verify program_interface_query GL_INTERFACE_TYPE_ENUM name GL_PNAME_ENUM integer
 *
 * or
 *
 *  verify program_interface_query GL_INTERFACE_TYPE_ENUM name GL_PNAME_ENUM GL_TYPE_ENUM
 */
static void
active_program_interface(const char *line, struct block_info block_data)
{
	static const struct string_to_enum all_props[] = {
		ENUM_STRING(GL_TYPE),
		ENUM_STRING(GL_ARRAY_SIZE),
		ENUM_STRING(GL_NAME_LENGTH),
		ENUM_STRING(GL_BLOCK_INDEX),
		ENUM_STRING(GL_OFFSET),
		ENUM_STRING(GL_ARRAY_STRIDE),
		ENUM_STRING(GL_MATRIX_STRIDE),
		ENUM_STRING(GL_IS_ROW_MAJOR),
		ENUM_STRING(GL_ATOMIC_COUNTER_BUFFER_INDEX),
		ENUM_STRING(GL_BUFFER_BINDING),
		ENUM_STRING(GL_BUFFER_DATA_SIZE),
		ENUM_STRING(GL_NUM_ACTIVE_VARIABLES),
		ENUM_STRING(GL_REFERENCED_BY_VERTEX_SHADER),
		ENUM_STRING(GL_REFERENCED_BY_TESS_CONTROL_SHADER),
		ENUM_STRING(GL_REFERENCED_BY_TESS_EVALUATION_SHADER),
		ENUM_STRING(GL_REFERENCED_BY_GEOMETRY_SHADER),
		ENUM_STRING(GL_REFERENCED_BY_FRAGMENT_SHADER),
		ENUM_STRING(GL_REFERENCED_BY_COMPUTE_SHADER),
		ENUM_STRING(GL_TOP_LEVEL_ARRAY_SIZE),
		ENUM_STRING(GL_TOP_LEVEL_ARRAY_STRIDE),
		ENUM_STRING(GL_LOCATION),
		ENUM_STRING(GL_LOCATION_INDEX),
		ENUM_STRING(GL_LOCATION_COMPONENT),
		ENUM_STRING(GL_IS_PER_PATCH),
		ENUM_STRING(GL_NUM_COMPATIBLE_SUBROUTINES),
		ENUM_STRING(GL_COMPATIBLE_SUBROUTINES),
		{ NULL, 0 }
	};

	static const struct string_to_enum all_program_interface[] = {
		ENUM_STRING(GL_UNIFORM),
		ENUM_STRING(GL_UNIFORM_BLOCK),
		ENUM_STRING(GL_PROGRAM_INPUT),
		ENUM_STRING(GL_PROGRAM_OUTPUT),
		ENUM_STRING(GL_BUFFER_VARIABLE),
		ENUM_STRING(GL_SHADER_STORAGE_BLOCK),
		ENUM_STRING(GL_ATOMIC_COUNTER_BUFFER),
		ENUM_STRING(GL_VERTEX_SUBROUTINE),
		ENUM_STRING(GL_TESS_CONTROL_SUBROUTINE),
		ENUM_STRING(GL_TESS_EVALUATION_SUBROUTINE),
		ENUM_STRING(GL_GEOMETRY_SUBROUTINE),
		ENUM_STRING(GL_FRAGMENT_SUBROUTINE),
		ENUM_STRING(GL_COMPUTE_SUBROUTINE),
		ENUM_STRING(GL_VERTEX_SUBROUTINE_UNIFORM),
		ENUM_STRING(GL_TESS_CONTROL_SUBROUTINE_UNIFORM),
		ENUM_STRING(GL_TESS_EVALUATION_SUBROUTINE_UNIFORM),
		ENUM_STRING(GL_GEOMETRY_SUBROUTINE_UNIFORM),
		ENUM_STRING(GL_FRAGMENT_SUBROUTINE_UNIFORM),
		ENUM_STRING(GL_COMPUTE_SUBROUTINE_UNIFORM),
		ENUM_STRING(GL_TRANSFORM_FEEDBACK_VARYING),
		{ NULL, 0 }
	};

	char name[512];
	char name_buf[512];
	unsigned prop, interface_type;
	int expected;
	int i;
	int num_active_buffers;

	if (!piglit_is_extension_supported("GL_ARB_program_interface_query") &&
	    piglit_get_gl_version() < 43) {
		fprintf(stderr,
			"GL_ARB_program_interface_query not supported or "
			"OpenGL version < 4.3\n");
		return;
	}

	REQUIRE(parse_enum_tab(all_program_interface, line,
			       &interface_type, &line),
		"Bad program interface at: %s\n", line);
	REQUIRE(parse_word_copy(line, name, sizeof(name), &line),
		"Bad program resource name at: %s\n", line);
	REQUIRE(parse_enum_tab(all_props, line, &prop, &line),
		"Bad glGetProgramResourceiv pname at: %s\n", line);
	REQUIRE(parse_enum_tab(all_types, line, (unsigned *)&expected, &line) ||
		parse_int(line, &expected, &line),
		"Bad expected value at: %s\n", line);

	glGetProgramInterfaceiv(prog, interface_type,
				GL_ACTIVE_RESOURCES, &num_active_buffers);
	for (i = 0; i < num_active_buffers; i++) {
		GLint got;
		GLint length;
		bool pass = true;

		if (!confirm_program_resource(interface_type, i, name, name_buf,
					      block_data, prop, expected))
			continue;

		/* Set 'got' to some value in case glGetActiveUniformsiv
		 * doesn't write to it.  That should only be able to occur
		 * when the function raises a GL error, but "should" is kind
		 * of a funny word.
		 */
		got = ~expected;
		glGetProgramResourceiv(prog, interface_type,
				       i, 1, &prop, 1,
				       &length, &got);

		if (!piglit_check_gl_error(GL_NO_ERROR)) {
			fprintf(stderr, "glGetProgramResourceiv error\n");
			piglit_report_result(PIGLIT_FAIL);
		}

		if (got != expected) {
			fprintf(stderr,
				"glGetProgramResourceiv(%s, %s): "
				"expected %d, got %d\n",
				name, piglit_get_gl_enum_name(prop),
				expected, got);
			pass = false;
		}

		if (!pass)
			piglit_report_result(PIGLIT_FAIL);

		return;
	}

	if (!force_no_names)
		fprintf(stderr, "No active resource named \"%s\"\n", name);
	else
		fprintf(stderr, "No active resource with binding %i\n", block_data.binding);

	piglit_report_result(PIGLIT_FAIL);
	return;
}


static unsigned
query_index(GLenum query)
{
	switch (query) {
	case GL_PRIMITIVES_GENERATED:
		return 0;
	case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
		return 1;
	default:
		return 0;
	}
 }
/**
 * Checks if the GL_QUERY_RESULT of a given query object has the
 * expected value.
 *
 * Note that right now it only supports the following two queries:
 *    * GL_PRIMITIVES_GENERATED
 *    * GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN
 *
 * As right now we are only creating those query objects with xfb
 * rendering.
 *
 * Format of the command:
 *
 *  verify query_object GL_PRIMITIVES_GENERATED integer
 *
 * or
 *
 *  verify query_object GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN integer
 */
static enum piglit_result
verify_query_object_result(const char *line)
{
	static const struct string_to_enum all_targets[] = {
		ENUM_STRING(GL_PRIMITIVES_GENERATED),
		ENUM_STRING(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN),
		{ NULL, 0 }
	};

	unsigned target;
	unsigned expected;
	unsigned value = 0;
	unsigned index = 0;

	REQUIRE(parse_enum_tab(all_targets, line,
			       &target, &line),
		"Bad query object target at: %s\n", line);

	REQUIRE(parse_uint(line, &expected, &line),
		"Bad expected value at: %s\n", line);

	/* Previous require already checks if the target is one of the
	 * supported ones.
	 */
	index = query_index(target);

	if (queries[index]) {
		glGetQueryObjectuiv(queries[index], GL_QUERY_RESULT, &value);
	} else {
		fprintf(stderr, "query object for target %s is not initialized. "
			"Did you forget to call \"xfb draw arrays\"?\n",
			piglit_get_gl_enum_name(target));
		return PIGLIT_FAIL;
	}

	if (expected != value) {
		fprintf(stderr, "glGetQueryObjectuiv(GL_QUERY_RESULT) for a %s "
			"query object: expected %d, got %d\n",
			piglit_get_gl_enum_name(target), expected, value);
		return PIGLIT_FAIL;
	}
	return PIGLIT_PASS;
}

static void
set_parameter(const char *line)
{
	float f[4];
	int index, count;
	char type[1024];

	count = sscanf(line, "%s %d (%f , %f , %f , %f)",
		       type, &index, &f[0], &f[1], &f[2], &f[3]);
	if (count != 6) {
		fprintf(stderr, "Couldn't parse parameter command:\n%s\n", line);
		piglit_report_result(PIGLIT_FAIL);
	}

	if (parse_str(type, "env_vp", NULL)) {
		glProgramEnvParameter4fvARB(GL_VERTEX_PROGRAM_ARB, index, f);
	} else if (parse_str(type, "local_vp", NULL)) {
		glProgramLocalParameter4fvARB(GL_VERTEX_PROGRAM_ARB, index, f);
	} else if (parse_str(type, "env_fp", NULL)) {
		glProgramEnvParameter4fvARB(GL_FRAGMENT_PROGRAM_ARB, index, f);
	} else if (parse_str(type, "local_fp", NULL)) {
		glProgramLocalParameter4fvARB(GL_FRAGMENT_PROGRAM_ARB, index, f);
	} else {
		fprintf(stderr, "Unknown parameter type `%s'\n", type);
		piglit_report_result(PIGLIT_FAIL);
	}
}

static void
set_patch_parameter(const char *line)
{
#ifdef PIGLIT_USE_OPENGL
	float f[4];
	int i, count;
	const char *const line0 = line;

	if (gl_version.num < 40)
		piglit_require_extension("GL_ARB_tessellation_shader");

	if (parse_str(line, "vertices ", &line)) {
		count = sscanf(line, "%d", &i);
		if (count != 1) {
			fprintf(stderr, "Couldn't parse patch parameter command:\n%s\n", line0);
			piglit_report_result(PIGLIT_FAIL);
		}
		glPatchParameteri(GL_PATCH_VERTICES, i);
	} else if (parse_str(line, "default level outer ", &line)) {
		count = sscanf(line, "%f %f %f %f", &f[0], &f[1], &f[2], &f[3]);
		if (count != 4) {
			fprintf(stderr, "Couldn't parse patch parameter command:\n%s\n", line0);
			piglit_report_result(PIGLIT_FAIL);
		}
		glPatchParameterfv(GL_PATCH_DEFAULT_OUTER_LEVEL, f);
	} else if (parse_str(line, "default level inner ", &line)) {
		count = sscanf(line, "%f %f", &f[0], &f[1]);
		if (count != 2) {
			fprintf(stderr, "Couldn't parse patch parameter command:\n%s\n", line0);
			piglit_report_result(PIGLIT_FAIL);
		}
		glPatchParameterfv(GL_PATCH_DEFAULT_INNER_LEVEL, f);
	} else {
		fprintf(stderr, "Couldn't parse patch parameter command:\n%s\n", line);
		piglit_report_result(PIGLIT_FAIL);
	}
#else
	printf("patch parameters are only available in desktop GL\n");
	piglit_report_result(PIGLIT_SKIP);
#endif
}

static void
set_provoking_vertex(const char *line)
{
	if (parse_str(line, "first", NULL)) {
		glProvokingVertexEXT(GL_FIRST_VERTEX_CONVENTION_EXT);
	} else if (parse_str(line, "last", NULL)) {
		glProvokingVertexEXT(GL_LAST_VERTEX_CONVENTION_EXT);
	} else {
		fprintf(stderr, "Unknown provoking vertex parameter `%s'\n", line);
		piglit_report_result(PIGLIT_FAIL);
	}
}

static const struct string_to_enum enable_table[] = {
	{ "GL_CLIP_PLANE0", GL_CLIP_PLANE0 },
	{ "GL_CLIP_PLANE1", GL_CLIP_PLANE1 },
	{ "GL_CLIP_PLANE2", GL_CLIP_PLANE2 },
	{ "GL_CLIP_PLANE3", GL_CLIP_PLANE3 },
	{ "GL_CLIP_PLANE4", GL_CLIP_PLANE4 },
	{ "GL_CLIP_PLANE5", GL_CLIP_PLANE5 },
	{ "GL_CLIP_PLANE6", GL_CLIP_PLANE0+6 },
	{ "GL_CLIP_PLANE7", GL_CLIP_PLANE0+7 },
	{ "GL_VERTEX_PROGRAM_TWO_SIDE", GL_VERTEX_PROGRAM_TWO_SIDE },
	{ "GL_PROGRAM_POINT_SIZE", GL_PROGRAM_POINT_SIZE },
	{ "GL_DEPTH_TEST", GL_DEPTH_TEST },
	{ "GL_DEPTH_CLAMP", GL_DEPTH_CLAMP },
	{ NULL, 0 }
};

static void
do_enable_disable(const char *line, bool enable_flag)
{
	GLenum value;
	REQUIRE(parse_enum_tab(enable_table, line, &value, NULL),
		"Bad enable/disable enum at: %s\n", line);

	if (enable_flag)
		glEnable(value);
	else
		glDisable(value);
}

static const struct string_to_enum hint_target_table[] = {
	ENUM_STRING(GL_LINE_SMOOTH_HINT),
	ENUM_STRING(GL_POLYGON_SMOOTH_HINT),
	ENUM_STRING(GL_TEXTURE_COMPRESSION_HINT),
	ENUM_STRING(GL_FRAGMENT_SHADER_DERIVATIVE_HINT),
	{ NULL, 0 }
};

static const struct string_to_enum hint_param_table[] = {
	ENUM_STRING(GL_FASTEST),
	ENUM_STRING(GL_NICEST),
	ENUM_STRING(GL_DONT_CARE),
	{ NULL, 0 }
};

static void do_hint(const char *line)
{
	unsigned target, param;
	REQUIRE(parse_enum_tab(hint_target_table, line, &target, &line),
		"Bad hint target at: %s\n", line);
	REQUIRE(parse_enum_tab(hint_param_table, line, &param, &line),
		"Bad hint param at: %s\n", line);

	glHint(target, param);
}

static void
draw_instanced_rect(int primcount, float x, float y, float w, float h)
{
	float verts[4][4];

	piglit_require_extension("GL_ARB_draw_instanced");

	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;

	glVertexPointer(4, GL_FLOAT, 0, verts);
	glEnableClientState(GL_VERTEX_ARRAY);

	glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, primcount);

	glDisableClientState(GL_VERTEX_ARRAY);
}


static GLenum
decode_drawing_mode(const char *mode_str)
{
	int i;

	for (i = GL_POINTS; i <= GL_PATCHES; ++i) {
		const char *name = piglit_get_prim_name(i);
		if (0 == strcmp(mode_str, name))
			return i;
	}

	printf("unknown drawing mode \"%s\"\n", mode_str);
	piglit_report_result(PIGLIT_FAIL);

	/* Should not be reached, but return 0 to avoid compiler warning */
	return 0;
}

static void
handle_texparameter(const char *line)
{
	static const struct string_to_enum compare_funcs[] = {
		{ "greater", GL_GREATER },
		{ "gequal", GL_GEQUAL },
		{ "less", GL_LESS },
		{ "lequal", GL_LEQUAL },
		{ "equal", GL_EQUAL },
		{ "notequal", GL_NOTEQUAL },
		{ "never", GL_NEVER },
		{ "always", GL_ALWAYS },
		{ NULL, 0 },
	};
	static const struct string_to_enum depth_modes[] = {
		{ "intensity", GL_INTENSITY },
		{ "luminance", GL_LUMINANCE },
		{ "alpha", GL_ALPHA },
		{ "red", GL_RED }, /* Requires GL 3.0 or GL_ARB_texture_rg */
		{ NULL, 0 },
	};
	static const struct string_to_enum min_filter_modes[] = {
		{ "nearest_mipmap_nearest", GL_NEAREST_MIPMAP_NEAREST },
		{ "linear_mipmap_nearest",  GL_LINEAR_MIPMAP_NEAREST  },
		{ "nearest_mipmap_linear",  GL_NEAREST_MIPMAP_LINEAR  },
		{ "linear_mipmap_linear",   GL_LINEAR_MIPMAP_LINEAR   },
		{ "nearest",                GL_NEAREST                },
		{ "linear",                 GL_LINEAR                 },
		{ NULL, 0 }
	};
	static const struct string_to_enum mag_filter_modes[] = {
		{ "nearest",                GL_NEAREST                },
		{ "linear",                 GL_LINEAR                 },
		{ NULL, 0 }
	};
	static const struct string_to_enum wrap_modes[] = {
		{ "repeat",                 GL_REPEAT                },
		{ "clamp_to_edge",          GL_CLAMP_TO_EDGE         },
		{ "clamp_to_border",        GL_CLAMP_TO_BORDER       },
		{ NULL, 0 }
	};
	static const struct string_to_enum swizzle_modes[] = {
		{ "red", GL_RED },
		{ "green", GL_GREEN },
		{ "blue", GL_BLUE },
		{ "alpha", GL_ALPHA },
		{ NULL, 0 }
	};
	unsigned target = 0;
	GLenum parameter = GL_NONE;
	const char *parameter_name = NULL;
	const struct string_to_enum *strings = NULL;
	unsigned value;

	REQUIRE(parse_tex_target(line, &target, &line),
		"Bad texture target at: %s\n", line);

	if (parse_str(line, "compare_func ", &line)) {
		parameter = GL_TEXTURE_COMPARE_FUNC;
		parameter_name = "compare_func";
		strings = compare_funcs;
	} else if (parse_str(line, "depth_mode ", &line)) {
		parameter = GL_DEPTH_TEXTURE_MODE;
		parameter_name = "depth_mode";
		strings = depth_modes;
	} else if (parse_str(line, "min ", &line)) {
		parameter = GL_TEXTURE_MIN_FILTER;
		parameter_name = "min";
		strings = min_filter_modes;
	} else if (parse_str(line, "mag ", &line)) {
		parameter = GL_TEXTURE_MAG_FILTER;
		parameter_name = "mag";
		strings = mag_filter_modes;
	} else if (parse_str(line, "wrap_s ", &line)) {
		parameter = GL_TEXTURE_WRAP_S;
		parameter_name = "wrap_s";
		strings = wrap_modes;
	} else if (parse_str(line, "wrap_t ", &line)) {
		parameter = GL_TEXTURE_WRAP_T;
		parameter_name = "wrap_t";
		strings = wrap_modes;
	} else if (parse_str(line, "wrap_r ", &line)) {
		parameter = GL_TEXTURE_WRAP_R;
		parameter_name = "wrap_r";
		strings = wrap_modes;
	} else if (parse_str(line, "lod_bias ", &line)) {
#ifdef PIGLIT_USE_OPENGL
		glTexParameterf(target, GL_TEXTURE_LOD_BIAS,
				strtod(line, NULL));
		return;
#else
		printf("lod_bias feature is only available in desktop GL\n");
		piglit_report_result(PIGLIT_SKIP);
#endif
	} else if (parse_str(line, "max_level ", &line)) {
		glTexParameteri(target, GL_TEXTURE_MAX_LEVEL,
				strtol(line, NULL, 10));
		return;
	} else if (parse_str(line, "base_level ", &line)) {
		glTexParameteri(target, GL_TEXTURE_BASE_LEVEL,
				strtol(line, NULL, 10));
		return;
	} else if (parse_str(line, "border_color ", &line)) {
		float bc[4];
		int count;
		count = sscanf(line, "%f %f %f %f", &bc[0], &bc[1], &bc[2], &bc[3]);
		if (count != 4) {
			fprintf(stderr, "Could not parse border_color texture parameter.\n");
			piglit_report_result(PIGLIT_FAIL);
		}
		glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, bc);
		return;
	} else if (parse_str(line, "swizzle_r ", &line)) {
		parameter = GL_TEXTURE_SWIZZLE_R;
		parameter_name = "swizzle_r";
		strings = swizzle_modes;
	} else {
		fprintf(stderr, "unknown texture parameter in `%s'\n", line);
		piglit_report_result(PIGLIT_FAIL);
	}

	REQUIRE(parse_enum_tab(strings, line, &value, &line),
		"Bad %s at: %s\n", parameter_name, line);

	glTexParameteri(target, parameter, value);
}

static void
setup_ubos(void)
{
	int i;

	if (!piglit_is_extension_supported("GL_ARB_uniform_buffer_object") &&
	    piglit_get_gl_version() < 31) {
		return;
	}

	if (prog == 0) {
		/* probably running an ARB_vertex/fragment_program test */
		return;
	}

	glGetProgramiv(prog, GL_ACTIVE_UNIFORM_BLOCKS, &num_uniform_blocks);
	if (num_uniform_blocks == 0)
		return;

	uniform_block_bos = calloc(num_uniform_blocks, sizeof(GLuint));
	glGenBuffers(num_uniform_blocks, uniform_block_bos);

	int max_ubos;
	glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &max_ubos);
	uniform_block_indexes = calloc(max_ubos, sizeof(int));

	for (i = 0; i < num_uniform_blocks; i++) {
		GLint size;
		GLint binding;

		glGetActiveUniformBlockiv(prog, i, GL_UNIFORM_BLOCK_DATA_SIZE,
					  &size);

		glBindBuffer(GL_UNIFORM_BUFFER, uniform_block_bos[i]);
		glBufferData(GL_UNIFORM_BUFFER, size, NULL, GL_STATIC_DRAW);

		if (!force_no_names) {
			glUniformBlockBinding(prog, i, i);
			uniform_block_indexes[i] = i;
			binding = i;
		} else {
			glGetActiveUniformBlockiv(prog, i, GL_UNIFORM_BLOCK_BINDING,
                                                  &binding);
			uniform_block_indexes[binding] = i;
		}

		glBindBufferBase(GL_UNIFORM_BUFFER, binding, uniform_block_bos[i]);
	}
}

static void
teardown_fbos(void)
{
	if (draw_fbo != 0 &&
	    draw_fbo != piglit_winsys_fbo)
		glDeleteFramebuffers(1, &draw_fbo);

	if (read_fbo != 0 &&
	    read_fbo != piglit_winsys_fbo &&
	    read_fbo != draw_fbo)
		glDeleteFramebuffers(1, &read_fbo);

	draw_fbo = 0;
	read_fbo = 0;
}

static void
teardown_ubos(void)
{
	if (num_uniform_blocks == 0) {
		return;
	}

	glDeleteBuffers(num_uniform_blocks, uniform_block_bos);
	free(uniform_block_bos);
	uniform_block_bos = NULL;
	free(uniform_block_indexes);
	uniform_block_indexes = NULL;
	num_uniform_blocks = 0;
}

static void
teardown_atomics(void)
{
	for (unsigned i = 0; i < ARRAY_SIZE(atomics_bos); ++i) {
		if (atomics_bos[i])
			glDeleteBuffers(1, &atomics_bos[i]);
	}
}

static void
teardown_shader_include_paths(void)
{
	for (unsigned i = 0; i < num_shader_include_paths; i++)
		free(shader_include_path[i]);

	for (unsigned i = 0; i < num_shader_includes; i++) {
		glDeleteNamedStringARB(-1, shader_include_names[i]);
		free(shader_include_names[i]);
	}

	num_shader_includes = 0;
	num_shader_include_paths = 0;
	free(shader_include_path);
	shader_include_path = NULL;
}

static void
teardown_xfb(void)
{
	glDeleteBuffers(MAX_XFB_BUFFERS, xfb);

	glDeleteQueries(1, &queries[query_index(GL_PRIMITIVES_GENERATED)]);
	glDeleteQueries(1, &queries[query_index(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN)]);
	queries[query_index(GL_PRIMITIVES_GENERATED)] = 0;
	queries[query_index(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN)] = 0;
}

static enum piglit_result
program_must_be_in_use(void)
{
	if (!link_ok) {
		fprintf(stderr, "Failed to link:\n%s\n", prog_err_info);
		return PIGLIT_FAIL;
	} else if (!prog_in_use) {
		fprintf(stderr, "Failed to use program: %s\n", prog_err_info);
		return PIGLIT_FAIL;
	}
	return PIGLIT_PASS;
}

static void
bind_vao_if_supported()
{
	if (vao == 0 && piglit_is_core_profile) {
		glGenVertexArrays(1, &vao);
		glBindVertexArray(vao);
	}
}

static enum piglit_result
draw_arrays_common(int first, size_t count)
{
	enum piglit_result result = program_must_be_in_use();
	if (first < 0) {
		printf("draw arrays 'first' must be >= 0\n");
		piglit_report_result(PIGLIT_FAIL);
	} else if (vbo_present &&
			(size_t) first >= num_vbo_rows) {
		printf("draw arrays 'first' must be < %lu\n",
			(unsigned long) num_vbo_rows);
		piglit_report_result(PIGLIT_FAIL);
	}
	if (count <= 0) {
		printf("draw arrays 'count' must be > 0\n");
		piglit_report_result(PIGLIT_FAIL);
	} else if (vbo_present &&
			count > num_vbo_rows - (size_t) first) {
		printf("draw arrays cannot draw beyond %lu\n",
			(unsigned long) num_vbo_rows);
		piglit_report_result(PIGLIT_FAIL);
	}
	bind_vao_if_supported();
	return result;
}

static bool
probe_atomic_counter(unsigned buffer_num, GLint counter_num, const char *op,
		     uint32_t value, bool uses_layout_qualifiers)
{
        uint32_t *p;
	uint32_t observed;
	enum comparison cmp;
	bool result;

	REQUIRE(parse_comparison_op(op, &cmp, NULL),
		"Invalid comparison operation at: %s\n", op);

	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomics_bos[buffer_num]);
	p = glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER,
			     uses_layout_qualifiers ? counter_num : counter_num * sizeof(uint32_t),
			     sizeof(uint32_t), GL_MAP_READ_BIT);

        if (!p) {
                printf("Couldn't map atomic counter to verify expected value.\n");
                return false;
        }

	observed = *p;
	result = compare_uint(value, observed, cmp);

	if (!result) {
		if (uses_layout_qualifiers)
			printf("Atomic counter (binding = %d, offset = %d) test failed: "
			       "Reference %s Observed\n",
			       buffer_num, counter_num, comparison_string(cmp));
		else
			printf("Atomic counter %d test failed: Reference %s Observed\n",
			       counter_num, comparison_string(cmp));
		printf("  Reference: %u\n", value);
		printf("  Observed:  %u\n", observed);
		glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
		return false;
	}

	glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
	return true;
}

static bool
probe_ssbo_uint(GLint ssbo_index, GLint ssbo_offset, const char *op, uint32_t value)
{
	uint32_t *p;
	uint32_t observed;
	enum comparison cmp;
	bool result;

	REQUIRE(parse_comparison_op(op, &cmp, NULL),
		"Invalid comparison operation at: %s\n", op);

	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo[ssbo_index]);
	p = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, ssbo_offset,
			     sizeof(uint32_t), GL_MAP_READ_BIT);

	if (!p) {
		printf("Couldn't map ssbo to verify expected value.\n");
		return false;
	}

	observed = *p;
	result = compare_uint(value, observed, cmp);

	if (!result) {
		printf("SSBO %d test failed: Reference %s Observed\n",
		       ssbo_offset, comparison_string(cmp));
		printf("  Reference: %u\n", value);
		printf("  Observed:  %u\n", observed);
		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
		return false;
	}

	glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
	return true;
}

static bool
probe_ssbo_int(GLint ssbo_index, GLint ssbo_offset, const char *op, int32_t value)
{
	int32_t *p;
	int32_t observed;
	enum comparison cmp;
	bool result;

	REQUIRE(parse_comparison_op(op, &cmp, NULL),
		"Invalid comparison operation at: %s\n", op);

	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo[ssbo_index]);
	p = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, ssbo_offset,
			     sizeof(uint32_t), GL_MAP_READ_BIT);

	if (!p) {
		printf("Couldn't map ssbo to verify expected value.\n");
		return false;
	}

	observed = *p;
	result = compare_int(value, observed, cmp);

	if (!result) {
		printf("SSBO %d test failed: Reference %s Observed\n",
		       ssbo_offset, comparison_string(cmp));
		printf("  Reference: %d\n", value);
		printf("  Observed:  %d\n", observed);
		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
		return false;
	}

	glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
	return true;
}

GLenum piglit_xfb_primitive_mode(GLenum draw_arrays_mode)
{
	switch (draw_arrays_mode) {
	case GL_POINTS:
		return GL_POINTS;
	case GL_LINES:
	case GL_LINE_LOOP:
	case GL_LINE_STRIP:
	case GL_LINES_ADJACENCY:
	case GL_LINE_STRIP_ADJACENCY:
		return GL_LINES;
	case GL_TRIANGLES:
	case GL_TRIANGLE_STRIP:
	case GL_TRIANGLE_FAN:
	case GL_TRIANGLES_ADJACENCY:
	case GL_TRIANGLE_STRIP_ADJACENCY:
		return GL_TRIANGLES;
	}

	printf("glDrawArrays mode %s not supported for a transform feedback operation\n",
	       piglit_get_gl_enum_name(draw_arrays_mode));
	piglit_report_result(PIGLIT_FAIL);
}

static void
begin_query(GLenum query_type)
{
	unsigned idx = query_index(query_type);
	if (queries[idx] == 0)
		glGenQueries(1, &queries[idx]);
	glBeginQuery(query_type, queries[idx]);
}

static void
piglit_xfb_draw_arrays(GLenum mode, int first, size_t count)
{
	GLenum primitive_mode = piglit_xfb_primitive_mode(mode);

	begin_query(GL_PRIMITIVES_GENERATED);
	begin_query(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);

	glEnable(GL_RASTERIZER_DISCARD);

	/* We don't need to call glBindBufferBase here, it is done on
	 * the "xfb buffer object" command
	 */

	glBeginTransformFeedback(primitive_mode);
	glDrawArrays(mode, first, count);
	glEndTransformFeedback();

	glDisable(GL_RASTERIZER_DISCARD);

	glFlush();

	glEndQuery(GL_PRIMITIVES_GENERATED);
	glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
}

static bool
probe_xfb_float(GLuint buf, unsigned n, float expected)
{
	float *p;
	bool result;
	glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
	p = glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, n * sizeof(float),
			     sizeof(float), GL_MAP_READ_BIT);
	if (!p) {
		printf("Couldn't map xfb buffer %u\n", buf);
		return false;
	}
	result = fabsf(expected - *p) < piglit_tolerance[0];
	if (!result) {
		printf("xfb buffer %i[%i]: %f, Expected: %f\n",
		       buf, n, *p, expected);
	}

	glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
	return result;
}

static bool
probe_xfb_double(GLuint buf, unsigned n, double expected)
{
	double *p;
	bool result;

	glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, buf);
	p = glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, n * sizeof(double),
			     sizeof(double), GL_MAP_READ_BIT);
	if (!p) {
		printf("Couldn't map xfb buffer %u\n", buf);
		return false;
	}
	result = fabs(expected - *p) < piglit_tolerance[0];
	if (!result) {
		printf("xfb buffer %i[%i]: %lf, Expected: %lf\n",
		       buf, n, *p, expected);
	}

	glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
	return result;
}

enum piglit_result
piglit_display(void)
{
	const char *line, *next_line, *rest;
	unsigned line_num;
	enum piglit_result full_result = PIGLIT_PASS;
	GLbitfield clear_bits = 0;
	bool link_error_expected = false;
	unsigned list = 0;
	struct block_info block_data = {0, -1, -1, -1, -1};

	if (test_start == NULL)
		return PIGLIT_PASS;

	next_line = test_start;
	line_num = test_start_line_num;
	while (next_line[0] != '\0') {
		float c[32];
		double d[4];
		int x, y, z, w, h, l, tex, level;
		unsigned ux, uy, uz;
		char s[300]; // 300 for safety
		enum piglit_result result = PIGLIT_PASS;

		parse_whitespace(next_line, &line);

		next_line = strchrnul(next_line, '\n');

		/* Duplicate the line to make it null terminated */
		line = strndup(line, next_line - line);

		/* If strchrnul found a newline, then skip it */
		if (next_line[0] != '\0')
			next_line++;

		if (line[0] == '\0') {
		} else if (sscanf(line, "active shader program %s", s) == 1) {
			switch (get_shader_from_string(s, &x)) {
			case GL_VERTEX_SHADER:
				glActiveShaderProgram(pipeline, sso_vertex_prog);
			break;
			case GL_TESS_CONTROL_SHADER:
				glActiveShaderProgram(pipeline, sso_tess_control_prog);
			break;
			case GL_TESS_EVALUATION_SHADER:
				glActiveShaderProgram(pipeline, sso_tess_eval_prog);
			break;
			case GL_GEOMETRY_SHADER:
				glActiveShaderProgram(pipeline, sso_geometry_prog);
			break;
			case GL_FRAGMENT_SHADER:
				glActiveShaderProgram(pipeline, sso_fragment_prog);
			break;
			case GL_COMPUTE_SHADER:
				glActiveShaderProgram(pipeline, sso_compute_prog);
			break;
			}
		} else if (sscanf(line, "atomic counter buffer %u %u", &x, &y) == 2) {
			GLuint *atomics_buf = calloc(y, sizeof(GLuint));
			glGenBuffers(1, &atomics_bos[x]);
			glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, x, atomics_bos[x]);
			glBufferData(GL_ATOMIC_COUNTER_BUFFER,
				     sizeof(GLuint) * y, atomics_buf,
				     GL_STATIC_DRAW);
			free(atomics_buf);
		} else if (sscanf(line, "atomic counters %d", &x) == 1) {
			GLuint *atomics_buf = calloc(x, sizeof(GLuint));
			glGenBuffers(1, &atomics_bos[0]);
			glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomics_bos[0]);
			glBufferData(GL_ATOMIC_COUNTER_BUFFER,
				     sizeof(GLuint) * x,
				     atomics_buf, GL_STATIC_DRAW);
			free(atomics_buf);
		} else if (sscanf(line, "atomic counter %u %u %u", &x, &y, &z) == 3) {
			glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, x, atomics_bos[x]);
			glBufferSubData(GL_ATOMIC_COUNTER_BUFFER,
					sizeof(GLuint) * y, sizeof(GLuint),
					&z);
		} else if (parse_str(line, "clear color ", &rest)) {
			parse_floats(rest, c, 4, NULL);
			glClearColor(c[0], c[1], c[2], c[3]);
			clear_bits |= GL_COLOR_BUFFER_BIT;
		} else if (parse_str(line, "clear depth ", &rest)) {
			parse_floats(rest, c, 1, NULL);
			glClearDepth(c[0]);
			clear_bits |= GL_DEPTH_BUFFER_BIT;
		} else if (parse_str(line, "clear", NULL)) {
			glClear(clear_bits);
		} else if (sscanf(line,
				  "clip plane %d %lf %lf %lf %lf",
				  &x, &d[0], &d[1], &d[2], &d[3]) == 5) {
			if (x < 0 || x >= gl_max_clip_planes) {
				printf("clip plane id %d out of range\n", x);
				piglit_report_result(PIGLIT_FAIL);
			}
			glClipPlane(GL_CLIP_PLANE0 + x, d);
#ifdef PIGLIT_USE_OPENGL
		} else if (parse_str(line, "color ", &rest)) {
			parse_floats(rest, c, 4, NULL);
			assert(!piglit_is_core_profile);
			glColor4fv(c);
#endif
		} else if (sscanf(line,
				  "compute %d %d %d",
				  &x, &y, &z) == 3) {
			result = program_must_be_in_use();
			glMemoryBarrier(GL_ALL_BARRIER_BITS);
			glDispatchCompute(x, y, z);
			glMemoryBarrier(GL_ALL_BARRIER_BITS);
		} else if (sscanf(line,
				  "compute group size %d %d %d %d %d %d",
				  &x, &y, &z, &w, &h, &l) == 6) {
			result = program_must_be_in_use();
			glMemoryBarrier(GL_ALL_BARRIER_BITS);
			glDispatchComputeGroupSizeARB(x, y, z, w, h, l);
			glMemoryBarrier(GL_ALL_BARRIER_BITS);
		} else if (parse_str(line, "draw rect tex ", &rest)) {
			result = program_must_be_in_use();
			program_subroutine_uniforms();
			parse_floats(rest, c, 8, NULL);
			piglit_draw_rect_tex(c[0], c[1], c[2], c[3],
					     c[4], c[5], c[6], c[7]);
		} else if (parse_str(line, "draw rect ortho patch ", &rest)) {
			result = program_must_be_in_use();
			program_subroutine_uniforms();
			parse_floats(rest, c, 4, NULL);

			piglit_draw_rect_custom(-1.0 + 2.0 * (c[0] / piglit_width),
						-1.0 + 2.0 * (c[1] / piglit_height),
						2.0 * (c[2] / piglit_width),
						2.0 * (c[3] / piglit_height), true, 1);
		} else if (parse_str(line, "draw rect ortho ", &rest)) {
			result = program_must_be_in_use();
			program_subroutine_uniforms();
			parse_floats(rest, c, 4, NULL);

			piglit_draw_rect(-1.0 + 2.0 * (c[0] / piglit_width),
					 -1.0 + 2.0 * (c[1] / piglit_height),
					 2.0 * (c[2] / piglit_width),
					 2.0 * (c[3] / piglit_height));
		} else if (parse_str(line, "draw rect patch ", &rest)) {
			result = program_must_be_in_use();
			parse_floats(rest, c, 4, NULL);
			piglit_draw_rect_custom(c[0], c[1], c[2], c[3], true, 1);
		} else if (parse_str(line, "draw rect ", &rest)) {
			result = program_must_be_in_use();
			program_subroutine_uniforms();
			parse_floats(rest, c, 4, NULL);
			piglit_draw_rect(c[0], c[1], c[2], c[3]);
		} else if (parse_str(line, "draw instanced rect ortho patch ", &rest)) {
			int instance_count;

			result = program_must_be_in_use();
			sscanf(rest, "%d %f %f %f %f",
			       &instance_count,
			       c + 0, c + 1, c + 2, c + 3);
			piglit_draw_rect_custom(-1.0 + 2.0 * (c[0] / piglit_width),
						-1.0 + 2.0 * (c[1] / piglit_height),
						2.0 * (c[2] / piglit_width),
						2.0 * (c[3] / piglit_height), true,
						instance_count);
		} else if (parse_str(line, "draw instanced rect ortho ", &rest)) {
			int instance_count;

			result = program_must_be_in_use();
			sscanf(rest, "%d %f %f %f %f",
			       &instance_count,
			       c + 0, c + 1, c + 2, c + 3);
			piglit_draw_rect_custom(-1.0 + 2.0 * (c[0] / piglit_width),
						-1.0 + 2.0 * (c[1] / piglit_height),
						2.0 * (c[2] / piglit_width),
						2.0 * (c[3] / piglit_height), false,
						instance_count);
		} else if (parse_str(line, "draw instanced rect ", &rest)) {
			int primcount;

			result = program_must_be_in_use();
			sscanf(rest, "%d %f %f %f %f",
			       &primcount,
			       c + 0, c + 1, c + 2, c + 3);
			draw_instanced_rect(primcount, c[0], c[1], c[2], c[3]);
		} else if (sscanf(line, "draw arrays instanced %31s %d %d %d", s, &x, &y, &z) == 4) {
			GLenum mode = decode_drawing_mode(s);
			int first = x;
			size_t count = (size_t) y;
			size_t primcount = (size_t) z;
			draw_arrays_common(first, count);
			glDrawArraysInstanced(mode, first, count, primcount);
		} else if (sscanf(line, "draw arrays %31s %d %d", s, &x, &y) == 3) {
			GLenum mode = decode_drawing_mode(s);
			int first = x;
			size_t count = (size_t) y;
			result = draw_arrays_common(first, count);
			glDrawArrays(mode, first, count);
		} else if (parse_str(line, "disable ", &rest)) {
			do_enable_disable(rest, false);
		} else if (parse_str(line, "enable ", &rest)) {
			do_enable_disable(rest, true);
		} else if (sscanf(line, "depthfunc %31s", s) == 1) {
			glDepthFunc(piglit_get_gl_enum_from_name(s));
		} else if (parse_str(line, "fb ", &rest)) {
			const GLenum target =
				parse_str(rest, "draw ", &rest) ? GL_DRAW_FRAMEBUFFER :
				parse_str(rest, "read ", &rest) ? GL_READ_FRAMEBUFFER :
				GL_FRAMEBUFFER;
			GLuint fbo = 0;

			if (parse_str(rest, "tex 2d ", &rest)) {
				GLenum attachments[32];
				unsigned num_attachments = 0;

				glGenFramebuffers(1, &fbo);
				glBindFramebuffer(target, fbo);

				while (parse_int(rest, &tex, &rest)) {
					attachments[num_attachments] =
						GL_COLOR_ATTACHMENT0 + num_attachments;
					glFramebufferTexture2D(
						target, attachments[num_attachments],
						GL_TEXTURE_2D,
						get_texture_binding(tex)->obj, 0);

					if (!piglit_check_gl_error(GL_NO_ERROR)) {
						fprintf(stderr,
							"glFramebufferTexture2D error\n");
						piglit_report_result(PIGLIT_FAIL);
					}

					num_attachments++;
				}

				if (target != GL_READ_FRAMEBUFFER)
					glDrawBuffers(num_attachments, attachments);

				w = get_texture_binding(tex)->width;
				h = get_texture_binding(tex)->height;

			} else if (parse_str(rest, "tex slice ", &rest)) {
				GLenum tex_target;

				REQUIRE(parse_tex_target(rest, &tex_target, &rest) &&
					parse_int(rest, &tex, &rest) &&
					parse_int(rest, &l, &rest) &&
					parse_int(rest, &z, &rest),
					"Framebuffer binding command not "
					"understood at: %s\n", rest);

				const GLuint tex_obj = get_texture_binding(tex)->obj;

				glGenFramebuffers(1, &fbo);
				glBindFramebuffer(target, fbo);

				if (tex_target == GL_TEXTURE_1D) {
					REQUIRE(z == 0,
						"Invalid layer index provided "
						"in command: %s\n", line);
					glFramebufferTexture1D(
						target, GL_COLOR_ATTACHMENT0,
						tex_target, tex_obj, l);

				} else if (tex_target == GL_TEXTURE_2D ||
					   tex_target == GL_TEXTURE_RECTANGLE ||
					   tex_target == GL_TEXTURE_2D_MULTISAMPLE) {
					REQUIRE(z == 0,
						"Invalid layer index provided "
						"in command: %s\n", line);
					glFramebufferTexture2D(
						target, GL_COLOR_ATTACHMENT0,
						tex_target, tex_obj, l);

				} else if (tex_target == GL_TEXTURE_CUBE_MAP) {
					static const GLenum cubemap_targets[] = {
						GL_TEXTURE_CUBE_MAP_POSITIVE_X,
						GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
						GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
						GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
						GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
						GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
					};
					REQUIRE(z < ARRAY_SIZE(cubemap_targets),
						"Invalid layer index provided "
						"in command: %s\n", line);
					tex_target = cubemap_targets[z];

					glFramebufferTexture2D(
						target, GL_COLOR_ATTACHMENT0,
						tex_target, tex_obj, l);

				} else {
					glFramebufferTextureLayer(
						target, GL_COLOR_ATTACHMENT0,
						tex_obj, l, z);
				}

				if (!piglit_check_gl_error(GL_NO_ERROR)) {
					fprintf(stderr, "Error binding texture "
						"attachment for command: %s\n",
						line);
					piglit_report_result(PIGLIT_FAIL);
				}

				w = MAX2(1, get_texture_binding(tex)->width >> l);
				h = MAX2(1, get_texture_binding(tex)->height >> l);

			} else if (sscanf(rest, "tex layered %d", &tex) == 1) {
				glGenFramebuffers(1, &fbo);
				glBindFramebuffer(target, fbo);

				glFramebufferTexture(
					target, GL_COLOR_ATTACHMENT0,
					get_texture_binding(tex)->obj, 0);
				if (!piglit_check_gl_error(GL_NO_ERROR)) {
					fprintf(stderr,
						"glFramebufferTexture error\n");
					piglit_report_result(PIGLIT_FAIL);
				}

				w = get_texture_binding(tex)->width;
				h = get_texture_binding(tex)->height;

			} else if (parse_str(rest, "ms ", &rest)) {
				GLuint rb;
				GLenum format;
				int samples;

				REQUIRE(parse_enum_gl(rest, &format, &rest) &&
					parse_int(rest, &w, &rest) &&
					parse_int(rest, &h, &rest) &&
					parse_int(rest, &samples, &rest),
					"Framebuffer binding command not "
					"understood at: %s\n", rest);

				glGenFramebuffers(1, &fbo);
				glBindFramebuffer(target, fbo);

				glGenRenderbuffers(1, &rb);
				glBindRenderbuffer(GL_RENDERBUFFER, rb);

				glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples,
								 format, w, h);

				glFramebufferRenderbuffer(target,
							  GL_COLOR_ATTACHMENT0,
							  GL_RENDERBUFFER, rb);

				if (!piglit_check_gl_error(GL_NO_ERROR)) {
					fprintf(stderr, "glFramebufferRenderbuffer error\n");
					piglit_report_result(PIGLIT_FAIL);
				}

			} else if (parse_str(rest, "winsys", &rest)) {
				fbo = piglit_winsys_fbo;
				glBindFramebuffer(target, fbo);
				if (!piglit_check_gl_error(GL_NO_ERROR)) {
					fprintf(stderr, "glBindFramebuffer error\n");
					piglit_report_result(PIGLIT_FAIL);
				}

				w = piglit_width;
				h = piglit_height;

			} else {
				fprintf(stderr, "Unknown fb bind subcommand "
					"\"%s\"\n", rest);
				piglit_report_result(PIGLIT_FAIL);
			}

			const GLenum status = glCheckFramebufferStatus(target);
			if (status != GL_FRAMEBUFFER_COMPLETE) {
				fprintf(stderr, "incomplete fbo (status 0x%x)\n",
					status);
				piglit_report_result(PIGLIT_FAIL);
			}

			if (target != GL_READ_FRAMEBUFFER) {
				render_width = w;
				render_height = h;

				/* Delete the previous draw FB in case
				 * it's no longer reachable.
				 */
				if (draw_fbo != 0 &&
				    draw_fbo != piglit_winsys_fbo &&
				    draw_fbo != (target == GL_DRAW_FRAMEBUFFER ?
						 read_fbo : 0))
					glDeleteFramebuffers(1, &draw_fbo);

				draw_fbo = fbo;
			}

			if (target != GL_DRAW_FRAMEBUFFER) {
				read_width = w;
				read_height = h;

				/* Delete the previous read FB in case
				 * it's no longer reachable.
				 */
				if (read_fbo != 0 &&
				    read_fbo != piglit_winsys_fbo &&
				    read_fbo != (target == GL_READ_FRAMEBUFFER ?
						 draw_fbo : 0))
					glDeleteFramebuffers(1, &read_fbo);

				read_fbo = fbo;
			}

		} else if (parse_str(line, "blit ", &rest)) {
			static const struct string_to_enum buffers[] = {
				{ "color", GL_COLOR_BUFFER_BIT },
				{ "depth", GL_DEPTH_BUFFER_BIT },
				{ "stencil", GL_STENCIL_BUFFER_BIT },
				{ NULL }
			};
			static const struct string_to_enum filters[] = {
				{ "linear", GL_LINEAR },
				{ "nearest", GL_NEAREST },
				{ NULL }
			};
			unsigned buffer, filter;

			REQUIRE(parse_enum_tab(buffers, rest, &buffer, &rest) &&
				parse_enum_tab(filters, rest, &filter, &rest),
				"FB blit command not understood at: %s\n",
				rest);

			glBlitFramebuffer(0, 0, read_width, read_height,
					  0, 0, render_width, render_height,
					  buffer, filter);

			if (!piglit_check_gl_error(GL_NO_ERROR)) {
				fprintf(stderr, "glBlitFramebuffer error\n");
				piglit_report_result(PIGLIT_FAIL);
			}

		} else if (parse_str(line, "frustum", &rest)) {
			parse_floats(rest, c, 6, NULL);
			piglit_frustum_projection(false, c[0], c[1], c[2],
						  c[3], c[4], c[5]);
		} else if (parse_str(line, "hint", &rest)) {
			do_hint(rest);
		} else if (sscanf(line,
				  "image texture %d %31s",
				  &tex, s) == 2) {
			const GLenum img_fmt = piglit_get_gl_enum_from_name(s);
			glBindImageTexture(tex, get_texture_binding(tex)->obj, 0,
					   GL_FALSE, 0, GL_READ_WRITE, img_fmt);
		} else if (sscanf(line, "memory barrier %s", s) == 1) {
			glMemoryBarrier(piglit_get_gl_memory_barrier_enum_from_name(s));
		} else if (parse_str(line, "blend barrier", NULL)) {
			glBlendBarrier();
		} else if (parse_str(line, "fbfetch barrier", NULL)) {
			glFramebufferFetchBarrierEXT();
		} else if (sscanf(line, "ortho %f %f %f %f",
				  c + 0, c + 1, c + 2, c + 3) == 4) {
			piglit_gen_ortho_projection(c[0], c[1], c[2], c[3],
						    -1, 1, GL_FALSE);
		} else if (parse_str(line, "ortho", NULL)) {
			piglit_ortho_projection(render_width, render_height,
						GL_FALSE);
		} else if (parse_str(line, "probe rgba ", &rest)) {
			parse_floats(rest, c, 6, NULL);
			if (!piglit_probe_pixel_rgba((int) c[0], (int) c[1],
						    & c[2])) {
				result = PIGLIT_FAIL;
			}
		} else if (parse_str(line, "probe depth ", &rest)) {
			parse_floats(rest, c, 3, NULL);
			if (!piglit_probe_pixel_depth((int) c[0], (int) c[1],
						      c[2])) {
				result = PIGLIT_FAIL;
			}
		} else if (sscanf(line,
				  "probe atomic counter buffer %u %u %s %u",
				  &ux, &uy, s, &uz) == 4) {
			if (!probe_atomic_counter(ux, uy, s, uz, true)) {
				result = PIGLIT_FAIL;
			}
		} else if (sscanf(line,
				  "probe atomic counter %u %s %u",
				  &ux, s, &uy) == 3) {
			if (!probe_atomic_counter(0, ux, s, uy, false)) {
				result = PIGLIT_FAIL;
			}
		} else if (sscanf(line, "probe ssbo uint %d %d %s 0x%x",
				  &x, &y, s, &z) == 4) {
			if (!probe_ssbo_uint(x, y, s, z))
				result = PIGLIT_FAIL;
		} else if (sscanf(line, "probe ssbo uint %d %d %s %d",
				  &x, &y, s, &z) == 4) {
			if (!probe_ssbo_uint(x, y, s, z))
				result = PIGLIT_FAIL;
		} else if (sscanf(line, "probe ssbo int %d %d %s %d",
				  &x, &y, s, &z) == 4) {
			if (!probe_ssbo_int(x, y, s, z))
				result = PIGLIT_FAIL;
		} else if (sscanf(line,
				  "relative probe rgba ( %f , %f ) "
				  "( %f , %f , %f , %f )",
				  c + 0, c + 1,
				  c + 2, c + 3, c + 4, c + 5) == 6) {
			x = c[0] * read_width;
			y = c[1] * read_height;
			if (x >= read_width)
				x = read_width - 1;
			if (y >= read_height)
				y = read_height - 1;

			if (!piglit_probe_pixel_rgba(x, y, &c[2])) {
				result = PIGLIT_FAIL;
			}
		} else if (parse_str(line, "probe rgb ", &rest)) {
			parse_floats(rest, c, 5, NULL);
			if (!piglit_probe_pixel_rgb((int) c[0], (int) c[1],
						    & c[2])) {
				result = PIGLIT_FAIL;
			}
		} else if (sscanf(line,
				  "relative probe rgb ( %f , %f ) "
				  "( %f , %f , %f )",
				  c + 0, c + 1,
				  c + 2, c + 3, c + 4) == 5) {
			x = c[0] * read_width;
			y = c[1] * read_height;
			if (x >= read_width)
				x = read_width - 1;
			if (y >= read_height)
				y = read_height - 1;

			if (!piglit_probe_pixel_rgb(x, y, &c[2])) {
				result = PIGLIT_FAIL;
			}
		} else if (sscanf(line, "probe rect rgba "
				  "( %d , %d , %d , %d ) "
				  "( %f , %f , %f , %f )",
				  &x, &y, &w, &h,
				  c + 0, c + 1, c + 2, c + 3) == 8) {
			if (!piglit_probe_rect_rgba(x, y, w, h, c)) {
				result = PIGLIT_FAIL;
			}
		} else if (sscanf(line, "relative probe rect rgb "
				  "( %f , %f , %f , %f ) "
				  "( %f , %f , %f )",
				  c + 0, c + 1, c + 2, c + 3,
				  c + 4, c + 5, c + 6) == 7) {
			x = c[0] * read_width;
			y = c[1] * read_height;
			w = c[2] * read_width;
			h = c[3] * read_height;

			if (!piglit_probe_rect_rgb(x, y, w, h, &c[4])) {
				result = PIGLIT_FAIL;
			}
		} else if (sscanf(line, "relative probe rect rgba "
				  "( %f , %f , %f , %f ) "
				  "( %f , %f , %f , %f )",
				  c + 0, c + 1, c + 2, c + 3,
				  c + 4, c + 5, c + 6, c + 7) == 8) {
			x = c[0] * read_width;
			y = c[1] * read_height;
			w = c[2] * read_width;
			h = c[3] * read_height;

			if (!piglit_probe_rect_rgba(x, y, w, h, &c[4])) {
				result = PIGLIT_FAIL;
			}
		} else if (sscanf(line, "relative probe rect rgba int "
				  "( %f , %f , %f , %f ) "
				  "( %d , %d , %d , %d )",
				  c + 0, c + 1, c + 2, c + 3,
				  &x, &y, &z, &w) == 8) {
			const int expected[] = { x, y, z, w };
			if (!piglit_probe_rect_rgba_int(c[0] * read_width,
							c[1] * read_height,
							c[2] * read_width,
							c[3] * read_height,
							expected))
				result = PIGLIT_FAIL;

		} else if (parse_str(line, "polygon mode ", &rest)) {
			GLenum face, mode;

			REQUIRE(parse_enum_gl(rest, &face, &rest) &&
				parse_enum_gl(rest, &mode, &rest),
				"Polygon mode command not understood at %s\n",
				rest);

			glPolygonMode(face, mode);

			if (!piglit_check_gl_error(GL_NO_ERROR)) {
				fprintf(stderr, "glPolygonMode error\n");
				piglit_report_result(PIGLIT_FAIL);
			}
		} else if (parse_str(line, "probe all rgba ", &rest)) {
			parse_floats(rest, c, 4, NULL);
			if (result != PIGLIT_FAIL &&
			    !piglit_probe_rect_rgba(0, 0, read_width,
						    read_height, c))
				result = PIGLIT_FAIL;
		} else if (parse_str(line, "probe warn all rgba ", &rest)) {
			parse_floats(rest, c, 4, NULL);
			if (result == PIGLIT_PASS &&
			    !piglit_probe_rect_rgba(0, 0, read_width,
						    read_height, c))
				result = PIGLIT_WARN;
		} else if (parse_str(line, "probe all rgb", &rest)) {
			parse_floats(rest, c, 3, NULL);
			if (result != PIGLIT_FAIL &&
			    !piglit_probe_rect_rgb(0, 0, read_width,
						   read_height, c))
				result = PIGLIT_FAIL;
		} else if (parse_str(line, "tolerance", &rest)) {
			parse_floats(rest, piglit_tolerance, 4, NULL);
		} else if (parse_str(line, "shade model smooth", NULL)) {
			glShadeModel(GL_SMOOTH);
		} else if (parse_str(line, "shade model flat", NULL)) {
			glShadeModel(GL_FLAT);
		} else if (sscanf(line, "ssbo %d %d", &x, &y) == 2) {
			GLuint *ssbo_init = calloc(y, 1);
			glGenBuffers(1, &ssbo[x]);
			glBindBufferBase(GL_SHADER_STORAGE_BUFFER, x, ssbo[x]);
			glBufferData(GL_SHADER_STORAGE_BUFFER, y,
				     ssbo_init, GL_DYNAMIC_DRAW);
			free(ssbo_init);
		}
		else if (sscanf(line, "xfb buffer object %u %u", &ux, &uy) == 2) {
			GLuint *xfb_init = calloc(uy, 1);
			if (ux < 0 || ux >= MAX_XFB_BUFFERS) {
				printf("xfb buffer id %d out of range\n", ux);
				piglit_report_result(PIGLIT_FAIL);
			}
			glGenBuffers(1, &xfb[ux]);
			glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, ux, xfb[ux]);
			glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, uy,
				     xfb_init, GL_STREAM_READ);
			free(xfb_init);
		} else if (sscanf(line, "xfb draw arrays %31s %d %d", s, &x, &y) == 3) {
			GLenum mode = decode_drawing_mode(s);
			int first = x;
			size_t count = (size_t) y;
			result = draw_arrays_common(first, count);
			piglit_xfb_draw_arrays(mode, first, count);
		} else if (sscanf(line, "probe xfb buffer float %u %u %f",
				  &ux, &uy, &c[0]) == 3) {
			if (!probe_xfb_float(xfb[ux], uy, c[0]))
				result = PIGLIT_FAIL;
		} else if (sscanf(line, "probe xfb buffer double %u %u %lf",
				  &ux, &uy, &d[0]) == 3) {
			if (!probe_xfb_double(xfb[ux], uy, d[0]))
				result = PIGLIT_FAIL;
		} else if (sscanf(line, "ssbo %d subdata float %d %f", &x, &y, &c[0]) == 3) {
			glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[x]);
			glBufferSubData(GL_SHADER_STORAGE_BUFFER, y, 4, &c[0]);
		} else if (sscanf(line, "ssbo %d subdata int %d %s", &x, &y, s) == 3) {
			parse_ints(s, &z, 1, NULL);
			glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[x]);
			glBufferSubData(GL_SHADER_STORAGE_BUFFER, y, 4, &z);
		} else if (sscanf(line, "texture rgbw %d ( %d", &tex, &w) == 2) {
			GLenum int_fmt = GL_RGBA;
			int num_scanned =
				sscanf(line,
				       "texture rgbw %d ( %d , %d ) %31s",
				       &tex, &w, &h, s);
			if (num_scanned < 3) {
				fprintf(stderr,
					"invalid texture rgbw command!\n");
				piglit_report_result(PIGLIT_FAIL);
			}

			if (num_scanned >= 4) {
				int_fmt = piglit_get_gl_enum_from_name(s);
			}

			glActiveTexture(GL_TEXTURE0 + tex);
			int handle = piglit_rgbw_texture(
				int_fmt, w, h, GL_FALSE, GL_FALSE,
				(piglit_is_gles() ? GL_UNSIGNED_BYTE :
				 GL_UNSIGNED_NORMALIZED));
			set_texture_binding(tex, handle, w, h, 1);

			if (!piglit_is_core_profile &&
			    !(piglit_is_gles() && piglit_get_gl_version() >= 20))
				glEnable(GL_TEXTURE_2D);

		} else if (sscanf(line, "resident texture %d", &tex) == 1) {
			GLuint64 handle;

			glBindTexture(GL_TEXTURE_2D, 0);

			handle = glGetTextureHandleARB(get_texture_binding(tex)->obj);
			glMakeTextureHandleResidentARB(handle);

			set_resident_handle(tex, handle, true);

			if (!piglit_check_gl_error(GL_NO_ERROR)) {
				fprintf(stderr,
					"glMakeTextureHandleResidentARB error\n");
				piglit_report_result(PIGLIT_FAIL);
			}
		} else if (sscanf(line, "resident image texture %d %31s",
				  &tex, s) == 2) {
			const GLenum img_fmt = piglit_get_gl_enum_from_name(s);
			GLuint64 handle;

			glBindTexture(GL_TEXTURE_2D, 0);

			handle = glGetImageHandleARB(get_texture_binding(tex)->obj,
						     0, GL_FALSE, 0, img_fmt);
			glMakeImageHandleResidentARB(handle, GL_READ_WRITE);

			set_resident_handle(tex, handle, false);

			if (!piglit_check_gl_error(GL_NO_ERROR)) {
				fprintf(stderr,
					"glMakeImageHandleResidentARB error\n");
				piglit_report_result(PIGLIT_FAIL);
			}
		} else if (parse_str(line, "texture integer ", &rest)) {
			GLenum int_fmt;
			int b, a;
			int num_scanned =
				sscanf(rest, "%d ( %d , %d ) ( %d, %d ) %31s",
				       &tex, &w, &h, &b, &a, s);
			if (num_scanned < 6) {
				fprintf(stderr,
					"invalid texture integer command!\n");
				piglit_report_result(PIGLIT_FAIL);
			}

			int_fmt = piglit_get_gl_enum_from_name(s);

			glActiveTexture(GL_TEXTURE0 + tex);
			const GLuint handle =
				piglit_integer_texture(int_fmt, w, h, b, a);
			set_texture_binding(tex, handle, w, h, 1);

		} else if (sscanf(line, "texture miptree %d", &tex) == 1) {
			glActiveTexture(GL_TEXTURE0 + tex);
			const GLuint handle = piglit_miptree_texture();
			set_texture_binding(tex, handle, 8, 8, 1);

			if (!piglit_is_core_profile &&
			    !(piglit_is_gles() && piglit_get_gl_version() >= 20))
				glEnable(GL_TEXTURE_2D);
		} else if (sscanf(line,
				  "texture checkerboard %d %d ( %d , %d ) "
				  "( %f , %f , %f , %f ) "
				  "( %f , %f , %f , %f )",
				  &tex, &level, &w, &h,
				  c + 0, c + 1, c + 2, c + 3,
				  c + 4, c + 5, c + 6, c + 7) == 12) {
			glActiveTexture(GL_TEXTURE0 + tex);
			const GLuint handle = piglit_checkerboard_texture(
				0, level, w, h, w / 2, h / 2, c + 0, c + 4);
			set_texture_binding(tex, handle, w, h, 1);

			if (!piglit_is_core_profile &&
			    !(piglit_is_gles() && piglit_get_gl_version() >= 20))
				glEnable(GL_TEXTURE_2D);
		} else if (sscanf(line,
				  "texture quads %d %d ( %d , %d ) ( %d , %d ) "
				  "( %f , %f , %f , %f ) "
				  "( %f , %f , %f , %f ) "
				  "( %f , %f , %f , %f ) "
				  "( %f , %f , %f , %f )",
				  &tex, &level, &w, &h, &x, &y,
				  c + 0, c + 1, c + 2, c + 3,
				  c + 4, c + 5, c + 6, c + 7,
				  c + 8, c + 9, c + 10, c + 11,
				  c + 12, c + 13, c + 14, c + 15) == 22) {
			glActiveTexture(GL_TEXTURE0 + tex);
			const GLuint handle = piglit_quads_texture(
				0, level, w, h, x, y, c + 0, c + 4, c + 8, c + 12);
			set_texture_binding(tex, handle, w, h, 1);

			if (!piglit_is_core_profile &&
			    !(piglit_is_gles() && piglit_get_gl_version() >= 20))
				glEnable(GL_TEXTURE_2D);
		} else if (sscanf(line,
				  "texture junk 2DArray %d ( %d , %d , %d )",
				  &tex, &w, &h, &l) == 4) {
			GLuint texobj;
			glActiveTexture(GL_TEXTURE0 + tex);
			glGenTextures(1, &texobj);
			glBindTexture(GL_TEXTURE_2D_ARRAY, texobj);
			glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA,
				     w, h, l, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
			set_texture_binding(tex, texobj, w, h, l);

		} else if (parse_str(line, "texture storage ", &rest)) {
			GLenum target, format;
			GLuint tex_obj;
			int d = h = w = 1;

			REQUIRE(parse_int(rest, &tex, &rest) &&
				parse_tex_target(rest, &target, &rest) &&
				parse_enum_gl(rest, &format, &rest) &&
				parse_str(rest, "(", &rest) &&
				parse_int(rest, &l, &rest) &&
				parse_int(rest, &w, &rest),
				"Texture storage command not understood "
				"at: %s\n", rest);

			glActiveTexture(GL_TEXTURE0 + tex);
			glGenTextures(1, &tex_obj);
			glBindTexture(target, tex_obj);

			if (!parse_int(rest, &h, &rest))
				glTexStorage1D(target, l, format, w);
			else if (!parse_int(rest, &d, &rest))
				glTexStorage2D(target, l, format, w, h);
			else
				glTexStorage3D(target, l, format, w, h, d);

			if (!piglit_check_gl_error(GL_NO_ERROR)) {
				fprintf(stderr, "glTexStorage error\n");
				piglit_report_result(PIGLIT_FAIL);
			}

			if (target == GL_TEXTURE_1D_ARRAY)
				set_texture_binding(tex, tex_obj, w, 1, h);
			else
				set_texture_binding(tex, tex_obj, w, h, d);

#ifdef PIGLIT_USE_OPENGL
		} else if (sscanf(line,
				  "texture rgbw 1D %d",
				  &tex) == 1) {
			glActiveTexture(GL_TEXTURE0 + tex);
			const GLuint handle = piglit_rgbw_texture_1d();
			set_texture_binding(tex, handle, 4, 1, 1);
#endif

		} else if (sscanf(line,
				  "texture rgbw 3D %d",
				  &tex) == 1) {
			glActiveTexture(GL_TEXTURE0 + tex);
			const GLuint handle = piglit_rgbw_texture_3d();
			set_texture_binding(tex, handle, 2, 2, 2);

		} else if (sscanf(line,
				  "texture rgbw 2DArray %d ( %d , %d , %d )",
				  &tex, &w, &h, &l) == 4) {
			glActiveTexture(GL_TEXTURE0 + tex);
			const GLuint handle = piglit_array_texture(
				GL_TEXTURE_2D_ARRAY, GL_RGBA, w, h, l, GL_FALSE);
			set_texture_binding(tex, handle, w, h, l);

		} else if (sscanf(line,
				  "texture rgbw 1DArray %d ( %d , %d )",
				  &tex, &w, &l) == 3) {
			glActiveTexture(GL_TEXTURE0 + tex);
                        h = 1;
			const GLuint handle = piglit_array_texture(
				GL_TEXTURE_1D_ARRAY, GL_RGBA, w, h, l, GL_FALSE);
			set_texture_binding(tex, handle, w, 1, l);

		} else if (sscanf(line,
				  "texture shadow2D %d ( %d , %d )",
				  &tex, &w, &h) == 3) {
			glActiveTexture(GL_TEXTURE0 + tex);
			const GLuint handle = piglit_depth_texture(
				GL_TEXTURE_2D, GL_DEPTH_COMPONENT,
				w, h, 1, GL_FALSE);
			glTexParameteri(GL_TEXTURE_2D,
					GL_TEXTURE_COMPARE_MODE,
					GL_COMPARE_R_TO_TEXTURE);
			glTexParameteri(GL_TEXTURE_2D,
					GL_TEXTURE_COMPARE_FUNC,
					GL_GREATER);
			set_texture_binding(tex, handle, w, h, 1);

			if (!piglit_is_core_profile &&
			    !(piglit_is_gles() && piglit_get_gl_version() >= 20))
				glEnable(GL_TEXTURE_2D);
		} else if (sscanf(line,
				  "texture shadowRect %d ( %d , %d )",
				  &tex, &w, &h) == 3) {
			glActiveTexture(GL_TEXTURE0 + tex);
			const GLuint handle = piglit_depth_texture(
				GL_TEXTURE_RECTANGLE, GL_DEPTH_COMPONENT,
				w, h, 1, GL_FALSE);
			glTexParameteri(GL_TEXTURE_RECTANGLE,
					GL_TEXTURE_COMPARE_MODE,
					GL_COMPARE_R_TO_TEXTURE);
			glTexParameteri(GL_TEXTURE_RECTANGLE,
					GL_TEXTURE_COMPARE_FUNC,
					GL_GREATER);
			set_texture_binding(tex, handle, w, h, 1);
		} else if (sscanf(line,
				  "texture shadow1D %d ( %d )",
				  &tex, &w) == 2) {
			glActiveTexture(GL_TEXTURE0 + tex);
			const GLuint handle = piglit_depth_texture(
				GL_TEXTURE_1D, GL_DEPTH_COMPONENT,
				w, 1, 1, GL_FALSE);
			glTexParameteri(GL_TEXTURE_1D,
					GL_TEXTURE_COMPARE_MODE,
					GL_COMPARE_R_TO_TEXTURE);
			glTexParameteri(GL_TEXTURE_1D,
					GL_TEXTURE_COMPARE_FUNC,
					GL_GREATER);
			set_texture_binding(tex, handle, w, 1, 1);
		} else if (sscanf(line,
				  "texture shadow1DArray %d ( %d , %d )",
				  &tex, &w, &l) == 3) {
			glActiveTexture(GL_TEXTURE0 + tex);
			const GLuint handle = piglit_depth_texture(
				GL_TEXTURE_1D_ARRAY, GL_DEPTH_COMPONENT,
				w, l, 1, GL_FALSE);
			glTexParameteri(GL_TEXTURE_1D_ARRAY,
					GL_TEXTURE_COMPARE_MODE,
					GL_COMPARE_R_TO_TEXTURE);
			glTexParameteri(GL_TEXTURE_1D_ARRAY,
					GL_TEXTURE_COMPARE_FUNC,
					GL_GREATER);
			set_texture_binding(tex, handle, w, 1, l);
		} else if (sscanf(line,
				  "texture shadow2DArray %d ( %d , %d , %d )",
				  &tex, &w, &h, &l) == 4) {
			glActiveTexture(GL_TEXTURE0 + tex);
			const GLuint handle = piglit_depth_texture(
				GL_TEXTURE_2D_ARRAY, GL_DEPTH_COMPONENT,
				w, h, l, GL_FALSE);
			glTexParameteri(GL_TEXTURE_2D_ARRAY,
					GL_TEXTURE_COMPARE_MODE,
					GL_COMPARE_R_TO_TEXTURE);
			glTexParameteri(GL_TEXTURE_2D_ARRAY,
					GL_TEXTURE_COMPARE_FUNC,
					GL_GREATER);
			set_texture_binding(tex, handle, w, h, l);
		} else if (sscanf(line, "texcoord %d ( %f , %f , %f , %f )",
		                  &x, c + 0, c + 1, c + 2, c + 3) == 5) {
			glMultiTexCoord4fv(GL_TEXTURE0 + x, c);
		} else if (parse_str(line, "texparameter ", &rest)) {
			handle_texparameter(rest);
		} else if (parse_str(line, "uniform ", &rest)) {
			result = program_must_be_in_use();
			set_uniform(rest, block_data);
		} else if (parse_str(line, "subuniform ", &rest)) {
			result = program_must_be_in_use();
			check_shader_subroutine_support();
			set_subroutine_uniform(rest);
		} else if (parse_str(line, "parameter ", &rest)) {
			set_parameter(rest);
		} else if (parse_str(line, "patch parameter ", &rest)) {
			set_patch_parameter(rest);
		} else if (parse_str(line, "program binary save restore", &rest)) {
			program_binary_save_restore(true);
		} else if (parse_str(line, "provoking vertex ", &rest)) {
			set_provoking_vertex(rest);
		} else if (parse_str(line, "link error", &rest)) {
			link_error_expected = true;
			if (link_ok) {
				printf("shader link error expected, but it was successful!\n");
				piglit_report_result(PIGLIT_FAIL);
			} else {
				fprintf(stderr, "Failed to link:\n%s\n", prog_err_info);
			}
		} else if (parse_str(line, "link success", &rest)) {
			result = program_must_be_in_use();
		} else if (parse_str(line, "ubo array index ", &rest)) {
			/* we allow "ubo array index" in order to not
			 * change existing tests using ubo array index
			 */
			parse_ints(rest, &block_data.array_index, 1, NULL);
		} else if (parse_str(line, "block array index ", &rest)) {
			parse_ints(rest, &block_data.array_index, 1, NULL);
		} else if (parse_str(line, "block binding ", &rest)) {
			parse_ints(rest, &block_data.binding, 1, NULL);
		} else if (parse_str(line, "block offset ", &rest)) {
			parse_ints(rest, &block_data.offset, 1, NULL);
		} else if (parse_str(line, "block matrix stride", &rest)) {
			parse_ints(rest, &block_data.matrix_stride, 1, NULL);
		} else if (parse_str(line, "block row major", &rest)) {
			parse_ints(rest, &block_data.row_major, 1, NULL);
		} else if (parse_str(line, "active uniform ", &rest)) {
			active_uniform(rest);
		} else if (parse_str(line, "verify program_query", &rest)) {
			verify_program_query(rest);
		} else if (parse_str(line, "verify program_interface_query ", &rest)) {
			active_program_interface(rest, block_data);
		} else if (parse_str(line, "verify query_object", &rest)) {
			result = verify_query_object_result(rest);
		} else if (parse_str(line, "vertex attrib ", &rest)) {
			set_vertex_attrib(rest);
		} else if (parse_str(line, "newlist ", &rest)) {
			GLenum mode;

			REQUIRE(parse_enum_gl(rest, &mode, &rest),
				"NewList mode command not understood at %s\n",
				rest);

			list = glGenLists(1);
			glNewList(list, mode);
		} else if (parse_str(line, "endlist", NULL)) {
			glEndList();
		} else if (parse_str(line, "calllist", NULL)) {
			glCallList(list);
		} else if (parse_str(line, "deletelist", NULL)) {
			glDeleteLists(list, 1);
		} else if ((line[0] != '\n') && (line[0] != '\0')
			   && (line[0] != '#')) {
			printf("unknown command \"%s\"\n", line);
			piglit_report_result(PIGLIT_FAIL);
		}

		free((void*) line);

		if (result != PIGLIT_PASS) {
			printf("Test failure on line %u\n", line_num);
			full_result = result;
		}

		line_num++;
	}

	if (!link_ok && !link_error_expected) {
		full_result = program_must_be_in_use();
	}

	piglit_present_results();

	if (piglit_automatic) {
		unsigned i;

		/* Free our resources, useful for valgrinding. */
	        free_subroutine_uniforms();

		for (i = 0; i < ARRAY_SIZE(resident_handles); i++)
			clear_resident_handle(i);

		for (i = 0; i < ARRAY_SIZE(texture_bindings); i++)
			clear_texture_binding(i);

		if (prog != 0) {
			glDeleteProgram(prog);
			glUseProgram(0);
		} else {
			if (!sso_in_use)
				glDeleteProgramsARB(1, &prog);
		}

		if (pipeline != 0)
			glDeleteProgramPipelines(1, &pipeline);
	}

	return full_result;
}

static enum piglit_result
init_test(const char *file)
{
	enum piglit_result result;

	result = process_test_script(file);
	if (result != PIGLIT_PASS)
		return result;

	result = link_and_use_shaders();
	if (result != PIGLIT_PASS)
		return result;

	if (sso_in_use)
		glBindProgramPipeline(pipeline);

	if (link_ok && vertex_data_start != NULL) {
		result = program_must_be_in_use();
		if (result != PIGLIT_PASS)
			return result;

		bind_vao_if_supported();

		num_vbo_rows = setup_vbo_from_text(prog, vertex_data_start,
						   vertex_data_end);
		vbo_present = true;
	}
	setup_ubos();
	return PIGLIT_PASS;
}

static void
recreate_gl_context(char *exec_arg, int param_argc, char **param_argv)
{
	int argc = param_argc + 4;
	char **argv = malloc(sizeof(char*) * argc);

	if (!argv) {
		fprintf(stderr, "%s: malloc failed.\n", __func__);
		piglit_report_result(PIGLIT_FAIL);
	}

	argv[0] = exec_arg;
	memcpy(&argv[1], param_argv, param_argc * sizeof(char*));
	argv[argc-3] = "-auto";
	argv[argc-2] = "-fbo";
	argv[argc-1] = "-report-subtests";

	if (gl_fw->destroy)
		gl_fw->destroy(gl_fw);
	gl_fw = NULL;

	exit(main(argc, argv));
}

static bool
validate_current_gl_context(const char *filename)
{
	struct piglit_gl_test_config config = { 0 };

	if (!piglit_gl_test_config_override_size(&config)) {
		config.window_width = DEFAULT_WINDOW_WIDTH;
		config.window_height = DEFAULT_WINDOW_HEIGHT;
	}

	get_required_config(filename, spirv_replaces_glsl, &config);

	if (!current_config.supports_gl_compat_version !=
	    !config.supports_gl_compat_version)
		return false;

	if (!current_config.supports_gl_core_version !=
	    !config.supports_gl_core_version)
		return false;

	if (!current_config.supports_gl_es_version !=
	    !config.supports_gl_es_version)
		return false;

	if (current_config.window_width != config.window_width ||
	    current_config.window_height != config.window_height)
		return false;

	if (!(current_config.window_visual & PIGLIT_GL_VISUAL_DEPTH) &&
	    config.window_visual & PIGLIT_GL_VISUAL_DEPTH)
		return false;

	return true;
}

void
piglit_init(int argc, char **argv)
{
	int major;
	int minor;
	bool core = piglit_is_core_profile;
	bool es;
	enum piglit_result result;
	float default_piglit_tolerance[4];

	use_get_program_binary =
		piglit_strip_arg(&argc, argv, "-get-program-binary") ||
		piglit_env_var_as_boolean("SHADER_RUNNER_GET_PROGRAM_BINARY",
		                          false);

	report_subtests = piglit_strip_arg(&argc, argv, "-report-subtests");
	force_glsl =  piglit_strip_arg(&argc, argv, "-glsl");
	ignore_missing_uniforms = piglit_strip_arg(&argc, argv, "-ignore-missing-uniforms");

	force_no_names = piglit_strip_arg(&argc, argv, "-force-no-names");

	if (force_glsl && spirv_replaces_glsl) {
		printf("Options -glsl and -spirv can't be used at the same time\n");
		piglit_report_result(PIGLIT_FAIL);
	}

	if (spirv_replaces_glsl)
		force_no_names = true;

	if (argc < 2) {
		printf("usage: shader_runner <test.shader_test> [-glsl] [-force-no-names]\n");
		exit(1);
	}

	memcpy(default_piglit_tolerance, piglit_tolerance,
	       sizeof(piglit_tolerance));

	piglit_require_GLSL();

	version_init(&gl_version, VERSION_GL,
		     core, !core,
	             piglit_is_gles(),
	             piglit_get_gl_version());
	piglit_get_glsl_version(&es, &major, &minor);
	version_init(&glsl_version, VERSION_GLSL, core, !core, es,
	             (major * 100) + minor);

#ifdef PIGLIT_USE_OPENGL
	if (piglit_get_gl_version() >= 32)
		glGetIntegerv(GL_MAX_VERTEX_OUTPUT_COMPONENTS,
			      &gl_max_vertex_output_components);
	if (piglit_get_gl_version() >= 20 ||
	    piglit_is_extension_supported("GL_ARB_fragment_shader"))
		glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS,
			      &gl_max_fragment_uniform_components);
	if (piglit_get_gl_version() >= 20 ||
	    piglit_is_extension_supported("GL_ARB_vertex_shader"))
		glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS,
			      &gl_max_vertex_uniform_components);
	if (piglit_get_gl_version() >= 20 ||
	    piglit_is_extension_supported("GL_ARB_vertex_shader") ||
	    piglit_is_extension_supported("GL_ARB_geometry_shader4") ||
	    piglit_is_extension_supported("GL_EXT_geometry_shader4"))
		glGetIntegerv(GL_MAX_VARYING_COMPONENTS,
			      &gl_max_varying_components);
#else
	glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS,
		      &gl_max_fragment_uniform_components);
	glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS,
		      &gl_max_vertex_uniform_components);
	glGetIntegerv(GL_MAX_VARYING_VECTORS,
		      &gl_max_varying_components);
	gl_max_fragment_uniform_components *= 4;
	gl_max_vertex_uniform_components *= 4;
	gl_max_varying_components *= 4;
#endif
	glGetIntegerv(GL_MAX_CLIP_PLANES, &gl_max_clip_planes);

	if (gl_version.num >= 20 ||
	    piglit_is_extension_supported("GL_ARB_vertex_shader"))
		glGetIntegerv(GL_MAX_VERTEX_ATTRIBS,
			      &gl_max_vertex_attribs);
	else
		gl_max_vertex_attribs = 16;

	read_width = render_width = piglit_width;
	read_height = render_height = piglit_height;

#ifdef PIGLIT_USE_OPENGL
	if (piglit_is_extension_supported("GL_ARB_get_program_binary"))
		glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS,
		              &gl_num_program_binary_formats);
#else
	if (piglit_is_extension_supported("GL_OES_get_program_binary"))
		glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS_OES,
		              &gl_num_program_binary_formats);
#endif

	if (use_get_program_binary) {
		if (gl_num_program_binary_formats == 0) {
			printf("Trying to use get_program_binary, but "
			       "GL_NUM_PROGRAM_BINARY == 0\n");
			piglit_report_result(PIGLIT_SKIP);
		}
	}

	/* Automatic mode can run multiple tests per session. */
	if (report_subtests) {
		char testname[4096], *ext;
		int i, j;

		for (i = 1; i < argc; i++) {
			const char *hit, *filename = argv[i];

			memcpy(piglit_tolerance, default_piglit_tolerance,
			       sizeof(piglit_tolerance));

			/* Re-initialize the GL context if a different GL config is required. */
			if (!validate_current_gl_context(filename))
				recreate_gl_context(argv[0], argc - i, argv + i);

			/* Clear global variables to defaults. */
			test_start = NULL;
			assert(num_vertex_shaders == 0);
			assert(num_tess_ctrl_shaders == 0);
			assert(num_tess_eval_shaders == 0);
			assert(num_geometry_shaders == 0);
			assert(num_fragment_shaders == 0);
			assert(num_compute_shaders == 0);
			assert(num_uniform_blocks == 0);
			assert(uniform_block_bos == NULL);
			assert(uniform_block_indexes == NULL);
			geometry_layout_input_type = GL_TRIANGLES;
			geometry_layout_output_type = GL_TRIANGLE_STRIP;
			geometry_layout_vertices_out = 0;
			memset(atomics_bos, 0, sizeof(atomics_bos));
			memset(ssbo, 0, sizeof(ssbo));
			for (j = 0; j < ARRAY_SIZE(subuniform_locations); j++)
				assert(subuniform_locations[j] == NULL);
			memset(num_subuniform_locations, 0, sizeof(num_subuniform_locations));
			shader_string = NULL;
			shader_string_size = 0;
			vertex_data_start = NULL;
			vertex_data_end = NULL;
			prog = 0;
			sso_vertex_prog = 0;
			sso_tess_control_prog = 0;
			sso_tess_eval_prog = 0;
			sso_geometry_prog = 0;
			sso_fragment_prog = 0;
			sso_compute_prog = 0;
			num_vbo_rows = 0;
			vbo_present = false;
			link_ok = false;
			prog_in_use = false;
			sso_in_use = false;
			separable_program = false;
			prog_err_info = NULL;
			vao = 0;

			/* Clear GL states to defaults. */
			glClearColor(0, 0, 0, 0);
# if PIGLIT_USE_OPENGL
			glClearDepth(1);
# else
			glClearDepthf(1.0);
# endif
			glBindFramebuffer(GL_FRAMEBUFFER, piglit_winsys_fbo);
			glActiveTexture(GL_TEXTURE0);
			glUseProgram(0);
			glDisable(GL_DEPTH_TEST);
			glBindBuffer(GL_ARRAY_BUFFER, 0);

			for (int k = 0; k < gl_max_clip_planes; k++) {
				glDisable(GL_CLIP_PLANE0 + k);
			}

			if (!(es) && (gl_version.num >= 20 ||
			     piglit_is_extension_supported("GL_ARB_vertex_program")))
				glDisable(GL_PROGRAM_POINT_SIZE);

			for (int i = 0; i < 16; i++)
				glDisableVertexAttribArray(i);

			if (!piglit_is_core_profile && !es) {
				glMatrixMode(GL_PROJECTION);
				glLoadIdentity();
				glMatrixMode(GL_MODELVIEW);
				glLoadIdentity();
				glShadeModel(GL_SMOOTH);
				glDisable(GL_VERTEX_PROGRAM_TWO_SIDE);
			}

			if (piglit_is_extension_supported("GL_ARB_vertex_program")) {
				glDisable(GL_VERTEX_PROGRAM_ARB);
				glBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0);
			}
			if (piglit_is_extension_supported("GL_ARB_fragment_program")) {
				glDisable(GL_FRAGMENT_PROGRAM_ARB);
				glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);
			}
			if (piglit_is_extension_supported("GL_ARB_separate_shader_objects")) {
				if (!pipeline)
					glGenProgramPipelines(1, &pipeline);
				glBindProgramPipeline(0);
			}

			if (piglit_is_extension_supported("GL_EXT_provoking_vertex"))
				glProvokingVertexEXT(GL_LAST_VERTEX_CONVENTION_EXT);

# if PIGLIT_USE_OPENGL
			if (gl_version.num >= 40 ||
			    piglit_is_extension_supported("GL_ARB_tessellation_shader")) {
				static float ones[] = {1, 1, 1, 1};
				glPatchParameteri(GL_PATCH_VERTICES, 3);
				glPatchParameterfv(GL_PATCH_DEFAULT_OUTER_LEVEL, ones);
				glPatchParameterfv(GL_PATCH_DEFAULT_INNER_LEVEL, ones);
			}
# else
			/* Ideally one would use the following code:
			 *
			 * if (gl_version.num >= 32) {
			 *         glPatchParameteri(GL_PATCH_VERTICES, 3);
			 * }
			 *
			 * however, that doesn't work with mesa because those
			 * symbols apparently need to be exported, but that
			 * breaks non-gles builds.
			 *
			 * It seems rather unlikely that an implementation
			 * would have GLES 3.2 support but not
			 * OES_tessellation_shader.
			 */
			if (piglit_is_extension_supported("GL_OES_tessellation_shader")) {
				glPatchParameteriOES(GL_PATCH_VERTICES_OES, 3);
			}
# endif

			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

			/* Strip the file path. */
			hit = strrchr(filename, PIGLIT_PATH_SEP);
			if (hit)
				strcpy(testname, hit+1);
			else
				strcpy(testname, filename);

			/* Strip the file extension. */
			ext = strstr(testname, ".shader_test");
			if (ext && !ext[12])
				*ext = 0;

			/* Print the name before we start the test, that way if
			 * the test fails we can still resume and know which
			 * test failed */
			printf("PIGLIT TEST: %i - %s\n", test_num, testname);
			fprintf(stderr, "PIGLIT TEST: %i - %s\n", test_num, testname);
			test_num++;

			/* Run the test. */
			result = init_test(filename);

			if (result == PIGLIT_PASS) {
				result = piglit_display();
			}
			/* Use subtest when running with more than one test,
			 * but use regular test result when running with just
			 * one.  This allows the standard process-at-a-time
			 * mode to keep working.
			 */
			if (report_subtests) {
				piglit_report_subtest_result(
					result, "%s", testname);
			} else {
				piglit_report_result(result);
			}

			/* destroy GL objects? */
			teardown_ubos();
			teardown_atomics();
			teardown_fbos();
			teardown_shader_include_paths();
			teardown_xfb();
		}
		exit(0);
	}

	result = init_test(argv[1]);
	if (result != PIGLIT_PASS)
		piglit_report_result(result);
}
