/* * 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 framebuffer-blit-levels.c * * This test verifies that glBlitFramebuffer operates correctly when * the read or draw framebuffer is bound to a nonzero miplevel of a * texture. * * The test can be run in two modes: "read" and "draw". In "read" * mode, the layered/mipmapped texture is attached to * GL_READ_FRAMEBUFFER, and in "draw" mode, the layered/mipmapped * texture is attached to GL_DRAW_FRAMEBUFFER. * * The test operates as follows: * * - A 2D test texture is created with all miplevels present. An * auxiliary 2D texture is also created which has a single miplevel. * * - The test texture is populated with a deterministic pattern of * data. In "read" mode, this is done by simply uploading the data * pattern using glTexImage2D. In "draw" mode, this is done by * first uploading the data pattern to the auxiliary texture, and * then blitting it to the test texture (this checks that blits work * properly when GL_DRAW_FRAMEBUFFER is the test texture). * * - The data in the test texture is then verified. In "draw" mode, * this is done by a direct call to glReadPixels(). In "read" mode, * this is done by first blitting the data to the auxiliary texture, * and then using glReadPixels() on the auxiliary texture (this * checks that blits work properly when GL_READ_FRAMEBUFFER is the * test texture). */ #include "piglit-util-gl.h" PIGLIT_GL_TEST_CONFIG_BEGIN config.supports_gl_compat_version = 10; config.window_visual = PIGLIT_GL_VISUAL_RGB; config.khr_no_error_support = PIGLIT_NO_ERRORS; PIGLIT_GL_TEST_CONFIG_END enum { TEST_MODE_DRAW, TEST_MODE_READ, } test_mode; GLuint test_framebuffer; GLuint aux_framebuffer; GLuint test_texture; GLuint aux_texture; GLenum texture_internal_format; GLenum texture_format; GLenum readback_format; GLenum texture_type; GLbitfield blit_mask; GLenum framebuffer_attachment; #define LOG2_SIZE 7 #define SIZE (1 << LOG2_SIZE) #define NUM_LEVELS (LOG2_SIZE + 1) /** * Generate a block of test data in which each pixel has a unique RGBA * color. Different values of the \c level parameter produce * different unique sets of pixels. * * This takes advantage of the Chinese Remainder Theorem to produce a * unique color for each pixel--we produce the R, G, B, and A values * by taking an integer mod four different primes. */ static void create_test_data_rgba(GLfloat *data, unsigned level, unsigned width, unsigned height) { unsigned pixel; unsigned num_pixels = width * height; for (pixel = 0; pixel < num_pixels; ++pixel) { unsigned unique_value = level * (SIZE * SIZE) + pixel; data[4*pixel + 0] = (unique_value % 233) / 233.0; data[4*pixel + 1] = (unique_value % 239) / 239.0; data[4*pixel + 2] = (unique_value % 241) / 241.0; data[4*pixel + 3] = (unique_value % 251) / 251.0; } } /** * Generate a block of test data where each pixel has a unique depth value in * the range [0.0, 1.0). */ static void create_test_data_depth(GLfloat *data, unsigned level, unsigned width, unsigned height) { unsigned pixel; unsigned num_pixels = width * height; double depth_delta = 0.95 / num_pixels; double depth_value = 0; for (pixel = 0; pixel < num_pixels; ++pixel) { data[pixel] = depth_value; depth_value += depth_delta; } } /** * Generate a block of test data appropriate for testing a stencil * buffer. Different values of the \c level parameter produce * different unique sets of pixels. * * Since there are only 256 possible stencil values, we need to be * clever to generate a pattern that doesn't repeat every 256 pixels. * Here's how we do it: for each consecutive pair of values, we * compute x = (16451 * (i + 1)) % 65521 (where i is the index of the * pair, and starts at a different value for each miplevel). Since * 16451 and 65521 are relatively prime, this pattern won't repeat for * 65521 pairs. Then we set the first pixel in the pair to x / 256 * and the second pixel in the pair to x % 256. */ static void create_test_data_stencil(GLbyte *data, unsigned level, unsigned width, unsigned height) { unsigned pixel; unsigned num_pixels = width * height; for (pixel = 0; pixel < num_pixels; ++pixel) { unsigned i = (level * (SIZE * SIZE) + pixel) / 2; unsigned x = (16451 * (i + 1)) % 65521; if (pixel % 2 == 0) data[pixel] = x / 256; else data[pixel] = x % 256; } } /** * As with create_test_data_stencil(), but intersperse the stencil * data with depth values of 0, so that the resulting data can be * uploaded to a depth/stencil texture using glTexImage2D(). */ static void create_test_data_depthstencil(GLbyte *data, unsigned level, unsigned width, unsigned height) { GLbyte *stencil_data = malloc(width * height); unsigned i; create_test_data_stencil(stencil_data, level, width, height); for (i = 0; i < width * height; ++i) { data[4 * i] = stencil_data[i]; data[4 * i + 1] = 0; data[4 * i + 2] = 0; data[4 * i + 3] = 0; } free(stencil_data); } static void create_test_data(GLfloat *data, GLenum format, unsigned level, unsigned width, unsigned height) { switch (format) { case GL_RGBA: create_test_data_rgba(data, level, width, height); break; case GL_DEPTH_COMPONENT: create_test_data_depth(data, level, width, height); break; case GL_STENCIL_INDEX: create_test_data_stencil((GLbyte *) data, level, width, height); break; case GL_DEPTH_STENCIL: create_test_data_depthstencil((GLbyte *) data, level, width, height); break; default: assert(0); break; } } static void print_usage_and_exit(char *prog_name) { printf("Usage: %s \n" " where is one of:\n" " draw: test blitting *to* the given texture type\n" " read: test blitting *from* the given texture type\n" " where is one of:\n" " rgba\n" " depth\n" " stencil\n", prog_name); piglit_report_result(PIGLIT_FAIL); } void piglit_init(int argc, char **argv) { unsigned level; if (argc != 3) { print_usage_and_exit(argv[0]); } if (strcmp(argv[1], "draw") == 0) { test_mode = TEST_MODE_DRAW; } else if (strcmp(argv[1], "read") == 0) { test_mode = TEST_MODE_READ; } else { print_usage_and_exit(argv[0]); } if(strcmp(argv[2], "rgba") == 0) { texture_internal_format = GL_RGBA; texture_format = GL_RGBA; readback_format = GL_RGBA; texture_type = GL_FLOAT; framebuffer_attachment = GL_COLOR_ATTACHMENT0; blit_mask = GL_COLOR_BUFFER_BIT; } else if (strcmp(argv[2], "depth") == 0) { texture_internal_format = GL_DEPTH_COMPONENT; texture_format = GL_DEPTH_COMPONENT; readback_format = GL_DEPTH_COMPONENT; texture_type = GL_FLOAT; framebuffer_attachment = GL_DEPTH_ATTACHMENT; blit_mask = GL_DEPTH_BUFFER_BIT; } else if (strcmp(argv[2], "stencil") == 0) { texture_internal_format = GL_DEPTH_STENCIL; texture_format = GL_DEPTH_STENCIL; readback_format = GL_STENCIL_INDEX; texture_type = GL_UNSIGNED_INT_24_8; framebuffer_attachment = GL_DEPTH_STENCIL_ATTACHMENT; blit_mask = GL_STENCIL_BUFFER_BIT; } else { print_usage_and_exit(argv[0]); } piglit_require_extension("GL_ARB_framebuffer_object"); /* Set up test framebuffer and test texture, but don't * populate with data yet. */ glGenFramebuffers(1, &test_framebuffer); glGenTextures(1, &test_texture); glBindTexture(GL_TEXTURE_2D, test_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); for (level = 0; level < NUM_LEVELS; ++level) { glTexImage2D(GL_TEXTURE_2D, level, texture_internal_format, SIZE >> level, SIZE >> level, 0 /* border */, texture_format, texture_type, NULL /* data */); } /* Set up aux framebuffer */ glGenFramebuffers(1, &aux_framebuffer); glGenTextures(1, &aux_texture); glBindTexture(GL_TEXTURE_2D, aux_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0 /* level */, texture_internal_format, SIZE, SIZE, 0 /* border */, texture_format, texture_type, NULL /* data */); glBindFramebuffer(GL_FRAMEBUFFER, aux_framebuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, framebuffer_attachment, GL_TEXTURE_2D, aux_texture, 0 /* level */); } /** * Upload test data to the given texture. * * \param data_level is the miplevel that the data is destined to * ultimately end up in--this influences the contents of the test * data. * * \param upload_level is the miplevel that the data should be * uploaded to. */ static void upload_test_data(GLuint texture, unsigned data_level, unsigned upload_level, unsigned width, unsigned height) { GLfloat *data = malloc(SIZE * SIZE * 4 * sizeof(GLfloat)); glBindTexture(GL_TEXTURE_2D, texture); create_test_data(data, texture_format, data_level, width, height); glTexImage2D(GL_TEXTURE_2D, upload_level, texture_internal_format, width, height, 0 /* border */, texture_format, texture_type, data); free(data); } static bool test_image(unsigned width, unsigned height, const GLfloat *expected) { if (readback_format == GL_STENCIL_INDEX) { return piglit_probe_image_stencil(0, 0, width, height, (const GLubyte *) expected); } else { return piglit_probe_image_color(0, 0, width, height, readback_format, expected); } } enum piglit_result piglit_display() { bool pass = true; GLfloat *data = malloc(SIZE * SIZE * 4 * sizeof(GLfloat)); unsigned level; /* Populate the test texture */ for (level = 0; level < NUM_LEVELS; ++level) { unsigned width = SIZE >> level; unsigned height = SIZE >> level; if (test_mode == TEST_MODE_READ) { /* Populate directly */ upload_test_data(test_texture, level, level, width, height); } else { /* Populate via aux texture */ upload_test_data(aux_texture, level, 0, width, height); glBindFramebuffer(GL_READ_FRAMEBUFFER, aux_framebuffer); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, test_framebuffer); glBindTexture(GL_TEXTURE_2D, test_texture); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, framebuffer_attachment, GL_TEXTURE_2D, test_texture, level); glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, blit_mask, GL_NEAREST); } } /* Verify the test texture */ for (level = 0; level < NUM_LEVELS; ++level) { unsigned width = SIZE >> level; unsigned height = SIZE >> level; printf("Testing level %d\n", level); create_test_data(data, readback_format, level, width, height); if (test_mode == TEST_MODE_DRAW) { /* Read texture data directly using glReadPixels() */ glBindFramebuffer(GL_READ_FRAMEBUFFER, test_texture); glFramebufferTexture2D(GL_READ_FRAMEBUFFER, framebuffer_attachment, GL_TEXTURE_2D, test_texture, level); pass = test_image(width, height, data) && pass; } else { /* Read via aux texture */ glBindFramebuffer(GL_READ_FRAMEBUFFER, test_framebuffer); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, aux_framebuffer); glBindTexture(GL_TEXTURE_2D, test_texture); glFramebufferTexture2D(GL_READ_FRAMEBUFFER, framebuffer_attachment, GL_TEXTURE_2D, test_texture, level); glBindTexture(GL_TEXTURE_2D, aux_texture); glTexImage2D(GL_TEXTURE_2D, 0 /* level */, texture_internal_format, width, height, 0 /* border */, texture_format, texture_type, NULL); glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, blit_mask, GL_NEAREST); glBindFramebuffer(GL_READ_FRAMEBUFFER, aux_framebuffer); pass = test_image(width, height, data) && pass; } } free(data); return pass ? PIGLIT_PASS : PIGLIT_FAIL; }