[Piglit] [PATCH 04/25] arb_shader_image_load_store: Import grid execution helpers.
Ian Romanick
idr at freedesktop.org
Thu Oct 9 01:34:00 PDT 2014
On 10/05/2014 11:00 PM, Francisco Jerez wrote:
> 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.
This is a really good idea. More comments below...
> ---
> 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, ...)
> +{
I'm not a huge fan of having this function release storage. Needing to
use hunk() on the constant string parameters is surprising. All of the
callers appear to be in generate_stage_source, and that function could
just free header and body after the big switch. That's seems pretty
straightforward and clean.
Also... this seems like a useful enough function to have in tests/util/
somewhere.
> + 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
>
More information about the Piglit
mailing list