/* * Copyright (c) 2017 Advanced Micro Devices, Inc. * * 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 * * Test sequences of BufferSubData, ClearBufferSubData, and CopyBufferSubData * with partially committed sparse buffers. * * The same sequence of operations is applied to a sparse buffer and a * shadow buffer on the CPU. In the end, the results are read back and compared. * * Has a stress-test mode: run with -stress N to perform N random operations. */ #include "piglit-util-gl.h" #include #include /* Set this to 1 for verbose logging of operations. */ #define VERBOSE 0 PIGLIT_GL_TEST_CONFIG_BEGIN config.supports_gl_compat_version = 33; config.supports_gl_core_version = 33; PIGLIT_GL_TEST_CONFIG_END static void usage(const char *name) { fprintf(stderr, "usage: %s [-stress N] [-seed S]\n", name); exit(1); } static bool use_ext_dsa_functions = false; static int sparse_buffer_page_size; struct buffer_pair { GLuint sparse_buffer; uint64_t size; uint64_t num_pages; /* Bitmap indicating which pages are committed. */ uint8_t *pagemap; /* Shadow data buffer, contains the expected data. */ uint8_t *shadow; /* Shadow copy that indicates which bytes contain defined data. */ uint8_t *defined; }; static struct buffer_pair * create_buffers(uint64_t size) { struct buffer_pair *buf = calloc(1, sizeof(*buf)); if (!buf) abort(); buf->size = size; buf->num_pages = (size + sparse_buffer_page_size - 1) / sparse_buffer_page_size; buf->shadow = malloc(size); buf->defined = calloc(size, 1); buf->pagemap = calloc((buf->num_pages + 7) / 8, 1); glCreateBuffers(1, &buf->sparse_buffer); glNamedBufferStorage(buf->sparse_buffer, size, NULL, GL_DYNAMIC_STORAGE_BIT | GL_SPARSE_STORAGE_BIT_ARB); if (!piglit_check_gl_error(GL_NO_ERROR)) piglit_report_result(PIGLIT_FAIL); return buf; } static void destroy_buffers(struct buffer_pair *buf) { glDeleteBuffers(1, &buf->sparse_buffer); if (!piglit_check_gl_error(GL_NO_ERROR)) piglit_report_result(PIGLIT_FAIL); free(buf->shadow); free(buf->defined); free(buf->pagemap); free(buf); } static void buffer_page_commitment(struct buffer_pair *buf, uint64_t start_page, uint64_t num_pages, bool commit) { uint64_t commit_start, commit_end; assert(start_page < buf->num_pages); assert(num_pages <= buf->num_pages - start_page); if (VERBOSE) printf("%s(%"PRIu64", %"PRIu64", %s)\n", __func__, start_page, num_pages, commit ? "true" : "false"); commit_start = start_page * sparse_buffer_page_size; commit_end = MIN2((start_page + num_pages) * sparse_buffer_page_size, buf->size); if (use_ext_dsa_functions) glNamedBufferPageCommitmentEXT(buf->sparse_buffer, commit_start, commit_end - commit_start, commit); else glNamedBufferPageCommitmentARB(buf->sparse_buffer, commit_start, commit_end - commit_start, commit); if (!piglit_check_gl_error(GL_NO_ERROR)) piglit_report_result(PIGLIT_FAIL); for (uint64_t page = start_page; page < start_page + num_pages; ++page) { uint8_t *pme = &buf->pagemap[page / 8]; uint8_t bit = 1 << (page % 8); if (!commit) { *pme &= ~bit; memset(&buf->defined[page * sparse_buffer_page_size], 0, sparse_buffer_page_size); } else { *pme |= bit; } } } static bool is_byte_committed(struct buffer_pair *buf, uint64_t offset) { uint64_t page = offset / sparse_buffer_page_size; return (buf->pagemap[page / 8] & (1 << (page % 8))) != 0; } static bool is_byte_defined(struct buffer_pair *buf, uint64_t offset) { return buf->defined[offset]; } /* Mark all bytes in the given range as defined (except for those in * uncommitted pages). */ static void mark_range_defined(struct buffer_pair *buf, uint64_t offset, uint64_t size) { while (size) { uint64_t bytes = MIN2(size, sparse_buffer_page_size - (offset % sparse_buffer_page_size)); if (is_byte_committed(buf, offset)) memset(&buf->defined[offset], 1, bytes); offset += bytes; size -= bytes; } } /* Copy the definedness of the source range to the destination range, except * that bytes in uncommitted pages are left undefined. */ static void copy_range_defined(struct buffer_pair *buf, uint64_t src_offset, uint64_t dst_offset, uint64_t size) { while (size) { uint64_t bytes = MIN3(size, sparse_buffer_page_size - (src_offset % sparse_buffer_page_size), sparse_buffer_page_size - (dst_offset % sparse_buffer_page_size)); if (is_byte_committed(buf, dst_offset)) { if (is_byte_committed(buf, src_offset)) { memcpy(&buf->defined[dst_offset], &buf->defined[src_offset], bytes); } else { memset(&buf->defined[dst_offset], 0, bytes); } } src_offset += bytes; dst_offset += bytes; size -= bytes; } } static void buffers_sub_data(struct buffer_pair *buf, uint64_t offset, uint64_t size) { if (VERBOSE) printf("%s(%"PRIu64", %"PRIu64")\n", __func__, offset, size); for (uint64_t i = 0; i < size; ++i) buf->shadow[offset + i] = rand(); glNamedBufferSubData(buf->sparse_buffer, offset, size, &buf->shadow[offset]); if (!piglit_check_gl_error(GL_NO_ERROR)) piglit_report_result(PIGLIT_FAIL); mark_range_defined(buf, offset, size); } static void clear_buffers_sub_data(struct buffer_pair *buf, uint64_t offset, uint64_t size, unsigned elt_size) { uint32_t pattern = rand(); GLenum internal_format; GLenum type; if (VERBOSE) printf("%s(%"PRIu64", %"PRIu64", %u)\n", __func__, offset, size, elt_size); switch (elt_size) { case 1: internal_format = GL_R8UI; type = GL_UNSIGNED_BYTE; memset(&buf->shadow[offset], pattern, size); break; case 2: internal_format = GL_R16UI; type = GL_UNSIGNED_SHORT; for (uint64_t i = 0; i < size; i += 2) *(uint16_t*)&buf->shadow[offset + i] = pattern; break; case 4: internal_format = GL_R32UI; type = GL_UNSIGNED_INT; for (uint64_t i = 0; i < size; i += 4) *(uint32_t*)&buf->shadow[offset + i] = pattern; break; default: abort(); } glClearNamedBufferSubData(buf->sparse_buffer, internal_format, offset, size, GL_RED_INTEGER, type, &pattern); if (!piglit_check_gl_error(GL_NO_ERROR)) piglit_report_result(PIGLIT_FAIL); mark_range_defined(buf, offset, size); } static void copy_buffers_sub_data(struct buffer_pair *buf, uint64_t src_offset, uint64_t dst_offset, uint64_t size) { assert(size <= buf->size); assert(src_offset <= buf->size - size); assert(dst_offset <= buf->size - size); if (VERBOSE) printf("%s(%"PRIu64", %"PRIu64", %"PRIu64")\n", __func__, src_offset, dst_offset, size); memcpy(&buf->shadow[dst_offset], &buf->shadow[src_offset], size); glCopyNamedBufferSubData(buf->sparse_buffer, buf->sparse_buffer, src_offset, dst_offset, size); if (!piglit_check_gl_error(GL_NO_ERROR)) piglit_report_result(PIGLIT_FAIL); copy_range_defined(buf, src_offset, dst_offset, size); } static void dump_region(struct buffer_pair *buf, uint64_t offset, uint64_t size) { uint64_t end = offset + size; uint8_t *readback = malloc(size); printf("Dump Shadow Sparse\n"); /* "xxxxxxxx: xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx" */ glGetNamedBufferSubData(buf->sparse_buffer, offset, size, readback); if (!piglit_check_gl_error(GL_NO_ERROR)) piglit_report_result(PIGLIT_FAIL); for (uint64_t row = offset & ~7; row < end; row += 8) { printf("%08"PRIx64":", row); for (unsigned i = 0; i < 8; ++i) { if (row + i < offset || row + i >= end) { printf(" "); continue; } bool defined = is_byte_defined(buf, row + i); bool committed = is_byte_committed(buf, row + i); if (defined && !committed) printf(" !!"); /* internal error */ else if (!committed) printf(" .."); else if (!defined) printf(" xx"); else printf(" %02x", buf->shadow[row + i]); } printf(" "); for (unsigned i = 0; i < 8; ++i) { if (row + i < offset || row + i >= end) printf(" "); else printf(" %02x", readback[row + i - offset]); } printf("\n"); } free(readback); } static bool verify_buffers_range(struct buffer_pair *buf, uint64_t offset, uint64_t size) { uint8_t *sparse_readback = malloc(size); bool pass = true; glGetNamedBufferSubData(buf->sparse_buffer, offset, size, sparse_readback); if (!piglit_check_gl_error(GL_NO_ERROR)) piglit_report_result(PIGLIT_FAIL); for (uint64_t i = 0; i < size; ++i) { if (is_byte_defined(buf, offset + i)) { if (!is_byte_committed(buf, offset + i)) { printf("Sanity check failed @ 0x%"PRIx64": defined && !committed\n", offset + i); dump_region(buf, (MAX2(offset + i, 8) - 8) & ~7, 64); pass = false; break; } if (sparse_readback[i] != buf->shadow[offset + i]) { printf("Mismatch in defined region @ 0x%"PRIx64"\n" " Non-sparse: %08x\n" " Sparse: %08x\n", offset + i, buf->shadow[offset + i], sparse_readback[i]); dump_region(buf, (MAX2(offset + i, 8) - 8) & ~7, 64); pass = false; break; } } } free(sparse_readback); return pass; } static bool verify_buffers(struct buffer_pair *buf) { return verify_buffers_range(buf, 0, buf->size); } static bool run_simple() { struct buffer_pair *buf; bool pass = true; buf = create_buffers(sparse_buffer_page_size * 5 / 2); buffer_page_commitment(buf, 0, 1, true); buffer_page_commitment(buf, 2, 1, true); /* Test each command in a way that touches the uncommitted region. */ buffers_sub_data(buf, 0, sparse_buffer_page_size * 5 / 2); if (!verify_buffers(buf)) { pass = false; goto out; } copy_buffers_sub_data(buf, sparse_buffer_page_size - sparse_buffer_page_size / 8, 2 * sparse_buffer_page_size, sparse_buffer_page_size / 4); if (!verify_buffers_range(buf, 2 * sparse_buffer_page_size, sparse_buffer_page_size / 4)) { pass = false; goto out; } clear_buffers_sub_data(buf, sparse_buffer_page_size - 128, 256, 1); if (!verify_buffers_range(buf, sparse_buffer_page_size - 128, 128)) { pass = false; goto out; } clear_buffers_sub_data(buf, sparse_buffer_page_size - 64, 128, 2); if (!verify_buffers_range(buf, sparse_buffer_page_size - 64, 64)) { pass = false; goto out; } clear_buffers_sub_data(buf, sparse_buffer_page_size - 32, 64, 4); if (!verify_buffers_range(buf, sparse_buffer_page_size - 32, 32)) { pass = false; goto out; } /* Test some small "unaligned" cases. */ clear_buffers_sub_data(buf, 3, 256, 1); clear_buffers_sub_data(buf, 262, 258, 2); clear_buffers_sub_data(buf, 644, 260, 4); buffers_sub_data(buf, 1025, 257); copy_buffers_sub_data(buf, 1, 2051, 257); if (!verify_buffers(buf)) { pass = false; goto out; } out: destroy_buffers(buf); return pass; } static unsigned rand_size(uint64_t max) { max = MIN2(max, 1 << 30); unsigned log_max = 0; for (unsigned i = MAX2(max, 1) - 1; i; i >>= 1) log_max++; unsigned log_rand = rand() % (log_max + 1); return rand() % (MIN2(max, 1 << log_rand) + 1); } static bool run_stress(unsigned num_iterations) { struct buffer_pair *buf; unsigned log_num_pages = 10; bool pass = true; buf = create_buffers((1ull << log_num_pages) * sparse_buffer_page_size); /* Initialize a fairly dense random commitment. */ for (unsigned page = 0; page < (1 << log_num_pages);) { unsigned num = MIN2(1 + rand() % 16, (1 << log_num_pages) - page); buffer_page_commitment(buf, page, num, true); page += num; page += 1; /* hole */ } /* Random buffer operations. */ for (unsigned i = 0; i < num_iterations; ++i) { unsigned op = rand() % 8; unsigned dst_offset, size; printf("Iteration %u\n", i); if (op < 2) { size = rand_size(buf->size / 2); dst_offset = rand() % (buf->size - 2 * size + 1); unsigned src_offset = dst_offset + size + rand() % (buf->size - (dst_offset + 2 * size) + 1); if (rand() & 1) { unsigned tmp = dst_offset; dst_offset = src_offset; src_offset = tmp; } copy_buffers_sub_data(buf, src_offset, dst_offset, size); } else if (op < 5) { static const unsigned elt_sizes[] = { 1, 2, 4 }; unsigned elt_size = elt_sizes[rand() % 3]; unsigned nelts = rand_size(buf->size / elt_size); dst_offset = elt_size * (rand() % (buf->size / elt_size - nelts + 1)); size = nelts * elt_size; clear_buffers_sub_data(buf, dst_offset, size, elt_size); } else { size = rand_size(buf->size); dst_offset = rand() % (buf->size - size + 1); buffers_sub_data(buf, dst_offset, size); } if (!verify_buffers_range(buf, dst_offset, size)) { pass = false; break; } } if (!verify_buffers(buf)) pass = false; destroy_buffers(buf); return pass; } enum piglit_result piglit_display(void) { /* not reached */ return PIGLIT_FAIL; } void piglit_init(int argc, char **argv) { int stress_iterations = 0; int seed = time(NULL); bool pass = true; for (int i = 1; i < argc; ++i) { if (!strcmp(argv[i], "-stress")) { ++i; if (i >= argc) usage(argv[0]); stress_iterations = atoi(argv[i]); } else if (!strcmp(argv[i], "-seed")) { ++i; if (i >= argc) usage(argv[0]); seed = i; } else { usage(argv[0]); } } piglit_require_extension("GL_ARB_direct_state_access"); piglit_require_extension("GL_ARB_sparse_buffer"); glGetIntegerv(GL_SPARSE_BUFFER_PAGE_SIZE_ARB, &sparse_buffer_page_size); if (stress_iterations > 0) { printf("Running with seed %d.\n" "To reproduce, run: %s -seed %d\n", seed, argv[0], seed); srand(seed); pass = run_stress(stress_iterations) && pass; } else { pass = run_simple() && pass; } if (piglit_is_extension_supported("GL_EXT_direct_state_access")) { use_ext_dsa_functions = true; pass = run_simple() && pass; /* Verify that using buffer = 0 generates an error */ glNamedBufferPageCommitmentEXT(0, 0, 1000, true); pass = piglit_check_gl_error(GL_INVALID_OPERATION) && pass; /* Verify that using a buffer that has never been bound * generates an error but initialize the buffer. */ GLuint b; glGenBuffers(1, &b); pass = !glIsBuffer(b) && pass; glNamedBufferPageCommitmentEXT(b, 0, 1000, true); pass = piglit_check_gl_error(GL_INVALID_OPERATION) && pass; pass = glIsBuffer(b) && pass; glDeleteBuffers(1, &b); } piglit_report_result(pass ? PIGLIT_PASS : PIGLIT_FAIL); }