/* * Copyright (c) 2011 Intel Corporation * Copyright (c) 2019 Rob Clark * * 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, sub license, * 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 NON-INFRINGEMENT. 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. */ #include #include #include #include #include #include #include #include #ifdef HAVE_LIBPNG #include #endif #include "common.h" #include "drm-common.h" /* A tool for debugging texture layout. Somewhat inspired by piglit's * texelFetch, but with some differences: * * - Uses GLES3+ and kms/gbm to reduce dependencies. * - Only samples from FS stage, since this simplifies things and * testing from VS/GS is not important for texture layout * - Encodes the slice and mipmap level as the texture contents; * since this is mostly for making sure we have the slice and * level offsets to put he pixel data where the hw expects to * read it from, the x/y gradient is less useful, but knowing * the slice/level that was actually read helps give better * error messages. (Once this is working correctly, you can * go off and run texelFetch to make sure you didn't get the * tiling format wrong, etc.) * - Supports all different formats, since the rules for calculate * level/slice offset can differ, or be parameterized differently, * for different formats. * - multiple levels of zoom.. samples same tex coord for NxN screen * coordinates, to more easily see smaller mipmap levels on hidpi * screens. * * Like texelFetch, it also supports: * - 2D, 3D, 2DArray * - Mipmapping * * Description of layout on screen from texelFetch: * * Draws a series of "rectangles" which display each miplevel and array slice, * at full size. They are laid out as follows: * * miplevel 3 + + + + + * * miplevel 2 +-+ +-+ +-+ +-+ +-+ * +-+ +-+ +-+ +-+ +-+ * * miplevel 1 +---+ +---+ +---+ +---+ +---+ * | | | | | | | | | | * +---+ +---+ +---+ +---+ +---+ * * +------+ +------+ +------+ +------+ +------+ * miplevel 0 | | | | | | | | | | * | | | | | | | | | | * +------+ +------+ +------+ +------+ +------+ * slice #0 slice #1 slice #2 slice #3 slice #4 * */ static struct egl _egl; static const struct egl *egl = &_egl; static const struct gbm *gbm; static const struct drm *drm; static int max_error_frames = 5; static int error_frames; static int zoom = 1; static bool full; static bool stop; #ifdef HAVE_LIBPNG static bool png; #endif static GLenum target; static struct size { unsigned x, y, z; } size, minsz, maxsz; int miplevels; static bool is_array(GLenum target) { return target == GL_TEXTURE_2D_ARRAY; } static int get_ncomp(GLenum ufmt) { switch (ufmt) { case GL_RED: case GL_RED_INTEGER: case GL_DEPTH_COMPONENT: return 1; case GL_RG: case GL_RG_INTEGER: case GL_DEPTH_STENCIL: return 2; case GL_RGB: case GL_RGB_INTEGER: return 3; case GL_RGBA: case GL_RGBA_INTEGER: return 4; default: assert(!"bad format"); return 0; } } /* * Formats Table * ------------- * * The worst case (smallest number of bits) to encode slice and level # are * the R8 formats, but 4 bits for each is sufficient precision. To simplify * things we standardize[*] how this is encoded: * * +----------+--------------+--------------------+ * | type | range used | GLenum type | * +----------+--------------+--------------------+ * | SNORM | -1.0..1.0 | GL_BYTE | * | UNORM | 0.0..1.0 | GL_UNSIGNED_BYTE | * | FLOAT | 0.0..1.0 | GL_FLOAT | * | SINT8 | -128..127 | GL_BYTE | * | UINT8 | -128..127 | GL_UNSIGNED_BYTE | * | SINT16 | -128..127 | GL_SHORT | * | UINT16 | -128..127 | GL_UNSIGNED_SHORT | * | SINT32 | -128..127 | GL_INT | * | UINT32 | -128..127 | GL_UNSIGNED_INT | * +----------+--------------+--------------------+ * * [*] The "oddball" formats like RGB565 will need an additional level of * "packing" to encode the 8bit slice+level across multiple channels. * Other formats simply replicate the same value across all channels. * * The shader unpacks the slice/level value and returns them as gl_FragColor * red and green channels. */ enum type { SNORM, UNORM, FLOAT, SINT8, UINT8, SINT16, UINT16, SINT32, UINT32, // TODO add oddballs }; static int enc_ls(int level, int slice) { return ((level << 3) & 0x78) | (slice & 0x7); } static void * encode_BYTE(void *buf, int ncomp, int w, int h, int level, int slice) { int8_t *ptr = buf; int8_t val = enc_ls(level, slice) - 127; for (int i = 0; i < h; i++) { for (int j = 0; j < (w*ncomp); j++) *(ptr++) = val; val += (i & 1) ? -128 : 128; } return ptr; } static void * encode_UNSIGNED_BYTE(void *buf, int ncomp, int w, int h, int level, int slice) { uint8_t *ptr = buf; uint8_t val = enc_ls(level, slice); for (int i = 0; i < h; i++) { for (int j = 0; j < (w*ncomp); j++) *(ptr++) = val; val += (i & 1) ? -128 : 128; } return ptr; } static void * encode_FLOAT(void *buf, int ncomp, int w, int h, int level, int slice) { float *ptr = buf; int encoded_value = enc_ls(level, slice); float original_value = ((float)encoded_value) / 255.0; float complement_value = ((float)(encoded_value + 128)) / 255.0; float val; for (int i = 0; i < h; i++) { val = (i & 1) ? complement_value : original_value; for (int j = 0; j < (w*ncomp); j++) *(ptr++) = val; } return ptr; } static void * encode_SHORT(void *buf, int ncomp, int w, int h, int level, int slice) { int16_t *ptr = buf; int16_t val = enc_ls(level, slice) - 127; for (int i = 0; i < h; i++) { for (int j = 0; j < (w*ncomp); j++) *(ptr++) = val; val += (i & 1) ? -128 : 128; } return ptr; } static void * encode_UNSIGNED_SHORT(void *buf, int ncomp, int w, int h, int level, int slice) { uint16_t *ptr = buf; uint16_t val = enc_ls(level, slice); for (int i = 0; i < h; i++) { for (int j = 0; j < (w*ncomp); j++) *(ptr++) = val; val += (i & 1) ? -128 : 128; } return ptr; } static void * encode_INT(void *buf, int ncomp, int w, int h, int level, int slice) { int32_t *ptr = buf; int32_t val = enc_ls(level, slice) - 127; for (int i = 0; i < h; i++) { for (int j = 0; j < (w*ncomp); j++) *(ptr++) = val; val += (i & 1) ? -128 : 128; } return ptr; } static void * encode_UNSIGNED_INT(void *buf, int ncomp, int w, int h, int level, int slice) { uint32_t *ptr = buf; uint32_t val = enc_ls(level, slice); for (int i = 0; i < h; i++) { for (int j = 0; j < (w*ncomp); j++) *(ptr++) = val; val += (i & 1) ? -128 : 128; } return ptr; } static struct type_info { const char *unpack; const char *convert; GLenum type; void * (*encode)(void *buf, int ncomp, int w, int h, int level, int slice); } type_info[] = { #define _TYPE(_name, _unpack, _convert, _type) \ [_name] = { _unpack, _convert, GL_ ## _type, encode_ ## _type } /* for the simple types that just encode value in .r channel: */ #define STYPE(_name, _convert, _type) _TYPE(_name, "color.r", _convert, _type) STYPE(SNORM, "(val + 1.0) * 127.0", BYTE), STYPE(UNORM, "val * 255.0", UNSIGNED_BYTE), STYPE(FLOAT, "val * 255.0", FLOAT), STYPE(SINT8, "val + 127", BYTE), STYPE(UINT8, "val", UNSIGNED_BYTE), STYPE(SINT16, "val + 127", SHORT), STYPE(UINT16, "val", UNSIGNED_SHORT), STYPE(SINT32, "val + 127", INT), STYPE(UINT32, "val", UNSIGNED_INT), }; static const struct fmt { const char *name; GLenum ifmt; /* sized internal format */ GLenum ufmt; /* unsized format */ enum type type; } fmts[] = { #define FMT(name, ufmt, t) { #name, GL_##name, ufmt, t } FMT(R8, GL_RED, UNORM), FMT(R8UI, GL_RED_INTEGER, UINT8 ), FMT(R8I, GL_RED_INTEGER, SINT8 ), FMT(R16UI, GL_RED_INTEGER, UINT16), FMT(R16I, GL_RED_INTEGER, SINT16), FMT(R32UI, GL_RED_INTEGER, UINT32), FMT(R32I, GL_RED_INTEGER, SINT32), FMT(RG8, GL_RG, UNORM ), FMT(RG8UI, GL_RG_INTEGER, UINT8 ), FMT(RG8I, GL_RG_INTEGER, SINT8 ), FMT(RG16UI, GL_RG_INTEGER, UINT16), FMT(RG16I, GL_RG_INTEGER, SINT16), FMT(RG32UI, GL_RG_INTEGER, UINT32), FMT(RG32I, GL_RG_INTEGER, SINT32), FMT(RGB8, GL_RGB, UNORM ), // FMT(RGB565, GL_RGB, UNORM565), FMT(RGBA8, GL_RGBA, UNORM ), // FMT(RGB5_A1, GL_RGBA, UINT5551), // FMT(RGBA4, GL_RGBA, UINT4444), // FMT(RGB10_A2, GL_RGBA, UINT10A2), FMT(RGBA8UI, GL_RGBA_INTEGER, UINT8 ), FMT(RGBA8I, GL_RGBA_INTEGER, SINT8 ), FMT(RGBA16UI, GL_RGBA_INTEGER, UINT16), FMT(RGBA16I, GL_RGBA_INTEGER, SINT16), FMT(RGBA32I, GL_RGBA_INTEGER, SINT32), FMT(RGBA32UI, GL_RGBA_INTEGER, UINT32), /* Not required to be color renderable: */ FMT(R8_SNORM, GL_RED, SNORM ), FMT(R16F, GL_RED, FLOAT ), FMT(R32F, GL_RED, FLOAT ), FMT(RG8_SNORM, GL_RG, SNORM ), FMT(RG16F, GL_RG, FLOAT ), FMT(RG32F, GL_RG, FLOAT ), FMT(SRGB8, GL_RGB, UNORM ), FMT(RGB8_SNORM, GL_RGB, SNORM ), FMT(R11F_G11F_B10F, GL_RGB, FLOAT ), FMT(RGB9_E5, GL_RGB, FLOAT ), FMT(RGB16F, GL_RGB, FLOAT ), FMT(RGB32F, GL_RGB, FLOAT ), FMT(RGB8UI, GL_RGB_INTEGER, UINT8 ), FMT(RGB8I, GL_RGB_INTEGER, SINT8 ), FMT(RGB16UI, GL_RGB_INTEGER, UINT16), FMT(RGB16I, GL_RGB_INTEGER, SINT16), FMT(RGB32UI, GL_RGB_INTEGER, UINT32), FMT(RGB32I, GL_RGB_INTEGER, SINT32), FMT(RGBA8_SNORM, GL_RGBA, UNORM ), FMT(RGBA16F, GL_RGBA, FLOAT ), FMT(RGBA32F, GL_RGBA, FLOAT ), FMT(DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, UINT16), FMT(DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, UINT32), FMT(DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, FLOAT ), // FMT(DEPTH24_STENCIL8, GL_DEPTH_STENCIL, UINTZ24S8), // FMT(DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, FLOATZ32UINTX24S8), }; static const struct fmt *fmt; /* * Shaders: */ static const char *get_prefix(void) { switch(fmt->type) { case SINT8: case SINT16: case SINT32: return "i"; case UINT8: case UINT16: case UINT32: return "u"; default: return ""; } } static const char *get_sampler(void) { static char buf[32]; const char *stype; if (target == GL_TEXTURE_2D) { stype = "2D"; } else if (target == GL_TEXTURE_2D_ARRAY) { stype = "2DArray"; } else if (target == GL_TEXTURE_3D) { stype = "3D"; } else { assert(!"bad mode!"); } sprintf(buf, "%ssampler%s", get_prefix(), stype); return buf; } #define IN_POSITION 0 #define IN_TEXCOORD 1 const char *vertex_shader_source = "#version 300 es \n" "in vec4 in_position; \n" "in vec4 in_texcoord; \n" "out vec4 v_texcoord; \n" "void main() \n" "{ \n" " v_texcoord = in_texcoord; \n" " gl_Position = in_position; \n" "} \n"; const char *fragment_shader_fmt = "#version 300 es \n" "#define ivec1 int \n" "#define uvec1 uint \n" "#define vec1 float \n" "precision highp float; \n" "precision highp int; \n" "in vec4 v_texcoord; \n" "uniform highp %1$s tex; \n" "out vec4 fragColor; \n" "void main() \n" "{ \n" " int lod = int(v_texcoord.w); \n" " %2$svec4 color = texelFetch(tex, ivec%3$d(v_texcoord), lod);\n" " %2$svec1 val = %4$s; \n" " int converted = int(%5$s); \n" " fragColor.rgb = vec3( \n" " float((converted >> 0) & 0x7) / 8.0, \n" " float((converted >> 3) & 0xf) / 16.0, \n" " float((converted >> 7) & 0x1) * 0.25 \n" " ); \n" " fragColor.a = 1.0; \n" "} \n"; static const char *get_fs(void) { static char buf[4096]; int ncoord = (target == GL_TEXTURE_2D) ? 2 : 3; unsigned n; n = sprintf(buf, fragment_shader_fmt, get_sampler(), get_prefix(), ncoord, type_info[fmt->type].unpack, type_info[fmt->type].convert); assert(n < sizeof(buf) - 1); return buf; } static void upload_texture(void) { int ncomp = get_ncomp(fmt->ufmt); char texbuf[size.x * size.y * size.z * ncomp * 16]; struct type_info *ti = &type_info[fmt->type]; glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); for (int m = 0; m < miplevels; m++) { int w = u_minify(size.x, m); int h = u_minify(size.y, m); /* size in 3rd dim minifies for 3D but not 2D_ARRAY: */ int slices = is_array(target) ? size.z : u_minify(size.z, m); void *ptr = texbuf; for (int s = 0; s < slices; s++) { ptr = ti->encode(ptr, ncomp, w, h, m, s); } switch (target) { case GL_TEXTURE_2D: glTexImage2D(target, m, fmt->ifmt, w, h, 0, fmt->ufmt, ti->type, texbuf); break; case GL_TEXTURE_3D: case GL_TEXTURE_2D_ARRAY: glTexImage3D(target, m, fmt->ifmt, w, h, slices, 0, fmt->ufmt, ti->type, texbuf); break; default: assert(!"bad target"); } } } static int tex_handle; static unsigned tex; static void setup_gl(void) { int prog, ret; prog = create_program(vertex_shader_source, get_fs()); assert(prog >= 0); glBindAttribLocation(prog, IN_POSITION, "in_position"); glBindAttribLocation(prog, IN_TEXCOORD, "in_texcoord"); ret = link_program(prog); assert(ret == 0); glUseProgram(prog); glViewport(0, 0, gbm->width, gbm->height); glGenTextures(1, &tex); tex_handle = glGetUniformLocation(prog, "tex"); } static void update_texture(void) { /* calculate # of miplevels: */ int max_dim; if (target == GL_TEXTURE_3D) max_dim = MAX3(size.x, size.y, size.z); else max_dim = MAX2(size.x, size.y); miplevels = (int) log2f(max_dim) + 1; glDeleteTextures(1, &tex); glGenTextures(1, &tex); glActiveTexture(GL_TEXTURE0); glBindTexture(target, tex); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); upload_texture(); glUniform1i(tex_handle, 0); /* '0' refers to texture unit 0. */ } static void draw_quad(float x, float y, float w, float h, float tw, float th, int m, int s) { float in_position[4][4]; float in_texcoord[4][4]; /* convert from 0..1 to -1..1: */ x = (x * 2.0) - 1.0; y = (y * 2.0) - 1.0; w *= 2.0; h *= 2.0; in_position[0][0] = x; in_position[0][1] = y; in_position[0][2] = 0.0; in_position[0][3] = 1.0; in_position[1][0] = x + w; in_position[1][1] = y; in_position[1][2] = 0.0; in_position[1][3] = 1.0; in_position[2][0] = x; in_position[2][1] = y + h; in_position[2][2] = 0.0; in_position[2][3] = 1.0; in_position[3][0] = x + w; in_position[3][1] = y + h; in_position[3][2] = 0.0; in_position[3][3] = 1.0; in_texcoord[0][0] = 0.0; in_texcoord[0][1] = 0.0; in_texcoord[0][2] = s; in_texcoord[0][3] = m; in_texcoord[1][0] = tw; in_texcoord[1][1] = 0.0; in_texcoord[1][2] = s; in_texcoord[1][3] = m; in_texcoord[2][0] = 0.0; in_texcoord[2][1] = th; in_texcoord[2][2] = s; in_texcoord[2][3] = m; in_texcoord[3][0] = tw; in_texcoord[3][1] = th; in_texcoord[3][2] = s; in_texcoord[3][3] = m; glVertexAttribPointer(IN_POSITION, 4, GL_FLOAT, GL_FALSE, 0, in_position); glEnableVertexAttribArray(IN_POSITION); glVertexAttribPointer(IN_TEXCOORD, 4, GL_FLOAT, GL_FALSE, 0, in_texcoord); glEnableVertexAttribArray(IN_TEXCOORD); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } static void extract_pix(uint8_t *rgba, int *slice, int *level, bool *complemented) { *slice = (((float)rgba[0]) / 255.0) * 8.0; *level = (((float)rgba[1]) / 255.0) * 16.0; *complemented = (((float)rgba[2]) / 255.0) / 0.25 != 0.0f; } static bool probe_pix(int x, int y, int w, int h, int s, int m) { uint32_t rgba[w*h], *ptr; bool err = false; glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, rgba); ptr = rgba; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { int slice, level; bool complemented; extract_pix((void *)(ptr++), &slice, &level, &complemented); if ((slice != s) || (level != m) || (complemented != ((i / zoom) & 1))) { printf("%ux%ux%u:%s: error at: S:L:C=%d:%d:%d, got %d:%d:%d at pix %d,%d (of %dx%d)\n", size.x, size.y, size.z, fmt->name, s, m, ((i / zoom) & 1), slice, level, complemented, j, i, w, h); err = true; if (!full) return err; } } } return err; } static bool check_quads(void) { const int pad = 2; float y = pad; bool err = false; /* draw quads for each level/slice: */ for (int m = 0; m < miplevels; m++) { float w = u_minify(size.x, m); float h = u_minify(size.y, m); /* size in 3rd dim minifies for 3D but not 2D_ARRAY: */ int slices = is_array(target) ? size.z : u_minify(size.z, m); float x = pad; for (int s = 0; s < slices; s++) { int rx = x * zoom; int ry = y * zoom; if ((rx + w * zoom >= gbm->width) || (ry + h * zoom >= gbm->height)) continue; err |= probe_pix(rx, ry, w*zoom, h*zoom, s, m); x += size.x + pad; } y += h + pad; } return err; } #ifdef HAVE_LIBPNG static void write_png_file(char *filename, int width, int height, uint8_t *buffer) { FILE *fp = fopen(filename, "wb"); png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); png_infop info = png_create_info_struct(png); png_init_io(png, fp); png_set_IHDR( png, info, width, height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE ); png_write_info(png, info); png_bytepp rows = (png_bytepp)png_malloc(png, height * sizeof(png_bytep)); for (int i = 0; i < height; i++) rows[i] = (png_bytep)(buffer + (height - i - 1) * width * 4); png_write_image(png, rows); png_write_end(png, NULL); fclose(fp); png_destroy_write_struct(&png, &info); free(rows); } #endif static bool needs_check = true; static void draw_and_check_quads(unsigned frame) { (void)frame; update_texture(); if (needs_check) printf("Testing %dx%dx%d:%s\n", size.x, size.y, size.z, fmt->name); /* clear the color buffer */ glClearColor(0.5, 0.5, 0.5, 1.0); glClear(GL_COLOR_BUFFER_BIT); float sw = gbm->width; float sh = gbm->height; sw /= zoom; sh /= zoom; const int pad = 2; float y = pad; /* draw quads for each level/slice: */ for (int m = 0; m < miplevels; m++) { float w = u_minify(size.x, m); float h = u_minify(size.y, m); /* size in 3rd dim minifies for 3D but not 2D_ARRAY: */ int slices = is_array(target) ? size.z : u_minify(size.z, m); float x = pad; for (int s = 0; s < slices; s++) { draw_quad(x/sw, y/sh, w/sw, h/sh, w, h, m, s); x += size.x + pad; } y += h + pad; } if (needs_check) { glFlush(); bool err = check_quads(); if (err) error_frames++; needs_check = false; #ifdef HAVE_LIBPNG if (png) { uint8_t rgba[gbm->width*gbm->height*4]; glReadPixels(0, 0, gbm->width, gbm->height, GL_RGBA, GL_UNSIGNED_BYTE, rgba); static char buf[64]; sprintf(buf, "kmscube-texturator-%dx%dx%d:%s.png", size.x, size.y, size.z, fmt->name); write_png_file(buf, gbm->width, gbm->height, rgba); } #endif } /* if we've hit max # of error frames, stop growing: */ if (error_frames >= max_error_frames) goto bail; if ((size.x < maxsz.x) || (size.y < maxsz.y) || (size.z < maxsz.z)) { /* Increase width first, then height, then depth: */ size.x++; if (size.x > maxsz.x) { size.x = minsz.x; size.y++; } if (size.y > maxsz.y) { size.x = minsz.x; size.y = minsz.y; size.z++; } assert(size.z <= maxsz.z); needs_check = true; return; } bail: if (stop) { printf("Exiting with %d errors\n", error_frames); exit((error_frames > 0) ? -1 : 0); } } static void print_summary(void) { printf("testing %s %s at %ux%ux%u-%ux%ux%u with %dx zoom\n", fmt->name, get_sampler(), minsz.x, minsz.y, minsz.z, maxsz.x, maxsz.y, maxsz.z, zoom); printf("VS:\n%s\n", vertex_shader_source); printf("FS:\n%s\n", get_fs()); } static const char *shortopts = "D:e:fsv:z"; static const struct option longopts[] = { {"device", required_argument, 0, 'D'}, {"errors", required_argument, 0, 'e'}, {"full", no_argument, 0, 'f'}, {"stop", no_argument, 0, 's'}, {"vmode", required_argument, 0, 'v'}, {"zoom", no_argument, 0, 'z'}, #ifdef HAVE_LIBPNG {"png", no_argument, 0, 'p'}, #endif {0, 0, 0, 0} }; static void usage(const char *name) { printf("Usage: %1$s [-Dvz] []\n" "\n" "options:\n" " -D, --device=DEVICE use the given device\n" " -e, --errors=N stop after N frames with errors (default 5)\n" " -f, --full check all pixels (do not stop after first faulty pixel)\n" " -s, --stop exit after testing all sizes\n" " -v, --vmode=VMODE specify the video mode in the format\n" " [-]\n" " -z, --zoom increase zoom (can be specified multiple times)\n" #ifdef HAVE_LIBPNG " -p, --png capture the screen to a png image\n" #endif "\n" "where:\n" " is one of 2D/2DArray/3D\n" " is a GL sized internal-format without GL_ prefix\n" " is XxY (2D) or XxYxZ (2DArray/3D)\n" "\n" "example:\n" " %1$s -z 3D RG16UI 37x65x4\n" , name); exit(-1); } static void parse_dims(const char *argv0, const char *sizestr, struct size *size) { if (target == GL_TEXTURE_2D) { if (sscanf(sizestr, "%ux%ux", &size->x, &size->y) != 2) { printf("invalid size: %s\n", sizestr); usage(argv0); } size->z = 1; } else { if (sscanf(sizestr, "%ux%ux%ux", &size->x, &size->y, &size->z) != 3) { printf("invalid size: %s\n", sizestr); usage(argv0); } } } int main(int argc, char *argv[]) { const char *device = "/dev/dri/card0"; char mode_str[DRM_DISPLAY_MODE_LEN] = ""; char *p; int ret, opt; unsigned int len; unsigned int vrefresh = 0; while ((opt = getopt_long_only(argc, argv, shortopts, longopts, NULL)) != -1) { switch (opt) { case 'D': device = optarg; break; case 'e': max_error_frames = atoi(optarg); break; case 'f': full = true; break; case 's': stop = true; break; case 'v': p = strchr(optarg, '-'); if (p == NULL) { len = strlen(optarg); } else { vrefresh = strtoul(p + 1, NULL, 0); len = p - optarg; } if (len > sizeof(mode_str) - 1) len = sizeof(mode_str) - 1; strncpy(mode_str, optarg, len); mode_str[len] = '\0'; break; case 'z': zoom++; break; #ifdef HAVE_LIBPNG case 'p': png = true; break; #endif default: usage(argv[0]); } } if ((optind + 2) >= argc) { usage(argv[0]); } /* parse remaining args: */ const char *targetstr = argv[optind + 0]; const char *fmtstr = argv[optind + 1]; const char *minstr = argv[optind + 2]; const char *maxstr = ((optind + 3) < argc) ? argv[optind + 3] : NULL; if (!strcmp(targetstr, "2D")) { target = GL_TEXTURE_2D; } else if (!strcmp(targetstr, "2DArray")) { target = GL_TEXTURE_2D_ARRAY; } else if (!strcmp(targetstr, "3D")) { target = GL_TEXTURE_3D; } else { printf("invalid target: %s\n", targetstr); usage(argv[0]); } for (unsigned i = 0; i < ARRAY_SIZE(fmts); i++) { if (!strcmp(fmtstr, fmts[i].name)) { fmt = &fmts[i]; break; } } if (!fmt) { printf("invalid format: %s\n", fmtstr); usage(argv[0]); } parse_dims(argv[0], minstr, &minsz); if (maxstr) { parse_dims(argv[0], maxstr, &maxsz); } else { maxsz = minsz; } size = minsz; print_summary(); /* no real need for atomic here: */ drm = init_drm_legacy(device, mode_str, vrefresh, ~0); if (!drm) { printf("failed to initialize DRM\n"); return -1; } gbm = init_gbm(drm->fd, drm->mode->hdisplay, drm->mode->vdisplay, DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, false); if (!gbm) { printf("failed to initialize GBM\n"); return -1; } ret = init_egl(&_egl, gbm, 0); if (ret) { printf("failed to initialize EGL\n"); return -1; } _egl.draw = draw_and_check_quads; setup_gl(); return drm->run(gbm, egl); }