[Piglit] [PATCH 02/25] shader_runner/spirv: support loading SPIR-V shaders

Alejandro Piñeiro apinheiro at igalia.com
Tue Apr 17 14:37:08 UTC 2018


From: Nicolai Hähnle <nicolai.haehnle at amd.com>

This commit add support to load a SPIR-V shader, possible since
ARB_gl_spirv.

The SPIR-V shader is included on the shader_test script on new shader
stage versions ([vertex shader spirv], [fragment shader spirv],
etc). It is not in direct binary format, but in assembly format that
it is assembled using spirv-tools. This is done by calling spirv-as,
that is provided by setting the envvar PIGLIT_SPIRV_AS_BINARY. In this
way we are adding an optional runtime dependency, instead of a build
time one.

Those sections are ignored unless [require] section includes "SPIRV
YES" or "SPIRV ONLY". The only difference between the two is that
"SPIRV ONLY" marks tests that can only be run on SPIR-V mode, as they
are testing specific SPIR-V/ARB_gl_spirv features, like
specializations.

By default if those sections are present, shader_runner will try to
load the SPIR-V shader (run in SPIR-V mode). This commit also adds a
command line option "-glsl", that would try to force run the shader in
GLSL mode even if the SPIR-V shader are available. If "SPIRV ONLY" is
present, and force GLSL is attempted, the test will be skipped.

Note that in GLSL mode, ARB_gl_spirv is not required. For that reason,
we will not add ARB_gl_spirv on the [require] section of the tests. We
check for this extension when assembling the SPIR-V shader.

Signed-off-by: Nicolai Hähnle <nicolai.haehnle at amd.com>
Signed-off-by: Alejandro Piñeiro <apinheiro at igalia.com>
Signed-off-by: Neil Roberts <nroberts at igalia.com>
---

Note specially to Nicolai: we have changed greatly the approach of how
SPIR-V shaders loading are supported. The original patch was focused
mostly on being able to use tests from other specs (borrowed tests),
but as we are not sure if that would be accepted, on in which extend,
we focused first on support for the barebone tests for the
extension.

* Removed the support to load SPIR-V from a external file: this is
  useful only for borrowed tests. If we agree that those are useful we
  will re-add the feature.

* Switched the option -spirv for a -glsl option. If a shader test
  includes SPIR-V, by default will be run on SPIR-V mode. -glsl would
  force to run it in GLSL mode (and the reason the original GLSL is
  included). This would be mostly a debug utility. If we add the
  borrowed tests support upstream, we could add a -spirv option, to
  point to try to use the external file.


 tests/shaders/shader_runner.c | 272 ++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 261 insertions(+), 11 deletions(-)

diff --git a/tests/shaders/shader_runner.c b/tests/shaders/shader_runner.c
index 700b11327..b1cd3529a 100644
--- a/tests/shaders/shader_runner.c
+++ b/tests/shaders/shader_runner.c
@@ -29,6 +29,7 @@
 #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"
@@ -39,7 +40,7 @@
 static struct piglit_gl_test_config current_config;
 
 static void
