summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Clark <robdclark@gmail.com>2019-01-06 11:34:24 -0500
committerRob Clark <robdclark@gmail.com>2019-05-04 16:35:43 +0000
commit1e82f507fe5469fdc03a2d56c63e2f422fabd5ba (patch)
tree94942af4a4f230bfd856a0f89e8d0a80e04da402
parent572b93d7080f1c089b8c236ad0bc932859e2712d (diff)
downloadkmscube-1e82f507fe5469fdc03a2d56c63e2f422fabd5ba.tar.gz
Add texturator
A utility for testing layouts of various types of textures in various formats. Not really too much related to kms or kmscube, other than re-using kmscube's kms/gbm code and egl helper.
-rw-r--r--Makefile.am11
-rw-r--r--common.h14
-rw-r--r--meson.build8
-rw-r--r--texturator.c867
4 files changed, 899 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am
index a36087d..ba064e4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -21,7 +21,7 @@
# SOFTWARE.
#
-bin_PROGRAMS = kmscube
+bin_PROGRAMS = kmscube texturator
kmscube_LDADD = \
$(DRM_LIBS) \
@@ -59,3 +59,12 @@ kmscube_LDADD += $(GST_LIBS)
kmscube_CFLAGS += $(GST_CFLAGS)
kmscube_SOURCES += cube-video.c gst-decoder.c
endif
+
+texturator_LDADD = $(kmscube_LDADD)
+texturator_CFLAGS = $(kmscube_CFLAGS)
+texturator_SOURCES = \
+ common.c \
+ common.h \
+ drm-common.c \
+ drm-legacy.c \
+ texturator.c
diff --git a/common.h b/common.h
index 17c4073..d461262 100644
--- a/common.h
+++ b/common.h
@@ -24,7 +24,9 @@
#ifndef _COMMON_H
#define _COMMON_H
+#ifndef GL_ES_VERSION_2_0
#include <GLES2/gl2.h>
+#endif
#include <GLES2/gl2ext.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
@@ -35,6 +37,18 @@
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+/* from mesa's util/macros.h: */
+#define MIN2( A, B ) ( (A)<(B) ? (A) : (B) )
+#define MAX2( A, B ) ( (A)>(B) ? (A) : (B) )
+#define MIN3( A, B, C ) ((A) < (B) ? MIN2(A, C) : MIN2(B, C))
+#define MAX3( A, B, C ) ((A) > (B) ? MAX2(A, C) : MAX2(B, C))
+
+static inline unsigned
+u_minify(unsigned value, unsigned levels)
+{
+ return MAX2(1, value >> levels);
+}
+
#ifndef DRM_FORMAT_MOD_LINEAR
#define DRM_FORMAT_MOD_LINEAR 0
#endif
diff --git a/meson.build b/meson.build
index 8f1eed9..4b27372 100644
--- a/meson.build
+++ b/meson.build
@@ -87,3 +87,11 @@ else
endif
executable('kmscube', sources, dependencies : dep_common, install : true)
+
+
+executable('texturator', files(
+ 'common.c',
+ 'drm-legacy.c',
+ 'drm-common.c',
+ 'texturator.c',
+), dependencies : dep_common, install : true)
diff --git a/texturator.c b/texturator.c
new file mode 100644
index 0000000..8867e75
--- /dev/null
+++ b/texturator.c
@@ -0,0 +1,867 @@
+/*
+ * Copyright (c) 2011 Intel Corporation
+ * Copyright (c) 2019 Rob Clark <robdclark@gmail.com>
+ *
+ * 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 <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+
+#include <GLES3/gl3.h>
+#include <GLES3/gl3ext.h>
+
+#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 layed 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;
+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 << 4) & 0xf0) | (slice & 0xf);
+}
+
+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 < (w*h*ncomp); i++)
+ *(ptr++) = val;
+
+ 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 < (w*h*ncomp); i++)
+ *(ptr++) = val;
+
+ return ptr;
+}
+
+static void * encode_FLOAT(void *buf, int ncomp, int w, int h, int level, int slice)
+{
+ float *ptr = buf;
+ float val = ((float)enc_ls(level, slice)) / 255.0;
+
+ for (int i = 0; i < (w*h*ncomp); i++)
+ *(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 < (w*h*ncomp); i++)
+ *(ptr++) = val;
+
+ 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 < (w*h*ncomp); i++)
+ *(ptr++) = val;
+
+ 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 < (w*h*ncomp); i++)
+ *(ptr++) = val;
+
+ 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 < (w*h*ncomp); i++)
+ *(ptr++) = val;
+
+ 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.rg = vec2( \n"
+ " float((converted >> 0) & 0xf) / 16.0, \n"
+ " float((converted >> 4) & 0xf) / 16.0 \n"
+ " ); \n"
+ " fragColor.ba = vec2(0.0, 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)
+{
+ *slice = (((float)rgba[0]) / 255.0) * 16.0;
+ *level = (((float)rgba[1]) / 255.0) * 16.0;
+}
+
+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;
+
+ extract_pix((void *)(ptr++), &slice, &level);
+ if ((slice != s) || (level != m)) {
+ printf("%ux%ux%u:%s: error at: S:L=%d:%d, got %d:%d at pix %d,%d (of %dx%d)\n",
+ size.x, size.y, size.z, fmt->name,
+ s, m, slice, level, 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 >= gbm->width) || (ry >= gbm->height))
+ continue;
+
+ err |= probe_pix(rx, ry, w*zoom, h*zoom, s, m);
+
+ x += size.x + pad;
+ }
+
+ y += h + pad;
+ }
+
+ return err;
+}
+
+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;
+ }
+
+ /* 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:fsz";
+
+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'},
+ {"zoom", no_argument, 0, 'z'},
+ {0, 0, 0, 0}
+};
+
+static void usage(const char *name)
+{
+ printf("Usage: %1$s [-Dz] <target> <format> <minsize> [<maxsize>]\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"
+ " -z, --zoom increase zoom (can be specified multiple times)\n"
+ "\n"
+ "where:\n"
+ " <target> is one of 2D/2DArray/3D\n"
+ " <format> is a GL sized internal-format without GL_ prefix\n"
+ " <size> 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";
+ int ret, opt;
+
+ 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 'z':
+ zoom++;
+ break;
+ 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);
+ if (!drm) {
+ printf("failed to initialize DRM\n");
+ return -1;
+ }
+
+ gbm = init_gbm(drm->fd, drm->mode->hdisplay, drm->mode->vdisplay,
+ DRM_FORMAT_MOD_LINEAR);
+ 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);
+}