[Piglit] [PATCH v2 04/24] arb_shader_image_load_store: Import grid execution helpers.
Francisco Jerez
currojerez at riseup.net
Thu Dec 4 04:21:53 PST 2014
Define helper functions that are able to run a piece of GLSL code on
an arbitrary shader stage (as of now VS, TCS, TES, GS, FS and CS are
supported) for a given execution size. This makes every shader stage
expose a consistent interface that looks like a sort of primitive
two-dimensional compute grid, with the peculiarity that you can run
several stages at the same time and chain the results of one stage
into the arguments of the next.
This is useful in cases where one needs to run the exact same test on
a number of shader stages and using code generators or duplicating
code would be inconvenient.
---
tests/spec/arb_shader_image_load_store/grid.c | 445 ++++++++++++++++++++++++++
tests/spec/arb_shader_image_load_store/grid.h | 155 +++++++++
2 files changed, 600 insertions(+)
create mode 100644 tests/spec/arb_shader_image_load_store/grid.c
create mode 100644 tests/spec/arb_shader_image_load_store/grid.h
diff --git a/tests/spec/arb_shader_image_load_store/grid.c b/tests/spec/arb_shader_image_load_store/grid.c
new file mode 100644
index 0000000..4c27349
--- /dev/null
+++ b/tests/spec/arb_shader_image_load_store/grid.c
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2014 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/** @file grid.c
+ *
+ * Utility code for running a grid of shader invocations abstracting
+ * out the details of the specific shader stage it's run on.
+ */
+
+#include "common.h"
+
+char *
+concat(char *hunk0, ...)
+{
+ char *s = hunk0;
+ char *hunk;
+ va_list ap;
+
+ va_start(ap, hunk0);
+
+ while ((hunk = va_arg(ap, char *))) {
+ char *t = s;
+ asprintf(&s, "%s\n%s", t, hunk);
+ free(t);
+ free(hunk);
+ }
+
+ va_end(ap);
+ return s;
+}
+
+char *
+image_hunk(const struct image_info img, const char *prefix)
+{
+ char *s = NULL;
+
+ asprintf(&s,
+ "#define %sBASE_T %s\n"
+ "#define %sDATA_T %s\n"
+ "#define %sSCALE vec4(%.8e, %.8e, %.8e, %.8e)\n"
+ "#define %sIMAGE_ADDR_(addr_t, ext, i) %s\n"
+ "#define %sIMAGE_ADDR(idx)"
+ " %sIMAGE_ADDR_(%s, ivec4(%d, %d, %d, %d),"
+ " ((idx).x + W * (idx).y))\n"
+ "#define %sIMAGE_LAYOUT_Q layout(%s)\n"
+ "#define %sIMAGE_BARE_T %s%s\n"
+ "#define %sIMAGE_T %sIMAGE_LAYOUT_Q %sIMAGE_BARE_T\n",
+ prefix, image_scalar_type_name(img.format),
+ prefix, image_vector_type_name(img.format),
+ prefix, image_format_scale(img.format).x,
+ image_format_scale(img.format).y,
+ image_format_scale(img.format).z,
+ image_format_scale(img.format).w,
+ prefix, (image_target_samples(img.target) > 1 ?
+ "addr_t(ivec3(i / ext.x % ext.y,"
+ " i / ext.x / ext.y % ext.z,"
+ " i / ext.x / ext.y / ext.z)),"
+ "(i % ext.x)" :
+ "addr_t(ivec3(i % ext.x,"
+ " i / ext.x % ext.y,"
+ " i / ext.x / ext.y))"),
+ prefix, prefix, img.target->addr_type_name,
+ img.size.x, img.size.y, img.size.z, img.size.w,
+ prefix, img.format->name,
+ prefix, image_type_name(img.format), img.target->name,
+ prefix, prefix, prefix);
+ return s;
+}
+
+static char *
+header_hunk(const struct grid_info grid)
+{
+ char *s = NULL;
+
+ asprintf(&s, "#version 150\n"
+ "#extension GL_ARB_shader_image_load_store : enable\n"
+ "#define W %d\n"
+ "#define H %d\n"
+ "#define N %d\n"
+ "#define GRID_T %s\n"
+ "#define RET_IMAGE_T layout(%s) %s2D\n",
+ grid.size.x, grid.size.y, product(grid.size),
+ image_vector_type_name(grid.format),
+ grid.format->name, image_type_name(grid.format));
+ return s;
+}
+
+static char *
+generate_stage_source(const struct grid_info grid,
+ unsigned stage, const char *_body)
+{
+ char *header = header_hunk(grid);
+ char *body = hunk(_body ? _body :
+ "GRID_T op(ivec2 idx, GRID_T x) {\n"
+ " return x;\n"
+ "}\n");
+
+ switch (stage) {
+ case GL_VERTEX_SHADER:
+ return concat(
+ header, body,
+ hunk("in vec4 piglit_vertex;\n"
+ "out ivec2 vidx;\n"
+ "flat out GRID_T vcolor;\n"
+ "\n"
+ "void main() {\n"
+ " ivec2 idx = ivec2((piglit_vertex + 1.0).xy *"
+ " vec2(W, H) / 2);\n"
+ "\n"
+ " vcolor = op(idx, GRID_T(0));\n"
+ " vidx = idx;\n"
+ " gl_Position = piglit_vertex;\n"
+ "}\n"), NULL);
+
+ case GL_TESS_CONTROL_SHADER:
+ return concat(
+ header,
+ hunk("#extension GL_ARB_tessellation_shader : enable\n"),
+ body,
+ hunk("layout(vertices=4) out;\n"
+ "\n"
+ "in ivec2 vidx[];\n"
+ "flat in GRID_T vcolor[];\n"
+ "out ivec2 tcidx[];\n"
+ "out GRID_T tccolor[];\n"
+ "\n"
+ "void main() {\n"
+ " if (gl_InvocationID == 0) {\n"
+ " /* No subdivisions, thanks. */\n"
+ " gl_TessLevelInner[0] = 1;\n"
+ " gl_TessLevelInner[1] = 1;\n"
+ " gl_TessLevelOuter[0] = 1;\n"
+ " gl_TessLevelOuter[1] = 1;\n"
+ " gl_TessLevelOuter[2] = 1;\n"
+ " gl_TessLevelOuter[3] = 1;\n"
+ " }\n"
+ " tccolor[gl_InvocationID] ="
+ " op(vidx[gl_InvocationID],"
+ " vcolor[gl_InvocationID]);\n"
+ " tcidx[gl_InvocationID] = vidx[gl_InvocationID];\n"
+ " gl_out[gl_InvocationID].gl_Position ="
+ " gl_in[gl_InvocationID].gl_Position;\n"
+ "}\n"), NULL);
+
+ case GL_TESS_EVALUATION_SHADER:
+ return concat(
+ header,
+ hunk("#extension GL_ARB_tessellation_shader : enable\n"),
+ body,
+ hunk("layout(quads, point_mode) in;\n"
+ "\n"
+ "in ivec2 tcidx[];\n"
+ "in GRID_T tccolor[];\n"
+ "out ivec2 teidx;\n"
+ "flat out GRID_T tecolor;\n"
+ "\n"
+ "void main() {\n"
+ " int idx = ((gl_TessCoord.x > 0.5 ? 1 : 0) +"
+ " (gl_TessCoord.y > 0.5 ? 2 : 0));\n"
+ "\n"
+ " tecolor = op(tcidx[idx], tccolor[idx]);\n"
+ " teidx = tcidx[idx];\n"
+ " gl_Position = gl_in[idx].gl_Position;\n"
+ "}\n"), NULL);
+
+ case GL_GEOMETRY_SHADER:
+ return concat(
+ header,
+ hunk(grid.stages & (GL_TESS_CONTROL_SHADER_BIT |
+ GL_TESS_EVALUATION_SHADER_BIT) ?
+ "#define IN(name) te##name\n" :
+ "#define IN(name) v##name\n"),
+ body,
+ hunk("layout(points) in;\n"
+ "layout(points, max_vertices=1) out;\n"
+ "\n"
+ "in ivec2 IN(idx)[];\n"
+ "flat in GRID_T IN(color)[];\n"
+ "flat out GRID_T gcolor;\n"
+ "\n"
+ "void main() {\n"
+ " gcolor = op(IN(idx)[0], IN(color)[0]);\n"
+ " gl_Position = gl_in[0].gl_Position;\n"
+ " EmitVertex();\n"
+ "}\n"), NULL);
+
+ case GL_FRAGMENT_SHADER:
+ return concat(
+ header,
+ hunk(grid.stages & (GL_TESS_CONTROL_SHADER_BIT |
+ GL_TESS_EVALUATION_SHADER_BIT |
+ GL_GEOMETRY_SHADER_BIT) ?
+ "#define IN(name) g##name\n" :
+ "#define IN(name) v##name\n"),
+ body,
+ hunk("flat in GRID_T IN(color);\n"
+ "out GRID_T fcolor;\n"
+ "\n"
+ "void main() {\n"
+ " fcolor = op(ivec2(gl_FragCoord), IN(color));\n"
+ "}\n"), NULL);
+
+ case GL_COMPUTE_SHADER:
+ return concat(
+ header,
+ hunk("#extension GL_ARB_compute_shader : enable\n"),
+ body,
+ hunk("layout (local_size_x = W) in;\n"
+ "\n"
+ "uniform RET_IMAGE_T ret_img;\n"
+ "\n"
+ "void main() {\n"
+ " ivec2 idx = ivec2(gl_GlobalInvocationID);\n"
+ " GRID_T x = op(idx, GRID_T(0));\n"
+ " imageStore(ret_img, idx, x);\n"
+ "}\n"), NULL);
+
+ default:
+ abort();
+ }
+}
+
+static inline unsigned
+get_stage_idx(const struct image_stage_info *stage)
+{
+ return stage - image_stages();
+}
+
+/**
+ * Generate a full program pipeline using the shader code provided in
+ * the \a sources array.
+ */
+static GLuint
+generate_program_v(const struct grid_info grid, const char **sources)
+{
+ const unsigned basic_stages = (GL_FRAGMENT_SHADER_BIT |
+ GL_VERTEX_SHADER_BIT);
+ const unsigned tess_stages = (GL_TESS_CONTROL_SHADER_BIT |
+ GL_TESS_EVALUATION_SHADER_BIT);
+ const unsigned graphic_stages = (basic_stages | tess_stages |
+ GL_GEOMETRY_SHADER_BIT);
+ const unsigned stages =
+ (grid.stages |
+ /* Make a full pipeline if a tesselation shader was
+ * requested. */
+ (grid.stages & tess_stages ? graphic_stages : 0) |
+ /* Make sure there is always a vertex and fragment
+ * shader if we're doing graphics. */
+ (grid.stages & graphic_stages ? basic_stages : 0));
+ GLuint prog = glCreateProgram();
+ const struct image_stage_info *stage;
+
+ for (stage = image_stages(); stage->stage; ++stage) {
+ if (stages & stage->bit) {
+ char *source = generate_stage_source(
+ grid, stage->stage,
+ sources[get_stage_idx(stage)]);
+ GLuint shader = piglit_compile_shader_text_nothrow(
+ stage->stage, source);
+
+ free(source);
+
+ if (!shader) {
+ glDeleteProgram(prog);
+ return 0;
+ }
+
+ glAttachShader(prog, shader);
+ glDeleteShader(shader);
+ }
+ }
+
+ glBindAttribLocation(prog, PIGLIT_ATTRIB_POS, "piglit_vertex");
+ glBindAttribLocation(prog, PIGLIT_ATTRIB_TEX, "piglit_texcoord");
+ glLinkProgram(prog);
+
+ if (!piglit_link_check_status(prog)) {
+ glDeleteProgram(prog);
+ return 0;
+ }
+
+ return prog;
+}
+
+GLuint
+generate_program(const struct grid_info grid, ...)
+{
+ char *sources[6] = { NULL };
+ va_list ap;
+ const struct image_stage_info *stage;
+ unsigned stages, i;
+ GLuint prog;
+
+ va_start(ap, grid);
+
+ for (stages = grid.stages; stages; stages &= ~stage->bit) {
+ stage = get_image_stage(va_arg(ap, GLenum));
+ assert(get_stage_idx(stage) < ARRAY_SIZE(sources));
+ sources[get_stage_idx(stage)] = va_arg(ap, char *);
+ }
+
+ va_end(ap);
+
+ prog = generate_program_v(grid, (const char **)sources);
+
+ for (i = 0; i < ARRAY_SIZE(sources); ++i)
+ free(sources[i]);
+
+ return prog;
+}
+
+bool
+draw_grid(const struct grid_info grid, GLuint prog)
+{
+ static GLuint lprog;
+
+ if (lprog != prog) {
+ glUseProgram(prog);
+ lprog = prog;
+ }
+
+ if (grid.stages & GL_COMPUTE_SHADER_BIT) {
+ set_uniform_int(prog, "ret_img", 7);
+ glDispatchCompute(1, grid.size.y, 1);
+
+ } else if (grid.stages & (GL_TESS_CONTROL_SHADER_BIT |
+ GL_TESS_EVALUATION_SHADER_BIT)) {
+ static struct image_extent size;
+ static GLuint vao, vbo;
+
+ if (size.x != grid.size.x || size.y != grid.size.y) {
+ size = grid.size;
+
+ if (!generate_grid_arrays(
+ &vao, &vbo,
+ 1.0 / size.x - 1.0, 1.0 / size.y - 1.0,
+ 2.0 / size.x, 2.0 / size.y,
+ size.x, size.y))
+ return false;
+ }
+
+ glBindVertexArray(vao);
+ glPatchParameteri(GL_PATCH_VERTICES, 4);
+ glDrawArrays(GL_PATCHES, 0, product(size));
+
+ } else if (grid.stages & (GL_VERTEX_SHADER_BIT |
+ GL_GEOMETRY_SHADER_BIT)) {
+ static struct image_extent size;
+ static GLuint vao, vbo;
+
+ if (size.x != grid.size.x || size.y != grid.size.y) {
+ size = grid.size;
+
+ if (!generate_grid_arrays(
+ &vao, &vbo,
+ 1.0 / size.x - 1.0, 1.0 / size.y - 1.0,
+ 2.0 / size.x, 2.0 / size.y,
+ size.x, size.y))
+ return false;
+ }
+
+ glBindVertexArray(vao);
+ glDrawArrays(GL_POINTS, 0, product(size));
+
+ } else {
+ static struct image_extent size;
+ static GLuint vao, vbo;
+
+ if (size.x != grid.size.x || size.y != grid.size.y) {
+ float vp[4];
+
+ glGetFloati_v(GL_VIEWPORT, 0, vp);
+ size = grid.size;
+
+ if (!generate_grid_arrays(
+ &vao, &vbo, -1.0, -1.0,
+ 2.0 * size.x / vp[2], 2.0 * size.y / vp[3],
+ 2, 2))
+ return false;
+ }
+
+
+ glBindVertexArray(vao);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ }
+
+ return piglit_check_gl_error(GL_NO_ERROR);
+}
+
+bool
+generate_grid_arrays(GLuint *vao, GLuint *vbo,
+ float x, float y, float dx, float dy,
+ unsigned nx, unsigned ny)
+{
+ float (*verts)[4] = malloc(sizeof(*verts) * nx * ny);
+ int i, j;
+
+ for (i = 0; i < nx; ++i) {
+ for (j = 0; j < ny; ++j) {
+ const unsigned k = (nx * (j & ~1) + 2 * (i & ~1) +
+ (i & 1) + 2 * (j & 1));
+ verts[k][0] = x + i * dx;
+ verts[k][1] = y + j * dy;
+ verts[k][2] = 0.0;
+ verts[k][3] = 1.0;
+ }
+ }
+
+ if (!*vao) {
+ glGenVertexArrays(1, vao);
+ glGenBuffers(1, vbo);
+ }
+
+ glBindVertexArray(*vao);
+ glBindBuffer(GL_ARRAY_BUFFER, *vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(*verts) * nx * ny,
+ verts, GL_STATIC_DRAW);
+
+ glVertexAttribPointer(PIGLIT_ATTRIB_POS, 4, GL_FLOAT,
+ GL_FALSE, 0, 0);
+ glEnableVertexAttribArray(PIGLIT_ATTRIB_POS);
+
+ free(verts);
+ return piglit_check_gl_error(GL_NO_ERROR);
+}
diff --git a/tests/spec/arb_shader_image_load_store/grid.h b/tests/spec/arb_shader_image_load_store/grid.h
new file mode 100644
index 0000000..fca67ce
--- /dev/null
+++ b/tests/spec/arb_shader_image_load_store/grid.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/** @file grid.h
+ *
+ * Utility code for running a grid of shader invocations abstracting
+ * out the details of the specific shader stage it's run on.
+ */
+
+#ifndef __PIGLIT_ARB_SHADER_IMAGE_LOAD_STORE_GRID_H__
+#define __PIGLIT_ARB_SHADER_IMAGE_LOAD_STORE_GRID_H__
+
+#include "image.h"
+
+struct grid_info {
+ /** Bitfield of shader stages present in the pipeline
+ * (GL_*_SHADER_BIT). */
+ unsigned stages;
+
+ /** Data type used to hold the values that are passed down
+ * through the pipeline. */
+ const struct image_format_info *format;
+
+ /** Size of the two-dimensional grid. */
+ struct image_extent size;
+};
+
+/**
+ * Construct a grid_info object.
+ */
+static inline struct grid_info
+grid_info(GLenum stage, GLenum format, unsigned w, unsigned h)
+{
+ const struct grid_info grid = {
+ get_image_stage(stage)->bit,
+ get_image_format(format),
+ { w, h, 1, 1 }
+ };
+
+ return grid;
+}
+
+static inline struct grid_info
+set_grid_size(struct grid_info grid, unsigned x, unsigned y)
+{
+ const struct image_extent size = { x, y, 1, 1 };
+ grid.size = size;
+ return grid;
+}
+
+/**
+ * Construct an image_info structure with the same dimensions and
+ * format as the specified grid.
+ */
+static inline struct image_info
+image_info_for_grid(const struct grid_info grid)
+{
+ const struct image_info img = {
+ get_image_target(GL_TEXTURE_2D),
+ grid.format, grid.size,
+ image_format_epsilon(grid.format)
+ };
+
+ return img;
+}
+
+/**
+ * Concatenate a variable number of strings into a newly allocated
+ * buffer. Note that concat() assumes ownership of the provided
+ * arguments and that the argument list must be NULL-terminated.
+ */
+char *
+concat(char *hunk0, ...);
+
+static inline char *
+hunk(const char *s)
+{
+ return strdup(s);
+}
+
+/**
+ * Generate preprocessor defines containing geometry and data type
+ * information for a shader image object.
+ */
+char *
+image_hunk(const struct image_info img, const char *prefix);
+
+/**
+ * Generate a shader program containing all the required stages to run
+ * the provided shader source from \a grid. A series of (GLenum, char *)
+ * pairs should follow as variadic arguments, where the GLenum
+ * argument specifies an additional shader stage (GL_*_SHADER) and the
+ * string specifies a fragment of GLSL code to be included in the same
+ * shader stage. Note that generate_program() takes ownership of the
+ * provided argument strings.
+ *
+ * Each fragment should define a GLSL function with prototype
+ * "GRID_T op(ivec2 idx, GRID_T x)", where \a idx is the
+ * two-dimensional coordinate of a particular shader invocation within
+ * the grid and \a x is the result of the last invocation of op() from
+ * a previous shader stage at the same grid coordinate. Zero is
+ * passed as argument to the topmost invocation of op() in the chain.
+ *
+ * The final result from the chain of op() calls is written as
+ * fragment color to the framebuffer, or written to the read-back
+ * buffer when running a compute shader.
+ *
+ * The generated program will typically be passed as argument to
+ * draw_grid() in order to launch the grid.
+ */
+GLuint
+generate_program(const struct grid_info grid, ...);
+
+/**
+ * Launch a grid of shader invocations of the specified size.
+ * Depending on the specified shader stages an array of triangles,
+ * points or patches will be drawn or a compute grid will be
+ * executed.
+ */
+bool
+draw_grid(const struct grid_info grid, GLuint prog);
+
+/**
+ * Generate vertex arrays intended to be used to launch a grid of
+ * shader invocations using the specified origin (\a x, \a y), spacing
+ * (\a dx, \a dy) and dimensions (\a nx, \a ny). This is done
+ * internally by draw_grid(), but it could be useful on its own for
+ * applications that require more control.
+ */
+bool
+generate_grid_arrays(GLuint *vao, GLuint *vbo,
+ float x, float y, float dx, float dy,
+ unsigned nx, unsigned ny);
+
+#endif
--
2.1.3
More information about the Piglit
mailing list