-get_required_config(const char *script_name,
+get_required_config(const char *script_name, bool spirv,
 		    struct piglit_gl_test_config *config);
 static GLenum
 decode_drawing_mode(const char *mode_str);
@@ -51,10 +52,15 @@ PIGLIT_GL_TEST_CONFIG_BEGIN
 	config.window_visual = PIGLIT_GL_VISUAL_RGBA | PIGLIT_GL_VISUAL_DOUBLE;
 	config.khr_no_error_support = PIGLIT_NO_ERRORS;
 
-	if (argc > 1)
-		get_required_config(argv[1], &config);
-	else
+	/* 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], false, &config);
+	} else {
 		config.supports_gl_compat_version = 10;
+	}
 
 	current_config = config;
 
@@ -139,6 +145,10 @@ static bool vbo_present = false;
 static bool link_ok = false;
 static bool prog_in_use = false;
 static bool sso_in_use = false;
+static bool glsl_in_use = false;
+static bool force_glsl = false;
+static bool spirv_in_use = false;
+static bool spirv_replaces_glsl = false;
 static GLchar *prog_err_info = NULL;
 static GLuint vao = 0;
 static GLuint draw_fbo, read_fbo;
@@ -239,14 +249,20 @@ enum states {
 	requirements,
 	vertex_shader,
 	vertex_shader_passthrough,
+	vertex_shader_spirv,
 	vertex_program,
 	tess_ctrl_shader,
+	tess_ctrl_shader_spirv,
 	tess_eval_shader,
+	tess_eval_shader_spirv,
 	geometry_shader,
+	geometry_shader_spirv,
 	geometry_layout,
 	fragment_shader,
+	fragment_shader_spirv,
 	fragment_program,
 	compute_shader,
+	compute_shader_spirv,
 	vertex_data,
 	test,
 };
@@ -429,6 +445,13 @@ 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 &&
@@ -545,6 +568,7 @@ compile_glsl(GLenum target)
 	return PIGLIT_PASS;
 }
 
+
 static enum piglit_result
 compile_and_bind_program(GLenum target, const char *start, int len)
 {
@@ -575,6 +599,139 @@ compile_and_bind_program(GLenum target, const char *start, int len)
 	return PIGLIT_PASS;
 }
 
+static enum piglit_result
+load_and_specialize_spirv(GLenum target,
+			  const char *binary, unsigned size)
+{
+	if (glsl_in_use) {
+		printf("Cannot mix SPIR-V and non-SPIR-V shaders\n");
+		return PIGLIT_FAIL;
+	}
+
+	spirv_in_use = true;
+
+	GLuint shader = glCreateShader(target);
+
+	glShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V_ARB,
+		       binary, size);
+
+	glSpecializeShaderARB(shader, "main", 0, NULL, NULL);
+
+	GLint ok;
+	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);
+
+		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;
+	}
+
+	char *arguments[] = {
+		getenv("PIGLIT_SPIRV_AS_BINARY"),
+		"-o", "-",
+		NULL
+	};
+
+	if (arguments[0] == NULL)
+		arguments[0] = "spirv-as";
+
+	/* 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;
+		}
+	}
+
+	uint8_t *binary_source;
+	size_t binary_source_length;
+	bool res = piglit_subprocess(arguments,
+				     p - stripped_source,
+				     (const uint8_t *)
+				     stripped_source,
+				     &binary_source_length,
+				     &binary_source);
+
+	free(stripped_source);
+
+	if (!res) {
+		fprintf(stderr, "spirv-as failed\n");
+		return PIGLIT_FAIL;
+	}
+
+	enum piglit_result ret;
+
+	ret = load_and_specialize_spirv(target,
+					(const char *)
+					binary_source,
+					binary_source_length);
+
+	free(binary_source);
+
+	return ret;
+}
+
 static enum piglit_result
 link_sso(GLenum target)
 {
@@ -904,6 +1061,22 @@ process_requirement(const char *line)
 
 		sso_in_use = true;
 		glGenProgramPipelines(1, &pipeline);
+	} 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;
+		}
 	}
 	return PIGLIT_PASS;
 }
@@ -934,9 +1107,8 @@ process_geometry_layout(const char *line)
 	}
 }
 
-
 static enum piglit_result
-leave_state(enum states state, const char *line)
+leave_state(enum states state, const char *line, const char *script_name)
 {
 	switch (state) {
 	case none:
@@ -946,6 +1118,8 @@ leave_state(enum states state, const char *line)
 		break;
 
 	case vertex_shader:
+		if (spirv_replaces_glsl)
+			break;
 		shader_string_size = line - shader_string;
 		return compile_glsl(GL_VERTEX_SHADER);
 
@@ -957,22 +1131,54 @@ leave_state(enum states state, const char *line)
 						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 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_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 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_layout:
 		break;
 
 	case fragment_shader:
+		if (spirv_replaces_glsl)
+			break;
 		shader_string_size = line - shader_string;
 		return compile_glsl(GL_FRAGMENT_SHADER);
 
@@ -982,10 +1188,24 @@ leave_state(enum states state, const char *line)
 						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 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 vertex_data:
 		vertex_data_end = line;
 		break;
@@ -1150,7 +1370,6 @@ cleanup:
 	return result;
 }
 
-
 static enum piglit_result
 process_test_script(const char *script_name)
 {
@@ -1170,7 +1389,7 @@ process_test_script(const char *script_name)
 
 	while (line[0] != '\0') {
 		if (line[0] == '[') {
-			result = leave_state(state, line);
+			result = leave_state(state, line, script_name);
 			if (result != PIGLIT_PASS)
 				return result;
 
@@ -1187,15 +1406,27 @@ process_test_script(const char *script_name)
 				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, "[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 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, "[geometry shader]", NULL)) {
 				state = geometry_shader;
 				shader_string = NULL;
+			} else if (parse_str(line, "[geometry shader spirv]", NULL)) {
+				state = geometry_shader_spirv;
+				shader_string = NULL;
 			} else if (parse_str(line, "[geometry layout]", NULL)) {
 				state = geometry_layout;
 				shader_string = NULL;
@@ -1205,9 +1436,15 @@ process_test_script(const char *script_name)
 			} else if (parse_str(line, "[fragment program]", NULL)) {
 				state = fragment_program;
 				shader_string = NULL;
+			} else if (parse_str(line, "[fragment shader spirv]", NULL)) {
+				state = fragment_shader_spirv;
+				shader_string = NULL;
 			} 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, "[vertex data]", NULL)) {
 				state = vertex_data;
 				vertex_data_start = NULL;
@@ -1247,6 +1484,12 @@ process_test_script(const char *script_name)
 			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:
 				if (shader_string == NULL)
 					shader_string = (char *) line;
 				break;
@@ -1268,7 +1511,7 @@ process_test_script(const char *script_name)
 		line_num++;
 	}
 
-	return leave_state(state, line);
+	return leave_state(state, line, script_name);
 }
 
 struct requirement_parse_results {
@@ -1403,7 +1646,7 @@ choose_required_gl_version(struct requirement_parse_results *parse_results,
  * the GL and GLSL version requirements.  Use these to guide context creation.
  */
 static void
-get_required_config(const char *script_name,
+get_required_config(const char *script_name, bool spirv,
 		    struct piglit_gl_test_config *config)
 {
 	struct requirement_parse_results parse_results;
@@ -1412,6 +1655,12 @@ get_required_config(const char *script_name,
 	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];
@@ -3929,7 +4178,7 @@ validate_current_gl_context(const char *filename)
 	config.window_width = DEFAULT_WINDOW_WIDTH;
 	config.window_height = DEFAULT_WINDOW_HEIGHT;
 
-	get_required_config(filename, &config);
+	get_required_config(filename, spirv_replaces_glsl, &config);
 
 	if (!current_config.supports_gl_compat_version !=
 	    !config.supports_gl_compat_version)
@@ -3965,6 +4214,7 @@ piglit_init(int argc, char **argv)
 	float default_piglit_tolerance[4];
 
 	report_subtests = piglit_strip_arg(&argc, argv, "-report-subtests");
+	force_glsl =  piglit_strip_arg(&argc, argv, "-glsl");
 	if (argc < 2) {
 		printf("usage: shader_runner <test.shader_test>\n");
 		exit(1);
-- 
2.14.1



More information about the Piglit mailing list