[02/35] shader_runner/spirv: support loading SPIR-V shaders

Submitted by apinheiro on Aug. 9, 2018, 11:35 a.m.

Details

Message ID 20180809113614.5122-3-apinheiro@igalia.com
State New
Headers show
Series "ARB_gl_spirv: all tests so far" ( rev: 1 ) in Piglit

Not browsing as part of any series.

Commit Message

apinheiro Aug. 9, 2018, 11:35 a.m.
From: Nicolai Hähnle <nicolai.haehnle@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@amd.com>
Signed-off-by: Alejandro Piñeiro <apinheiro@igalia.com>
Signed-off-by: Neil Roberts <nroberts@igalia.com>
---
 tests/shaders/shader_runner.c | 272 ++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 261 insertions(+), 11 deletions(-)

Patch hide | download patch | download mbox

diff --git a/tests/shaders/shader_runner.c b/tests/shaders/shader_runner.c
index 5b35f6b45..baca0a95c 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);
@@ -53,10 +54,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;
 
@@ -143,6 +149,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;
@@ -245,14 +255,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,
 };
@@ -436,6 +452,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 &&
@@ -552,6 +575,7 @@  compile_glsl(GLenum target)
 	return PIGLIT_PASS;
 }
 
+
 static enum piglit_result
 compile_and_bind_program(GLenum target, const char *start, int len)
 {
@@ -677,6 +701,139 @@  program_binary_save_restore(bool script_command)
 	return true;
 }
 
+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)
 {
@@ -1015,6 +1172,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;
 }
@@ -1045,9 +1218,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:
@@ -1057,6 +1229,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);
 
@@ -1068,22 +1242,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);
 
@@ -1093,10 +1299,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;
@@ -1263,7 +1483,6 @@  cleanup:
 	return result;
 }
 
-
 static enum piglit_result
 process_test_script(const char *script_name)
 {
@@ -1283,7 +1502,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;
 
@@ -1300,15 +1519,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;
@@ -1318,9 +1549,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;
@@ -1360,6 +1597,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;
@@ -1381,7 +1624,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 {
@@ -1516,7 +1759,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;
@@ -1525,6 +1768,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];
@@ -4110,7 +4359,7 @@  validate_current_gl_context(const char *filename)
 		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)
@@ -4151,6 +4400,7 @@  piglit_init(int argc, char **argv)
 		                          false);
 
 	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);