/* * Copyright © 2012 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 change-size.c * * Confirm that transform feedback properly handles a change in the * size of a transform feedback buffer after it is bound but before it * is used. * * In particular, this test verifies the following behaviours, from * the GL 4.3 spec, section 6.1.1 ("Binding Buffer Objects to Indexed * Targets"): * * BindBufferBase binds the entire buffer, even when the size of the buffer * is changed after the binding is established. It is equivalent to calling * BindBufferRange with offset zero, while size is determined by the size of * the bound buffer at the time the binding is used. * * Regardless of the size specified with BindBufferRange, or indirectly with * BindBufferBase, the GL will never read or write beyond the end of a bound * buffer. In some cases this constraint may result in visibly different * behavior when a buffer overflow would otherwise result, such as described * for transform feedback operations in section 13.2.2. * * This test verifies that the expected number of primitives are * written after a change to the size of the transform feedback * buffer, using both a GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN query * and by looking at the contents of the buffer itself. We run * transform feedback in GL_TRIANGLES mode and use a buffer size that * is not a multiple of 3, so that we can look at the last element in * the transform feedback buffer and verify that transform feedback * didn't overwrite it. * * The test performs the following operations: * * 1. Create a transform feedback buffer using glBufferData(). * * 2. Bind the buffer for transform feedback using either * glBindBufferBase, glBindBufferRange, or glBindBufferOffsetEXT * (if supported). * * 3. Change the size of the bound buffer using glBufferData(). A * non-null data pointer is passed to glBufferData() to store a * known pattern in the buffer, so that in step 6 we'll be able to * determine which parts of the buffer were overwritten. * * 4. Draw some triangles, feeding back a single float from each * vertex. * * 5. Verify, using a GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN query, * that the expected number of primitives were written to the * buffer. * * 6. Verify, using glMapBuffer, that the expected data was written to * the buffer. */ #include "piglit-util-gl.h" PIGLIT_GL_TEST_CONFIG_BEGIN config.supports_gl_compat_version = 10; config.window_visual = PIGLIT_GL_VISUAL_DOUBLE | PIGLIT_GL_VISUAL_RGBA; config.khr_no_error_support = PIGLIT_NO_ERRORS; PIGLIT_GL_TEST_CONFIG_END /** * Maximum buffer size--used for declaraing static arrays. Measured * in multiples of sizeof(GLfloat). */ #define MAX_BUFFER_SIZE_FLOATS 10 static GLuint prog; static GLuint xfb_buf; static GLuint query; const struct test_case { /** * Name of the test case. NULL is used as a sentinel to mark * the end of the list of test cases. */ const char *name; /** * Size that the buffer should have before binding. Measured * in multiples of sizeof(GLfloat). */ unsigned initial_size; /** * Offset to pass to glBindBufferRange/glBindBufferOffsetEXT, * or zero if glBindBufferBase should be used. Measured in * multiples of sizeof(GLfloat). */ unsigned bind_offset; /** * Size to pass to glBindBufferRange, or zero if * glBindBufferOffsetEXT/glBindBufferBase should be used. * Measured in multiples of sizeof(GLfloat). */ unsigned bind_size; /** * Size of the buffer that should be passed to * glBindBufferData after the buffer is bound. Measured in * multiples of sizeof(GLfloat). */ unsigned new_size; /** * Number of triangles to draw. */ unsigned num_draw_triangles; /** * Number of primitives that are expected to be written to the * buffer. */ unsigned num_feedback_triangles; } test_cases[] = { /* name initial bind bind new num tris: * size offset size size draw feedback */ { "base-shrink", 7, 0, 0, 4, 2, 1 }, { "base-grow", 4, 0, 0, 7, 2, 2 }, { "offset-shrink", 10, 3, 0, 7, 2, 1 }, { "offset-grow", 7, 3, 0, 10, 2, 2 }, { "range-shrink", 10, 3, 7, 7, 2, 1 }, { "range-grow", 7, 3, 4, 10, 2, 1 }, { NULL, 0, 0, 0, 0, 0, 0 } }; const struct test_case *selected_test; /** * Vertex shader, which simply copies its input attribute to its * output varying, adding 100 in the process. */ static const char *vstext = "#version 120\n" "attribute float input_value;\n" "varying float output_value;\n" "\n" "void main()\n" "{\n" " gl_Position = vec4(0.0);\n" " output_value = 100.0 + input_value;\n" "}\n"; static void print_usage_and_exit(const char *prog_name) { unsigned i; printf("Usage: %s \n" " where is one of the following:\n", prog_name); for (i = 0; test_cases[i].name != NULL; i++) printf(" %s\n", test_cases[i].name); exit(1); } static const struct test_case * interpret_test_case_arg(const char *arg) { unsigned i; for (i = 0; test_cases[i].name != NULL; i++) { if (strcmp(test_cases[i].name, arg) == 0) return &test_cases[i]; } return NULL; } void piglit_init(int argc, char **argv) { const char *varying_name = "output_value"; /* Parse args */ if (argc != 2) print_usage_and_exit(argv[0]); selected_test = interpret_test_case_arg(argv[1]); if (selected_test == NULL) print_usage_and_exit(argv[0]); /* Make sure required GL features are present */ piglit_require_GLSL_version(120); piglit_require_transform_feedback(); if (selected_test->bind_offset != 0 && selected_test->bind_size == 0) { /* Test requires glBindBufferOffsetEXT, which is in * EXT_transform_feedback, but was never adopted into * OpenGL. */ piglit_require_extension("GL_EXT_transform_feedback"); } /* Create program and buffer */ prog = piglit_build_simple_program_unlinked(vstext, NULL); glTransformFeedbackVaryings(prog, 1, &varying_name, GL_INTERLEAVED_ATTRIBS); glLinkProgram(prog); if (!piglit_link_check_status(prog)) piglit_report_result(PIGLIT_FAIL); glGenBuffers(1, &xfb_buf); glGenQueries(1, &query); if (!piglit_check_gl_error(GL_NO_ERROR)) piglit_report_result(PIGLIT_FAIL); } enum piglit_result piglit_display(void) { GLint input_index = glGetAttribLocation(prog, "input_value"); GLfloat canary_data[MAX_BUFFER_SIZE_FLOATS]; GLfloat input_data[MAX_BUFFER_SIZE_FLOATS]; GLfloat expected_data[MAX_BUFFER_SIZE_FLOATS]; GLfloat *output_data; GLuint query_result; GLboolean pass = GL_TRUE; unsigned i; glUseProgram(prog); /* Create a transform feedback buffer. */ glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfb_buf); glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, selected_test->initial_size * sizeof(GLfloat), NULL, GL_STREAM_READ); /* Bind the buffer for transform feedback. */ if (selected_test->bind_size != 0) { glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfb_buf, selected_test->bind_offset * sizeof(GLfloat), selected_test->bind_size * sizeof(GLfloat)); } else if (selected_test->bind_offset != 0) { glBindBufferOffsetEXT(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfb_buf, selected_test->bind_offset * sizeof(GLfloat)); } else { glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfb_buf); } /* Change the size of the bound buffer. */ for (i = 0; i < MAX_BUFFER_SIZE_FLOATS; i++) canary_data[i] = -1; glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, selected_test->new_size * sizeof(GLfloat), canary_data, GL_STREAM_READ); /* Draw some triangles. */ for (i = 0; i < MAX_BUFFER_SIZE_FLOATS; i++) input_data[i] = i + 1; glBindBuffer(GL_ARRAY_BUFFER, 0); glVertexAttribPointer(input_index, 1, GL_FLOAT, GL_FALSE, sizeof(GLfloat), input_data); glEnableVertexAttribArray(input_index); glBeginTransformFeedback(GL_TRIANGLES); glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query); glDrawArrays(GL_TRIANGLES, 0, selected_test->num_draw_triangles * 3); glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN); glEndTransformFeedback(); /* Verify that the expected number of primitives were * written. */ glGetQueryObjectuiv(query, GL_QUERY_RESULT, &query_result); printf("PRIMITIVES_WRITTEN: expected=%u, actual=%u\n", selected_test->num_feedback_triangles, query_result); if (query_result != selected_test->num_feedback_triangles) pass = GL_FALSE; /* Verify that the expected data was written. */ for (i = 0; i < selected_test->new_size; i++) { if (i >= selected_test->bind_offset && i < (3 * selected_test->num_feedback_triangles + selected_test->bind_offset)) { expected_data[i] = 100.0 + input_data[i - selected_test->bind_offset]; } else { expected_data[i] = canary_data[i]; } } output_data = glMapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, GL_READ_ONLY); for (i = 0; i < selected_test->new_size; ++i) { printf("data[%u]: expected=%f, actual=%f\n", i, expected_data[i], output_data[i]); if (expected_data[i] != output_data[i]) pass = GL_FALSE; } glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); piglit_present_results(); return pass ? PIGLIT_PASS : PIGLIT_FAIL; }