diff -Nru kmscube-0.0.0~git20170617/autogen.sh kmscube-0.0.0~git20210103/autogen.sh --- kmscube-0.0.0~git20170617/autogen.sh 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/autogen.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -#! /bin/sh - -srcdir=`dirname "$0"` -test -z "$srcdir" && srcdir=. - -ORIGDIR=`pwd` -cd "$srcdir" - -git config --local --get format.subjectPrefix >/dev/null || - git config --local format.subjectPrefix "PATCH kmscube" 2>/dev/null - -git config --local --get sendemail.to >/dev/null || - git config --local sendemail.to "mesa-dev@lists.freedesktop.org" 2>/dev/null - -autoreconf --force --verbose --install || exit 1 -cd "$ORIGDIR" || exit $? - -if test -z "$NOCONFIGURE"; then - exec "$srcdir"/configure "$@" -fi diff -Nru kmscube-0.0.0~git20170617/common.c kmscube-0.0.0~git20210103/common.c --- kmscube-0.0.0~git20170617/common.c 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/common.c 2020-09-02 16:50:08.000000000 +0000 @@ -22,12 +22,15 @@ * DEALINGS IN THE SOFTWARE. */ +#include #include #include #include #include #include #include +#include +#include #include "common.h" @@ -39,15 +42,59 @@ uint32_t format, const uint64_t *modifiers, const unsigned int count); +WEAK struct gbm_bo * +gbm_bo_create_with_modifiers(struct gbm_device *gbm, + uint32_t width, uint32_t height, + uint32_t format, + const uint64_t *modifiers, + const unsigned int count); -const struct gbm * init_gbm(int drm_fd, int w, int h, uint64_t modifier) +static struct gbm_bo * init_bo(uint64_t modifier) { - gbm.dev = gbm_create_device(drm_fd); - gbm.format = GBM_FORMAT_XRGB8888; - gbm.surface = NULL; + struct gbm_bo *bo = NULL; + + if (gbm_bo_create_with_modifiers) { + bo = gbm_bo_create_with_modifiers(gbm.dev, + gbm.width, gbm.height, + gbm.format, + &modifier, 1); + } + + if (!bo) { + if (modifier != DRM_FORMAT_MOD_LINEAR) { + fprintf(stderr, "Modifiers requested but support isn't available\n"); + return NULL; + } + + bo = gbm_bo_create(gbm.dev, + gbm.width, gbm.height, + gbm.format, + GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + } + + if (!bo) { + printf("failed to create gbm bo\n"); + return NULL; + } + + return bo; +} +static struct gbm * init_surfaceless(uint64_t modifier) +{ + for (unsigned i = 0; i < ARRAY_SIZE(gbm.bos); i++) { + gbm.bos[i] = init_bo(modifier); + if (!gbm.bos[i]) + return NULL; + } + return &gbm; +} + +static struct gbm * init_surface(uint64_t modifier) +{ if (gbm_surface_create_with_modifiers) { - gbm.surface = gbm_surface_create_with_modifiers(gbm.dev, w, h, + gbm.surface = gbm_surface_create_with_modifiers(gbm.dev, + gbm.width, gbm.height, gbm.format, &modifier, 1); @@ -58,7 +105,8 @@ fprintf(stderr, "Modifiers requested but support isn't available\n"); return NULL; } - gbm.surface = gbm_surface_create(gbm.dev, w, h, + gbm.surface = gbm_surface_create(gbm.dev, + gbm.width, gbm.height, gbm.format, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); @@ -69,10 +117,23 @@ return NULL; } + return &gbm; +} + +const struct gbm * init_gbm(int drm_fd, int w, int h, uint32_t format, + uint64_t modifier, bool surfaceless) +{ + gbm.dev = gbm_create_device(drm_fd); + gbm.format = format; + gbm.surface = NULL; + gbm.width = w; gbm.height = h; - return &gbm; + if (surfaceless) + return init_surfaceless(modifier); + + return init_surface(modifier); } static bool has_ext(const char *extension_list, const char *ext) @@ -161,6 +222,82 @@ return true; } +static bool +create_framebuffer(const struct egl *egl, struct gbm_bo *bo, + struct framebuffer *fb) { + assert(egl->eglCreateImageKHR); + assert(bo); + assert(fb); + + // 1. Create EGLImage. + int fd = gbm_bo_get_fd(bo); + if (fd < 0) { + printf("failed to get fd for bo: %d\n", fd); + return false; + } + + EGLint khr_image_attrs[17] = { + EGL_WIDTH, gbm_bo_get_width(bo), + EGL_HEIGHT, gbm_bo_get_height(bo), + EGL_LINUX_DRM_FOURCC_EXT, (int)gbm_bo_get_format(bo), + EGL_DMA_BUF_PLANE0_FD_EXT, fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, + EGL_DMA_BUF_PLANE0_PITCH_EXT, gbm_bo_get_stride(bo), + EGL_NONE, EGL_NONE, /* modifier lo */ + EGL_NONE, EGL_NONE, /* modifier hi */ + EGL_NONE, + }; + + if (egl->modifiers_supported) { + const uint64_t modifier = gbm_bo_get_modifier(bo); + if (modifier != DRM_FORMAT_MOD_LINEAR) { + size_t attrs_index = 12; + khr_image_attrs[attrs_index++] = + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT; + khr_image_attrs[attrs_index++] = modifier & 0xfffffffful; + khr_image_attrs[attrs_index++] = + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT; + khr_image_attrs[attrs_index++] = modifier >> 32; + } + } + + fb->image = egl->eglCreateImageKHR(egl->display, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, NULL /* no client buffer */, + khr_image_attrs); + + if (fb->image == EGL_NO_IMAGE_KHR) { + printf("failed to make image from buffer object\n"); + return false; + } + + // EGLImage takes the fd ownership. + close(fd); + + // 2. Create GL texture and framebuffer. + glGenTextures(1, &fb->tex); + glBindTexture(GL_TEXTURE_2D, fb->tex); + egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, fb->image); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + + glGenFramebuffers(1, &fb->fb); + glBindFramebuffer(GL_FRAMEBUFFER, fb->fb); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + fb->tex, 0); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + printf("failed framebuffer check for created target buffer\n"); + glDeleteFramebuffers(1, &fb->fb); + glDeleteTextures(1, &fb->tex); + return false; + } + + return true; +} + int init_egl(struct egl *egl, const struct gbm *gbm, int samples) { EGLint major, minor; @@ -252,11 +389,15 @@ return -1; } - egl->surface = eglCreateWindowSurface(egl->display, egl->config, - (EGLNativeWindowType)gbm->surface, NULL); - if (egl->surface == EGL_NO_SURFACE) { - printf("failed to create egl surface\n"); - return -1; + if (!gbm->surface) { + egl->surface = EGL_NO_SURFACE; + } else { + egl->surface = eglCreateWindowSurface(egl->display, egl->config, + (EGLNativeWindowType)gbm->surface, NULL); + if (egl->surface == EGL_NO_SURFACE) { + printf("failed to create egl surface\n"); + return -1; + } } /* connect the context to the surface */ @@ -273,6 +414,27 @@ get_proc_gl(GL_OES_EGL_image, glEGLImageTargetTexture2DOES); + get_proc_gl(GL_AMD_performance_monitor, glGetPerfMonitorGroupsAMD); + get_proc_gl(GL_AMD_performance_monitor, glGetPerfMonitorCountersAMD); + get_proc_gl(GL_AMD_performance_monitor, glGetPerfMonitorGroupStringAMD); + get_proc_gl(GL_AMD_performance_monitor, glGetPerfMonitorCounterStringAMD); + get_proc_gl(GL_AMD_performance_monitor, glGetPerfMonitorCounterInfoAMD); + get_proc_gl(GL_AMD_performance_monitor, glGenPerfMonitorsAMD); + get_proc_gl(GL_AMD_performance_monitor, glDeletePerfMonitorsAMD); + get_proc_gl(GL_AMD_performance_monitor, glSelectPerfMonitorCountersAMD); + get_proc_gl(GL_AMD_performance_monitor, glBeginPerfMonitorAMD); + get_proc_gl(GL_AMD_performance_monitor, glEndPerfMonitorAMD); + get_proc_gl(GL_AMD_performance_monitor, glGetPerfMonitorCounterDataAMD); + + if (!gbm->surface) { + for (unsigned i = 0; i < ARRAY_SIZE(gbm->bos); i++) { + if (!create_framebuffer(egl, gbm->bos[i], &egl->fbs[i])) { + printf("failed to create framebuffer\n"); + return -1; + } + } + } + return 0; } @@ -296,6 +458,7 @@ log = malloc(ret); glGetShaderInfoLog(vertex_shader, ret, NULL, log); printf("%s", log); + free(log); } return -1; @@ -317,6 +480,7 @@ log = malloc(ret); glGetShaderInfoLog(fragment_shader, ret, NULL, log); printf("%s", log); + free(log); } return -1; @@ -347,6 +511,7 @@ log = malloc(ret); glGetProgramInfoLog(program, ret, NULL, log); printf("%s", log); + free(log); } return -1; @@ -354,3 +519,10 @@ return 0; } + +int64_t get_time_ns(void) +{ + struct timespec tv; + clock_gettime(CLOCK_MONOTONIC, &tv); + return tv.tv_nsec + tv.tv_sec * NSEC_PER_SEC; +} diff -Nru kmscube-0.0.0~git20170617/common.h kmscube-0.0.0~git20210103/common.h --- kmscube-0.0.0~git20170617/common.h 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/common.h 2020-09-02 16:50:08.000000000 +0000 @@ -24,7 +24,9 @@ #ifndef _COMMON_H #define _COMMON_H +#ifndef GL_ES_VERSION_2_0 #include +#endif #include #include #include @@ -33,6 +35,20 @@ #include #include +#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 @@ -58,6 +74,10 @@ #endif #endif /* EGL_EXT_platform_base */ +#ifndef EGL_VERSION_1_5 +#define EGLImage EGLImageKHR +#endif /* EGL_VERSION_1_5 */ + #define WEAK __attribute__((weak)) /* Define tokens from EGL_EXT_image_dma_buf_import_modifiers */ @@ -76,21 +96,30 @@ #define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A #endif +#define NUM_BUFFERS 2 + struct gbm { struct gbm_device *dev; struct gbm_surface *surface; + struct gbm_bo *bos[NUM_BUFFERS]; /* for the surfaceless case */ uint32_t format; int width, height; }; -const struct gbm * init_gbm(int drm_fd, int w, int h, uint64_t modifier); +const struct gbm * init_gbm(int drm_fd, int w, int h, uint32_t format, uint64_t modifier, bool surfaceless); +struct framebuffer { + EGLImageKHR image; + GLuint tex; + GLuint fb; +}; struct egl { EGLDisplay display; EGLConfig config; EGLContext context; EGLSurface surface; + struct framebuffer fbs[NUM_BUFFERS]; /* for the surfaceless case */ PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT; PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; @@ -102,6 +131,19 @@ PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR; PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID; + /* AMD_performance_monitor */ + PFNGLGETPERFMONITORGROUPSAMDPROC glGetPerfMonitorGroupsAMD; + PFNGLGETPERFMONITORCOUNTERSAMDPROC glGetPerfMonitorCountersAMD; + PFNGLGETPERFMONITORGROUPSTRINGAMDPROC glGetPerfMonitorGroupStringAMD; + PFNGLGETPERFMONITORCOUNTERSTRINGAMDPROC glGetPerfMonitorCounterStringAMD; + PFNGLGETPERFMONITORCOUNTERINFOAMDPROC glGetPerfMonitorCounterInfoAMD; + PFNGLGENPERFMONITORSAMDPROC glGenPerfMonitorsAMD; + PFNGLDELETEPERFMONITORSAMDPROC glDeletePerfMonitorsAMD; + PFNGLSELECTPERFMONITORCOUNTERSAMDPROC glSelectPerfMonitorCountersAMD; + PFNGLBEGINPERFMONITORAMDPROC glBeginPerfMonitorAMD; + PFNGLENDPERFMONITORAMDPROC glEndPerfMonitorAMD; + PFNGLGETPERFMONITORCOUNTERDATAAMDPROC glGetPerfMonitorCounterDataAMD; + bool modifiers_supported; void (*draw)(unsigned i); @@ -128,10 +170,12 @@ NV12_2IMG, /* NV12, handled as two textures and converted to RGB in shader */ NV12_1IMG, /* NV12, imported as planar YUV eglimg */ VIDEO, /* video textured cube */ + SHADERTOY, /* display shadertoy shader */ }; const struct egl * init_cube_smooth(const struct gbm *gbm, int samples); const struct egl * init_cube_tex(const struct gbm *gbm, enum mode mode, int samples); +const struct egl * init_cube_shadertoy(const struct gbm *gbm, const char *shadertoy, int samples); #ifdef HAVE_GST @@ -152,4 +196,16 @@ } #endif +void init_perfcntrs(const struct egl *egl, const char *perfcntrs); +void start_perfcntrs(void); +void end_perfcntrs(void); +void finish_perfcntrs(void); +void dump_perfcntrs(unsigned nframes, uint64_t elapsed_time_ns); + +#define NSEC_PER_SEC (INT64_C(1000) * USEC_PER_SEC) +#define USEC_PER_SEC (INT64_C(1000) * MSEC_PER_SEC) +#define MSEC_PER_SEC INT64_C(1000) + +int64_t get_time_ns(void); + #endif /* _COMMON_H */ diff -Nru kmscube-0.0.0~git20170617/configure.ac kmscube-0.0.0~git20210103/configure.ac --- kmscube-0.0.0~git20170617/configure.ac 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/configure.ac 1970-01-01 00:00:00.000000000 +0000 @@ -1,53 +0,0 @@ -# -# Copyright (c) 2012 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, 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. -# - -# Initialize Autoconf -AC_PREREQ([2.60]) -AC_INIT([kmscube], [0.0.1], [https://cgit.freedesktop.org/mesa/kmscube/], [kmscube]) -AC_CONFIG_AUX_DIR([build-aux]) - -# Initialize Automake -AM_INIT_AUTOMAKE([foreign dist-bzip2]) - -AC_PROG_CC - -# Enable quiet compiles on automake 1.11. -m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) - -# Obtain compiler/linker options for depedencies -PKG_CHECK_MODULES(DRM, [libdrm >= 2.4.71]) -PKG_CHECK_MODULES(GBM, gbm >= 13.0) -PKG_CHECK_MODULES(EGL, egl) -PKG_CHECK_MODULES(GLES2, glesv2) - -# Check for gst and enable cube-video conditionally: -PKG_CHECK_MODULES(GST, gstreamer-1.0 >= 1.6.0 gstreamer-plugins-base-1.0 >= 1.6.0 gstreamer-app-1.0 >= 1.6.0 gstreamer-allocators-1.0 >= 1.6.0 gstreamer-video-1.0 >= 1.6.0 glib-2.0, - [HAVE_GST=yes], [HAVE_GST=no]) -if test "x$HAVE_GST" = "xyes"; then - AC_DEFINE(HAVE_GST, 1, [Have GStreamer support]) - AC_MSG_NOTICE([Building cube-video support]) -fi -AM_CONDITIONAL(ENABLE_GST, [test "x$HAVE_GST" = "xyes"]) - -AC_CONFIG_FILES([Makefile]) -AC_OUTPUT diff -Nru kmscube-0.0.0~git20170617/cube-shadertoy.c kmscube-0.0.0~git20210103/cube-shadertoy.c --- kmscube-0.0.0~git20170617/cube-shadertoy.c 1970-01-01 00:00:00.000000000 +0000 +++ kmscube-0.0.0~git20210103/cube-shadertoy.c 2020-09-02 16:50:08.000000000 +0000 @@ -0,0 +1,459 @@ +/* + * Copyright © 2020 Google, 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, 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. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" +#include "esUtil.h" + +static struct { + struct egl egl; + + const struct gbm *gbm; + + /* Shadertoy rendering (to FBO): */ + GLuint stoy_program; + GLuint stoy_fbo, stoy_fbotex; + GLint stoy_time_loc; + GLuint stoy_vbo; + + /* Cube rendering (textures from FBO): */ + GLfloat aspect; + GLuint program; + /* uniform handles: */ + GLint modelviewmatrix, modelviewprojectionmatrix, normalmatrix; + GLint texture; + GLuint vbo; + GLuint positionsoffset, texcoordsoffset, normalsoffset; + GLuint tex[2]; +} gl; + +static const GLfloat vVertices[] = { + // front + -1.0f, -1.0f, +1.0f, + +1.0f, -1.0f, +1.0f, + -1.0f, +1.0f, +1.0f, + +1.0f, +1.0f, +1.0f, + // back + +1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, + +1.0f, +1.0f, -1.0f, + -1.0f, +1.0f, -1.0f, + // right + +1.0f, -1.0f, +1.0f, + +1.0f, -1.0f, -1.0f, + +1.0f, +1.0f, +1.0f, + +1.0f, +1.0f, -1.0f, + // left + -1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, +1.0f, + -1.0f, +1.0f, -1.0f, + -1.0f, +1.0f, +1.0f, + // top + -1.0f, +1.0f, +1.0f, + +1.0f, +1.0f, +1.0f, + -1.0f, +1.0f, -1.0f, + +1.0f, +1.0f, -1.0f, + // bottom + -1.0f, -1.0f, -1.0f, + +1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, +1.0f, + +1.0f, -1.0f, +1.0f, +}; + +static const GLfloat vTexCoords[] = { + //front + 1.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 0.0f, + //back + 1.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 0.0f, + //right + 1.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 0.0f, + //left + 1.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 0.0f, + //top + 1.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 0.0f, + //bottom + 1.0f, 0.0f, + 0.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, +}; + +static const GLfloat vNormals[] = { + // front + +0.0f, +0.0f, +1.0f, // forward + +0.0f, +0.0f, +1.0f, // forward + +0.0f, +0.0f, +1.0f, // forward + +0.0f, +0.0f, +1.0f, // forward + // back + +0.0f, +0.0f, -1.0f, // backward + +0.0f, +0.0f, -1.0f, // backward + +0.0f, +0.0f, -1.0f, // backward + +0.0f, +0.0f, -1.0f, // backward + // right + +1.0f, +0.0f, +0.0f, // right + +1.0f, +0.0f, +0.0f, // right + +1.0f, +0.0f, +0.0f, // right + +1.0f, +0.0f, +0.0f, // right + // left + -1.0f, +0.0f, +0.0f, // left + -1.0f, +0.0f, +0.0f, // left + -1.0f, +0.0f, +0.0f, // left + -1.0f, +0.0f, +0.0f, // left + // top + +0.0f, +1.0f, +0.0f, // up + +0.0f, +1.0f, +0.0f, // up + +0.0f, +1.0f, +0.0f, // up + +0.0f, +1.0f, +0.0f, // up + // bottom + +0.0f, -1.0f, +0.0f, // down + +0.0f, -1.0f, +0.0f, // down + +0.0f, -1.0f, +0.0f, // down + +0.0f, -1.0f, +0.0f // down +}; + +static const char *cube_vs = + "uniform mat4 modelviewMatrix; \n" + "uniform mat4 modelviewprojectionMatrix;\n" + "uniform mat3 normalMatrix; \n" + " \n" + "attribute vec4 in_position; \n" + "attribute vec3 in_normal; \n" + "attribute vec2 in_TexCoord; \n" + " \n" + "vec4 lightSource = vec4(2.0, 2.0, 20.0, 0.0);\n" + " \n" + "varying vec4 vVaryingColor; \n" + "varying vec2 vTexCoord; \n" + " \n" + "void main() \n" + "{ \n" + " gl_Position = modelviewprojectionMatrix * in_position;\n" + " vec3 vEyeNormal = normalMatrix * in_normal;\n" + " vec4 vPosition4 = modelviewMatrix * in_position;\n" + " vec3 vPosition3 = vPosition4.xyz / vPosition4.w;\n" + " vec3 vLightDir = normalize(lightSource.xyz - vPosition3);\n" + " float diff = max(0.0, dot(vEyeNormal, vLightDir));\n" + " vVaryingColor = vec4(diff * vec3(1.0, 1.0, 1.0), 1.0);\n" + " vTexCoord = in_TexCoord; \n" + "} \n"; + +static const char *cube_fs = + "precision mediump float; \n" + " \n" + "uniform sampler2D uTex; \n" + " \n" + "varying vec4 vVaryingColor; \n" + "varying vec2 vTexCoord; \n" + " \n" + "void main() \n" + "{ \n" + " gl_FragColor = vVaryingColor * texture2D(uTex, vTexCoord);\n" + "} \n"; + +static const char *shadertoy_vs = + "attribute vec3 position; \n" + "void main() \n" + "{ \n" + " gl_Position = vec4(position, 1.0);\n" + "} \n"; + +static const char *shadertoy_fs_tmpl = + "precision mediump float; \n" + "uniform vec3 iResolution; // viewport resolution (in pixels) \n" + "uniform float iGlobalTime; // shader playback time (in seconds) \n" + "uniform vec4 iMouse; // mouse pixel coords \n" + "uniform vec4 iDate; // (year, month, day, time in seconds) \n" + "uniform float iSampleRate; // sound sample rate (i.e., 44100) \n" + "uniform vec3 iChannelResolution[4]; // channel resolution (in pixels) \n" + "uniform float iChannelTime[4]; // channel playback time (in sec) \n" + "uniform float iTime; \n" + " \n" + "%s \n" + " \n" + "void main() \n" + "{ \n" + " mainImage(gl_FragColor, gl_FragCoord.xy); \n" + "} \n"; + + +static const uint32_t texw = 512, texh = 512; + +static int load_shader(const char *file) +{ + struct stat statbuf; + char *frag; + int fd, ret; + + /* load src file: */ + fd = open(file, 0); + if (fd < 0) { + err(fd, "could not open '%s'", file); + } + + ret = fstat(fd, &statbuf); + if (ret < 0) { + err(ret, "could not stat '%s'", file); + } + + const char *text = + mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + + asprintf(&frag, shadertoy_fs_tmpl, text); + + return create_program(shadertoy_vs, frag); +} + +static int init_shadertoy(const char *file) +{ + int ret = load_shader(file); + gl.stoy_program = ret; + + glBindAttribLocation(gl.program, 0, "position"); + + ret = link_program(gl.stoy_program); + + glUseProgram(gl.stoy_program); + gl.stoy_time_loc = glGetUniformLocation(gl.stoy_program, "iTime"); + + /* we can set iResolution a single time, it doesn't change: */ + GLint resolution_location = glGetUniformLocation(gl.stoy_program, "iResolution"); + glUniform3f(resolution_location, texw, texh, 0); + + glGenFramebuffers(1, &gl.stoy_fbo); + glGenTextures(1, &gl.stoy_fbotex); + glBindFramebuffer(GL_FRAMEBUFFER, gl.stoy_fbo); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, gl.stoy_fbotex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texw, texh, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + gl.stoy_fbotex, 0); + + const GLfloat vertices[] = { + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + }; + glGenBuffers(1, &gl.stoy_vbo); + glBindBuffer(GL_ARRAY_BUFFER, gl.stoy_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), 0, GL_STATIC_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), &vertices[0]); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)(intptr_t)0); + + return 0; +} + +static void draw_shadertoy(unsigned i) +{ + GLenum mrt_bufs[] = {GL_COLOR_ATTACHMENT0}; + + glBindFramebuffer(GL_FRAMEBUFFER, gl.stoy_fbo); + glViewport(0, 0, texw, texh); + + glUseProgram(gl.stoy_program); + glUniform1f(gl.stoy_time_loc, (float)i / 60.0f); + + glBindBuffer(GL_ARRAY_BUFFER, gl.stoy_vbo); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)(intptr_t)0); + glEnableVertexAttribArray(0); + + glDrawBuffers(1, mrt_bufs); + + start_perfcntrs(); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + end_perfcntrs(); + + glDisableVertexAttribArray(0); + + /* switch back to back buffer: */ + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +static void draw_cube_shadertoy(unsigned i) +{ + ESMatrix modelview; + + draw_shadertoy(i); + + glViewport(0, 0, gl.gbm->width, gl.gbm->height); + glEnable(GL_CULL_FACE); + + /* clear the color buffer */ + glClearColor(0.5, 0.5, 0.5, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(gl.program); + + esMatrixLoadIdentity(&modelview); + esTranslate(&modelview, 0.0f, 0.0f, -8.0f); + esRotate(&modelview, 45.0f + (0.25f * i), 1.0f, 0.0f, 0.0f); + esRotate(&modelview, 45.0f - (0.5f * i), 0.0f, 1.0f, 0.0f); + esRotate(&modelview, 10.0f + (0.15f * i), 0.0f, 0.0f, 1.0f); + + ESMatrix projection; + esMatrixLoadIdentity(&projection); + esFrustum(&projection, -2.8f, +2.8f, -2.8f * gl.aspect, +2.8f * gl.aspect, 6.0f, 10.0f); + + ESMatrix modelviewprojection; + esMatrixLoadIdentity(&modelviewprojection); + esMatrixMultiply(&modelviewprojection, &modelview, &projection); + + float normal[9]; + normal[0] = modelview.m[0][0]; + normal[1] = modelview.m[0][1]; + normal[2] = modelview.m[0][2]; + normal[3] = modelview.m[1][0]; + normal[4] = modelview.m[1][1]; + normal[5] = modelview.m[1][2]; + normal[6] = modelview.m[2][0]; + normal[7] = modelview.m[2][1]; + normal[8] = modelview.m[2][2]; + + glUniformMatrix4fv(gl.modelviewmatrix, 1, GL_FALSE, &modelview.m[0][0]); + glUniformMatrix4fv(gl.modelviewprojectionmatrix, 1, GL_FALSE, &modelviewprojection.m[0][0]); + glUniformMatrix3fv(gl.normalmatrix, 1, GL_FALSE, normal); + + glBindBuffer(GL_ARRAY_BUFFER, gl.vbo); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)(intptr_t)gl.positionsoffset); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)(intptr_t)gl.normalsoffset); + glEnableVertexAttribArray(1); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)(intptr_t)gl.texcoordsoffset); + glEnableVertexAttribArray(2); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, gl.stoy_fbotex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_REPEAT); + glUniform1i(gl.texture, 0); /* '0' refers to texture unit 0. */ + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glDrawArrays(GL_TRIANGLE_STRIP, 4, 4); + glDrawArrays(GL_TRIANGLE_STRIP, 8, 4); + glDrawArrays(GL_TRIANGLE_STRIP, 12, 4); + glDrawArrays(GL_TRIANGLE_STRIP, 16, 4); + glDrawArrays(GL_TRIANGLE_STRIP, 20, 4); + + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(2); +} + +const struct egl * init_cube_shadertoy(const struct gbm *gbm, const char *file, int samples) +{ + int ret; + + ret = init_egl(&gl.egl, gbm, samples); + if (ret) + return NULL; + + gl.aspect = (GLfloat)(gbm->height) / (GLfloat)(gbm->width); + gl.gbm = gbm; + + ret = create_program(cube_vs, cube_fs); + if (ret < 0) + return NULL; + + gl.program = ret; + + glBindAttribLocation(gl.program, 0, "in_position"); + glBindAttribLocation(gl.program, 1, "in_normal"); + glBindAttribLocation(gl.program, 2, "in_color"); + + ret = link_program(gl.program); + if (ret) + return NULL; + + glUseProgram(gl.program); + + gl.modelviewmatrix = glGetUniformLocation(gl.program, "modelviewMatrix"); + gl.modelviewprojectionmatrix = glGetUniformLocation(gl.program, "modelviewprojectionMatrix"); + gl.normalmatrix = glGetUniformLocation(gl.program, "normalMatrix"); + gl.texture = glGetUniformLocation(gl.program, "uTex"); + + glViewport(0, 0, gbm->width, gbm->height); + glEnable(GL_CULL_FACE); + + gl.positionsoffset = 0; + gl.texcoordsoffset = sizeof(vVertices); + gl.normalsoffset = sizeof(vVertices) + sizeof(vTexCoords); + + glGenBuffers(1, &gl.vbo); + glBindBuffer(GL_ARRAY_BUFFER, gl.vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vVertices) + sizeof(vTexCoords) + sizeof(vNormals), 0, GL_STATIC_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, gl.positionsoffset, sizeof(vVertices), &vVertices[0]); + glBufferSubData(GL_ARRAY_BUFFER, gl.texcoordsoffset, sizeof(vTexCoords), &vTexCoords[0]); + glBufferSubData(GL_ARRAY_BUFFER, gl.normalsoffset, sizeof(vNormals), &vNormals[0]); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)(intptr_t)gl.positionsoffset); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)(intptr_t)gl.normalsoffset); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)(intptr_t)gl.texcoordsoffset); + + ret = init_shadertoy(file); + if (ret) { + printf("failed to initialize\n"); + return NULL; + } + + gl.egl.draw = draw_cube_shadertoy; + + return &gl.egl; +} diff -Nru kmscube-0.0.0~git20170617/cube-smooth.c kmscube-0.0.0~git20210103/cube-smooth.c --- kmscube-0.0.0~git20170617/cube-smooth.c 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/cube-smooth.c 2020-09-02 16:50:08.000000000 +0000 @@ -28,7 +28,7 @@ #include "esUtil.h" -struct { +static struct { struct egl egl; GLfloat aspect; diff -Nru kmscube-0.0.0~git20170617/cube-tex.c kmscube-0.0.0~git20210103/cube-tex.c --- kmscube-0.0.0~git20170617/cube-tex.c 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/cube-tex.c 2020-09-02 16:50:08.000000000 +0000 @@ -24,14 +24,13 @@ #include #include #include +#include #include #include "common.h" #include "esUtil.h" -#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) - -struct { +static struct { struct egl egl; GLfloat aspect; @@ -363,6 +362,7 @@ egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, img); egl->eglDestroyImageKHR(egl->display, img); + close(fd); return 0; } @@ -429,6 +429,7 @@ egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, img_y); egl->eglDestroyImageKHR(egl->display, img_y); + close(fd_y); /* UV plane texture: */ img_uv = egl->eglCreateImageKHR(egl->display, EGL_NO_CONTEXT, @@ -443,6 +444,7 @@ egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, img_uv); egl->eglDestroyImageKHR(egl->display, img_uv); + close(fd_uv); return 0; } @@ -499,6 +501,8 @@ egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, img); egl->eglDestroyImageKHR(egl->display, img); + close(fd_y); + close(fd_uv); return 0; } @@ -512,8 +516,7 @@ return init_tex_nv12_2img(); case NV12_1IMG: return init_tex_nv12_1img(); - case SMOOTH: - case VIDEO: + default: assert(!"unreachable"); return -1; } diff -Nru kmscube-0.0.0~git20170617/cube-video.c kmscube-0.0.0~git20210103/cube-video.c --- kmscube-0.0.0~git20170617/cube-video.c 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/cube-video.c 2020-09-02 16:50:08.000000000 +0000 @@ -31,7 +31,7 @@ #include "common.h" #include "esUtil.h" -struct { +static struct { struct egl egl; GLfloat aspect; diff -Nru kmscube-0.0.0~git20170617/debian/changelog kmscube-0.0.0~git20210103/debian/changelog --- kmscube-0.0.0~git20170617/debian/changelog 2018-07-06 17:11:01.000000000 +0000 +++ kmscube-0.0.0~git20210103/debian/changelog 2021-01-03 14:42:37.000000000 +0000 @@ -1,3 +1,17 @@ +kmscube (0.0.0~git20210103-1) unstable; urgency=medium + + * New upstream git snapshot + (Closes: #961794) + * d/control: Build-depend on meson. + now be used as a build system + * d/control: Build-depend on libpng-dev + * d/control: Add explicit glib-2.0 build-dep + * d/control: Bump standards version to 4.5.1 + * Switch to dh 13 + * debian: Add salsa-ci + + -- Guido Günther Sun, 03 Jan 2021 15:42:37 +0100 + kmscube (0.0.0~git20170617-2) unstable; urgency=medium [ Guido Günther ] diff -Nru kmscube-0.0.0~git20170617/debian/compat kmscube-0.0.0~git20210103/debian/compat --- kmscube-0.0.0~git20170617/debian/compat 2018-06-28 07:24:04.000000000 +0000 +++ kmscube-0.0.0~git20210103/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -10 diff -Nru kmscube-0.0.0~git20170617/debian/control kmscube-0.0.0~git20210103/debian/control --- kmscube-0.0.0~git20170617/debian/control 2018-06-28 07:26:03.000000000 +0000 +++ kmscube-0.0.0~git20210103/debian/control 2021-01-03 14:42:37.000000000 +0000 @@ -2,14 +2,17 @@ Section: graphics Priority: optional Maintainer: Guido Günther -Build-Depends: debhelper (>= 10), +Build-Depends: debhelper-compat (= 13), libdrm-dev, libgbm-dev, libgles2-mesa-dev, + libglib2.0-dev, libgstreamer1.0-dev, libgstreamer-plugins-base1.0-dev, + libpng-dev, + meson, pkg-config, -Standards-Version: 4.1.3 +Standards-Version: 4.5.1 Homepage: https://cgit.freedesktop.org/mesa/kmscube/ Vcs-Git: https://salsa.debian.org/agx/kmscube.git Vcs-Browser: https://salsa.debian.org/agx/kmscube diff -Nru kmscube-0.0.0~git20170617/debian/salsa-ci.yml kmscube-0.0.0~git20210103/debian/salsa-ci.yml --- kmscube-0.0.0~git20170617/debian/salsa-ci.yml 1970-01-01 00:00:00.000000000 +0000 +++ kmscube-0.0.0~git20210103/debian/salsa-ci.yml 2021-01-03 14:42:37.000000000 +0000 @@ -0,0 +1,7 @@ +--- +variables: + GIT_SUBMODULE_STRATEGY: recursive + +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml diff -Nru kmscube-0.0.0~git20170617/drm-atomic.c kmscube-0.0.0~git20210103/drm-atomic.c --- kmscube-0.0.0~git20170617/drm-atomic.c 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/drm-atomic.c 2020-09-02 16:50:08.000000000 +0000 @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -179,6 +180,7 @@ struct drm_fb *fb; uint32_t i = 0; uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK; + int64_t start_time, report_time, cur_time; int ret; if (egl_check(egl, eglDupNativeFenceFDANDROID) || @@ -191,7 +193,10 @@ /* Allow a modeset change for the first commit only. */ flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; - while (1) { + start_time = report_time = get_time_ns(); + + while (i < drm.count) { + unsigned frame = i; struct gbm_bo *next_bo; EGLSyncKHR gpu_fence = NULL; /* out-fence from gpu, in-fence to kms */ EGLSyncKHR kms_fence = NULL; /* in-fence to gpu, out-fence from kms */ @@ -211,6 +216,17 @@ egl->eglWaitSyncKHR(egl->display, kms_fence, 0); } + /* Start fps measuring on second frame, to remove the time spent + * compiling shader, etc, from the fps: + */ + if (i == 1) { + start_time = report_time = get_time_ns(); + } + + if (!gbm->surface) { + glBindFramebuffer(GL_FRAMEBUFFER, egl->fbs[frame % NUM_BUFFERS].fb); + } + egl->draw(i++); /* insert fence to be singled in cmdstream.. this fence will be @@ -219,7 +235,9 @@ gpu_fence = create_fence(egl, EGL_NO_NATIVE_FENCE_FD_ANDROID); assert(gpu_fence); - eglSwapBuffers(egl->display, egl->surface); + if (gbm->surface) { + eglSwapBuffers(egl->display, egl->surface); + } /* after swapbuffers, gpu_fence should be flushed, so safe * to get fd: @@ -228,7 +246,11 @@ egl->eglDestroySyncKHR(egl->display, gpu_fence); assert(drm.kms_in_fence_fd != -1); - next_bo = gbm_surface_lock_front_buffer(gbm->surface); + if (gbm->surface) { + next_bo = gbm_surface_lock_front_buffer(gbm->surface); + } else { + next_bo = gbm->bos[frame % NUM_BUFFERS]; + } if (!next_bo) { printf("Failed to lock frontbuffer\n"); return -1; @@ -257,6 +279,27 @@ egl->eglDestroySyncKHR(egl->display, kms_fence); } + cur_time = get_time_ns(); + if (cur_time > (report_time + 2 * NSEC_PER_SEC)) { + double elapsed_time = cur_time - start_time; + double secs = elapsed_time / (double)NSEC_PER_SEC; + unsigned frames = i - 1; /* first frame ignored */ + printf("Rendered %u frames in %f sec (%f fps)\n", + frames, secs, (double)frames/secs); + report_time = cur_time; + } + + /* Check for user input: */ + struct pollfd fdset[] = { { + .fd = STDIN_FILENO, + .events = POLLIN, + } }; + ret = poll(fdset, ARRAY_SIZE(fdset), 0); + if (ret > 0) { + printf("user interrupted!\n"); + return 0; + } + /* * Here you could also update drm plane layers if you want * hw composition @@ -268,7 +311,7 @@ } /* release last buffer to render on again: */ - if (bo) + if (bo && gbm->surface) gbm_surface_release_buffer(gbm->surface, bo); bo = next_bo; @@ -276,6 +319,17 @@ flags &= ~(DRM_MODE_ATOMIC_ALLOW_MODESET); } + finish_perfcntrs(); + + cur_time = get_time_ns(); + double elapsed_time = cur_time - start_time; + double secs = elapsed_time / (double)NSEC_PER_SEC; + unsigned frames = i - 1; /* first frame ignored */ + printf("Rendered %u frames in %f sec (%f fps)\n", + frames, secs, (double)frames/secs); + + dump_perfcntrs(frames, elapsed_time); + return ret; } @@ -337,12 +391,13 @@ return ret; } -const struct drm * init_drm_atomic(const char *device) +const struct drm * init_drm_atomic(const char *device, const char *mode_str, + unsigned int vrefresh, unsigned int count) { uint32_t plane_id; int ret; - ret = init_drm(&drm, device); + ret = init_drm(&drm, device, mode_str, vrefresh, count); if (ret) return NULL; @@ -391,7 +446,7 @@ return NULL; \ } \ drm.type->props_info = calloc(drm.type->props->count_props, \ - sizeof(drm.type->props_info)); \ + sizeof(*drm.type->props_info)); \ for (i = 0; i < drm.type->props->count_props; i++) { \ drm.type->props_info[i] = drmModeGetProperty(drm.fd, \ drm.type->props->props[i]); \ diff -Nru kmscube-0.0.0~git20170617/drm-common.c kmscube-0.0.0~git20210103/drm-common.c --- kmscube-0.0.0~git20170617/drm-common.c 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/drm-common.c 2020-09-02 16:50:08.000000000 +0000 @@ -27,6 +27,7 @@ #include #include #include +#include #include "common.h" #include "drm-common.h" @@ -160,21 +161,75 @@ return -1; } -int init_drm(struct drm *drm, const char *device) +static int get_resources(int fd, drmModeRes **resources) +{ + *resources = drmModeGetResources(fd); + if (*resources == NULL) + return -1; + return 0; +} + +#define MAX_DRM_DEVICES 64 + +static int find_drm_device(drmModeRes **resources) +{ + drmDevicePtr devices[MAX_DRM_DEVICES] = { NULL }; + int num_devices, fd = -1; + + num_devices = drmGetDevices2(0, devices, MAX_DRM_DEVICES); + if (num_devices < 0) { + printf("drmGetDevices2 failed: %s\n", strerror(-num_devices)); + return -1; + } + + for (int i = 0; i < num_devices; i++) { + drmDevicePtr device = devices[i]; + int ret; + + if (!(device->available_nodes & (1 << DRM_NODE_PRIMARY))) + continue; + /* OK, it's a primary device. If we can get the + * drmModeResources, it means it's also a + * KMS-capable device. + */ + fd = open(device->nodes[DRM_NODE_PRIMARY], O_RDWR); + if (fd < 0) + continue; + ret = get_resources(fd, resources); + if (!ret) + break; + close(fd); + fd = -1; + } + drmFreeDevices(devices, num_devices); + + if (fd < 0) + printf("no drm device found!\n"); + return fd; +} + +int init_drm(struct drm *drm, const char *device, const char *mode_str, + unsigned int vrefresh, unsigned int count) { drmModeRes *resources; drmModeConnector *connector = NULL; drmModeEncoder *encoder = NULL; - int i, area; + int i, ret, area; - drm->fd = open(device, O_RDWR); + if (device) { + drm->fd = open(device, O_RDWR); + ret = get_resources(drm->fd, &resources); + if (ret < 0 && errno == EOPNOTSUPP) + printf("%s does not look like a modeset device\n", device); + } else { + drm->fd = find_drm_device(&resources); + } if (drm->fd < 0) { printf("could not open drm device\n"); return -1; } - resources = drmModeGetResources(drm->fd); if (!resources) { printf("drmModeGetResources failed: %s\n", strerror(errno)); return -1; @@ -199,18 +254,37 @@ return -1; } + /* find user requested mode: */ + if (mode_str && *mode_str) { + for (i = 0; i < connector->count_modes; i++) { + drmModeModeInfo *current_mode = &connector->modes[i]; + + if (strcmp(current_mode->name, mode_str) == 0) { + if (vrefresh == 0 || current_mode->vrefresh == vrefresh) { + drm->mode = current_mode; + break; + } + } + } + if (!drm->mode) + printf("requested mode not found, using default mode!\n"); + } + /* find preferred mode or the highest resolution mode: */ - for (i = 0, area = 0; i < connector->count_modes; i++) { - drmModeModeInfo *current_mode = &connector->modes[i]; + if (!drm->mode) { + for (i = 0, area = 0; i < connector->count_modes; i++) { + drmModeModeInfo *current_mode = &connector->modes[i]; - if (current_mode->type & DRM_MODE_TYPE_PREFERRED) { - drm->mode = current_mode; - } + if (current_mode->type & DRM_MODE_TYPE_PREFERRED) { + drm->mode = current_mode; + break; + } - int current_area = current_mode->hdisplay * current_mode->vdisplay; - if (current_area > area) { - drm->mode = current_mode; - area = current_area; + int current_area = current_mode->hdisplay * current_mode->vdisplay; + if (current_area > area) { + drm->mode = current_mode; + area = current_area; + } } } @@ -250,6 +324,7 @@ drmModeFreeResources(resources); drm->connector_id = connector->connector_id; + drm->count = count; return 0; } diff -Nru kmscube-0.0.0~git20170617/drm-common.h kmscube-0.0.0~git20210103/drm-common.h --- kmscube-0.0.0~git20170617/drm-common.h 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/drm-common.h 2020-09-02 16:50:08.000000000 +0000 @@ -63,6 +63,9 @@ uint32_t crtc_id; uint32_t connector_id; + /* number of frames to run for: */ + unsigned int count; + int (*run)(const struct gbm *gbm, const struct egl *egl); }; @@ -73,8 +76,8 @@ struct drm_fb * drm_fb_get_from_bo(struct gbm_bo *bo); -int init_drm(struct drm *drm, const char *device); -const struct drm * init_drm_legacy(const char *device); -const struct drm * init_drm_atomic(const char *device); +int init_drm(struct drm *drm, const char *device, const char *mode_str, unsigned int vrefresh, unsigned int count); +const struct drm * init_drm_legacy(const char *device, const char *mode_str, unsigned int vrefresh, unsigned int count); +const struct drm * init_drm_atomic(const char *device, const char *mode_str, unsigned int vrefresh, unsigned int count); #endif /* _DRM_COMMON_H */ diff -Nru kmscube-0.0.0~git20170617/drm-legacy.c kmscube-0.0.0~git20210103/drm-legacy.c --- kmscube-0.0.0~git20170617/drm-legacy.c 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/drm-legacy.c 2020-09-02 16:50:08.000000000 +0000 @@ -51,10 +51,15 @@ struct gbm_bo *bo; struct drm_fb *fb; uint32_t i = 0; + int64_t start_time, report_time, cur_time; int ret; - eglSwapBuffers(egl->display, egl->surface); - bo = gbm_surface_lock_front_buffer(gbm->surface); + if (gbm->surface) { + eglSwapBuffers(egl->display, egl->surface); + bo = gbm_surface_lock_front_buffer(gbm->surface); + } else { + bo = gbm->bos[0]; + } fb = drm_fb_get_from_bo(bo); if (!fb) { fprintf(stderr, "Failed to get a new framebuffer BO\n"); @@ -69,14 +74,33 @@ return ret; } - while (1) { + start_time = report_time = get_time_ns(); + + while (i < drm.count) { + unsigned frame = i; struct gbm_bo *next_bo; int waiting_for_flip = 1; + /* Start fps measuring on second frame, to remove the time spent + * compiling shader, etc, from the fps: + */ + if (i == 1) { + start_time = report_time = get_time_ns(); + } + + if (!gbm->surface) { + glBindFramebuffer(GL_FRAMEBUFFER, egl->fbs[frame % NUM_BUFFERS].fb); + } + egl->draw(i++); - eglSwapBuffers(egl->display, egl->surface); - next_bo = gbm_surface_lock_front_buffer(gbm->surface); + if (gbm->surface) { + eglSwapBuffers(egl->display, egl->surface); + next_bo = gbm_surface_lock_front_buffer(gbm->surface); + } else { + glFinish(); + next_bo = gbm->bos[frame % NUM_BUFFERS]; + } fb = drm_fb_get_from_bo(next_bo); if (!fb) { fprintf(stderr, "Failed to get a new framebuffer BO\n"); @@ -114,19 +138,43 @@ drmHandleEvent(drm.fd, &evctx); } + cur_time = get_time_ns(); + if (cur_time > (report_time + 2 * NSEC_PER_SEC)) { + double elapsed_time = cur_time - start_time; + double secs = elapsed_time / (double)NSEC_PER_SEC; + unsigned frames = i - 1; /* first frame ignored */ + printf("Rendered %u frames in %f sec (%f fps)\n", + frames, secs, (double)frames/secs); + report_time = cur_time; + } + /* release last buffer to render on again: */ - gbm_surface_release_buffer(gbm->surface, bo); + if (gbm->surface) { + gbm_surface_release_buffer(gbm->surface, bo); + } bo = next_bo; } + finish_perfcntrs(); + + cur_time = get_time_ns(); + double elapsed_time = cur_time - start_time; + double secs = elapsed_time / (double)NSEC_PER_SEC; + unsigned frames = i - 1; /* first frame ignored */ + printf("Rendered %u frames in %f sec (%f fps)\n", + frames, secs, (double)frames/secs); + + dump_perfcntrs(frames, elapsed_time); + return 0; } -const struct drm * init_drm_legacy(const char *device) +const struct drm * init_drm_legacy(const char *device, const char *mode_str, + unsigned int vrefresh, unsigned int count) { int ret; - ret = init_drm(&drm, device); + ret = init_drm(&drm, device, mode_str, vrefresh, count); if (ret) return NULL; diff -Nru kmscube-0.0.0~git20170617/.gitlab-ci.yml kmscube-0.0.0~git20210103/.gitlab-ci.yml --- kmscube-0.0.0~git20170617/.gitlab-ci.yml 1970-01-01 00:00:00.000000000 +0000 +++ kmscube-0.0.0~git20210103/.gitlab-ci.yml 2020-09-02 16:50:08.000000000 +0000 @@ -0,0 +1,55 @@ +.artifacts-meson: &artifacts-meson + when: always + paths: + - _build/meson-logs + +.meson-build: &meson-build + - meson _build -D auto_features=enabled + - ninja -C _build + +latest-meson: + stage: build + image: archlinux/base:latest + artifacts: *artifacts-meson + before_script: + - pacman -Syu --noconfirm --needed + base-devel + meson + libdrm + mesa + gstreamer + gst-plugins-base + libpng + script: *meson-build + +oldest-meson: + stage: build + image: debian:stable + artifacts: *artifacts-meson + before_script: + - printf > /etc/dpkg/dpkg.cfg.d/99-exclude-cruft "%s\n" + 'path-exclude=/usr/share/doc/*' + 'path-exclude=/usr/share/man/*' + - printf > /usr/sbin/policy-rc.d "%s\n" + '#!/bin/sh' + 'exit 101' + - chmod +x /usr/sbin/policy-rc.d + - apt-get update + - apt-get -y --no-install-recommends install + build-essential + pkg-config + libdrm-dev + libgbm-dev + libegl1-mesa-dev + libgles2-mesa-dev + libgstreamer1.0-dev + libgstreamer-plugins-base1.0-dev + gstreamer1.0-plugins-base + gstreamer1.0-plugins-base-apps + ninja-build + python3 python3-pip + libpng-dev + - pip3 install wheel setuptools + - pip3 install meson==0.47 + script: *meson-build + diff -Nru kmscube-0.0.0~git20170617/gst-decoder.c kmscube-0.0.0~git20210103/gst-decoder.c --- kmscube-0.0.0~git20170617/gst-decoder.c 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/gst-decoder.c 2020-09-02 16:50:08.000000000 +0000 @@ -490,7 +490,7 @@ } /* Cleanup */ - for (unsigned i = 0; i < nmems; i++) + for (unsigned i = 0; i < nplanes; i++) close(planes[i].fd); return image; diff -Nru kmscube-0.0.0~git20170617/kmscube.c kmscube-0.0.0~git20210103/kmscube.c --- kmscube-0.0.0~git20170617/kmscube.c 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/kmscube.c 2020-09-02 16:50:08.000000000 +0000 @@ -37,51 +37,73 @@ GST_DEBUG_CATEGORY(kmscube_debug); #endif -#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) - static const struct egl *egl; static const struct gbm *gbm; static const struct drm *drm; -static const char *shortopts = "AD:M:m:V:"; +static const char *shortopts = "Ac:D:f:M:m:p:S:s:V:v:x"; static const struct option longopts[] = { {"atomic", no_argument, 0, 'A'}, + {"count", required_argument, 0, 'c'}, {"device", required_argument, 0, 'D'}, + {"format", required_argument, 0, 'f'}, {"mode", required_argument, 0, 'M'}, {"modifier", required_argument, 0, 'm'}, + {"perfcntr", required_argument, 0, 'p'}, {"samples", required_argument, 0, 's'}, {"video", required_argument, 0, 'V'}, + {"vmode", required_argument, 0, 'v'}, + {"surfaceless", no_argument, 0, 'x'}, {0, 0, 0, 0} }; static void usage(const char *name) { - printf("Usage: %s [-ADMmV]\n" + printf("Usage: %s [-ADfMmSsVvx]\n" "\n" "options:\n" " -A, --atomic use atomic modesetting and fencing\n" + " -c, --count run for the specified number of frames\n" " -D, --device=DEVICE use the given device\n" + " -f, --format=FOURCC framebuffer format\n" " -M, --mode=MODE specify mode, one of:\n" " smooth - smooth shaded cube (default)\n" " rgba - rgba textured cube\n" " nv12-2img - yuv textured (color conversion in shader)\n" " nv12-1img - yuv textured (single nv12 texture)\n" " -m, --modifier=MODIFIER hardcode the selected modifier\n" + " -p, --perfcntr=LIST sample specified performance counters using\n" + " the AMD_performance_monitor extension (comma\n" + " separated list, shadertoy mode only)\n" + " -S, --shadertoy=FILE use specified shadertoy shader\n" " -s, --samples=N use MSAA\n" - " -V, --video=FILE video textured cube\n", + " -V, --video=FILE video textured cube (comma separated list)\n" + " -v, --vmode=VMODE specify the video mode in the format\n" + " [-]\n" + " -x, --surfaceless use surfaceless mode, instead of gbm surface\n" + , name); } int main(int argc, char *argv[]) { - const char *device = "/dev/dri/card0"; + const char *device = NULL; const char *video = NULL; + const char *shadertoy = NULL; + const char *perfcntr = NULL; + char mode_str[DRM_DISPLAY_MODE_LEN] = ""; + char *p; enum mode mode = SMOOTH; + uint32_t format = DRM_FORMAT_XRGB8888; uint64_t modifier = DRM_FORMAT_MOD_LINEAR; int samples = 0; int atomic = 0; int opt; + unsigned int len; + unsigned int vrefresh = 0; + unsigned int count = ~0; + bool surfaceless = false; #ifdef HAVE_GST gst_init(&argc, &argv); @@ -93,9 +115,27 @@ case 'A': atomic = 1; break; + case 'c': + count = strtoul(optarg, NULL, 0); + break; case 'D': device = optarg; break; + case 'f': { + char fourcc[4] = " "; + int length = strlen(optarg); + if (length > 0) + fourcc[0] = optarg[0]; + if (length > 1) + fourcc[1] = optarg[1]; + if (length > 2) + fourcc[2] = optarg[2]; + if (length > 3) + fourcc[3] = optarg[3]; + format = fourcc_code(fourcc[0], fourcc[1], + fourcc[2], fourcc[3]); + break; + } case 'M': if (strcmp(optarg, "smooth") == 0) { mode = SMOOTH; @@ -114,6 +154,13 @@ case 'm': modifier = strtoull(optarg, NULL, 0); break; + case 'p': + perfcntr = optarg; + break; + case 'S': + mode = SHADERTOY; + shadertoy = optarg; + break; case 's': samples = strtoul(optarg, NULL, 0); break; @@ -121,6 +168,22 @@ mode = VIDEO; video = optarg; 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 'x': + surfaceless = true; + break; default: usage(argv[0]); return -1; @@ -128,16 +191,16 @@ } if (atomic) - drm = init_drm_atomic(device); + drm = init_drm_atomic(device, mode_str, vrefresh, count); else - drm = init_drm_legacy(device); + drm = init_drm_legacy(device, mode_str, vrefresh, count); if (!drm) { printf("failed to initialize %s DRM\n", atomic ? "atomic" : "legacy"); return -1; } gbm = init_gbm(drm->fd, drm->mode->hdisplay, drm->mode->vdisplay, - modifier); + format, modifier, surfaceless); if (!gbm) { printf("failed to initialize GBM\n"); return -1; @@ -147,6 +210,8 @@ egl = init_cube_smooth(gbm, samples); else if (mode == VIDEO) egl = init_cube_video(gbm, video, samples); + else if (mode == SHADERTOY) + egl = init_cube_shadertoy(gbm, shadertoy, samples); else egl = init_cube_tex(gbm, mode, samples); @@ -155,6 +220,14 @@ return -1; } + if (perfcntr) { + if (mode != SHADERTOY) { + printf("performance counters only supported in shadertoy mode\n"); + return -1; + } + init_perfcntrs(egl, perfcntr); + } + /* clear the color buffer */ glClearColor(0.5, 0.5, 0.5, 1.0); glClear(GL_COLOR_BUFFER_BIT); diff -Nru kmscube-0.0.0~git20170617/Makefile.am kmscube-0.0.0~git20210103/Makefile.am --- kmscube-0.0.0~git20170617/Makefile.am 2018-06-19 13:43:09.000000000 +0000 +++ kmscube-0.0.0~git20210103/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,61 +0,0 @@ -# -# Copyright (c) 2012 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, 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. -# - -bin_PROGRAMS = kmscube - -kmscube_LDADD = \ - $(DRM_LIBS) \ - $(GBM_LIBS) \ - $(EGL_LIBS) \ - $(GLES2_LIBS) \ - -lm - -kmscube_CFLAGS = \ - -O0 -g \ - -Wall -Wextra \ - -std=c99 \ - $(DRM_CFLAGS) \ - $(GBM_CFLAGS) \ - $(EGL_CFLAGS) \ - $(GLES2_CFLAGS) - -kmscube_SOURCES = \ - common.c \ - common.h \ - cube-smooth.c \ - cube-tex.c \ - drm-atomic.c \ - drm-common.c \ - drm-common.h \ - drm-legacy.c \ - esTransform.c \ - esUtil.h \ - frame-512x512-NV12.c \ - frame-512x512-RGBA.c \ - kmscube.c - -if ENABLE_GST -kmscube_LDADD += $(GST_LIBS) -kmscube_CFLAGS += $(GST_CFLAGS) -kmscube_SOURCES += cube-video.c gst-decoder.c -endif diff -Nru kmscube-0.0.0~git20170617/meson.build kmscube-0.0.0~git20210103/meson.build --- kmscube-0.0.0~git20170617/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ kmscube-0.0.0~git20210103/meson.build 2020-09-02 16:50:08.000000000 +0000 @@ -0,0 +1,105 @@ +# +# Copyright (c) 2018 Lyude Paul +# +# 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. +# +project( + 'kmscube', + 'c', + version : '0.0.1', + license : 'MIT', + meson_version : '>= 0.47', + default_options : ['c_std=gnu99', 'warning_level=2'] +) + +if get_option('c_std') != 'gnu99' + error('c_std must be gnu99') +endif + +sources = files( + 'common.c', + 'cube-shadertoy.c', + 'cube-smooth.c', + 'cube-tex.c', + 'drm-atomic.c', + 'drm-common.c', + 'drm-legacy.c', + 'esTransform.c', + 'frame-512x512-NV12.c', + 'frame-512x512-RGBA.c', + 'kmscube.c', + 'perfcntrs.c', +) + +cc = meson.get_compiler('c') +dep_m = cc.find_library('m', required : false) +dep_threads = dependency('threads') +dep_libdrm = dependency('libdrm', version : '>=2.4.71') +dep_gbm = dependency('gbm', version : '>=13.0') +dep_egl = dependency('egl') +dep_gles2 = dependency('glesv2') +dep_libpng = dependency('libpng', required : false) + +if dep_libpng.found() + add_project_arguments('-DHAVE_LIBPNG', language : 'c') +endif + + +dep_common = [dep_m, dep_threads, dep_libdrm, dep_gbm, dep_egl, dep_gles2, dep_libpng] + +dep_gst = [] +foreach _dep : [ + 'gstreamer-1.0', + 'gstreamer-plugins-base-1.0', + 'gstreamer-app-1.0', + 'gstreamer-allocators-1.0', + 'gstreamer-video-1.0', +] + dep_gst += dependency(_dep, version : '>= 1.6.0', required : get_option('gstreamer')) +endforeach +dep_gst += dependency('glib-2.0', required : get_option('gstreamer')) + +# See if we found everything we needed +with_gst = true +foreach _dep : dep_gst + if not _dep.found() + with_gst = false + endif +endforeach + +if with_gst + dep_common += dep_gst + sources += files('cube-video.c', 'gst-decoder.c') + add_project_arguments('-DHAVE_GST', language : 'c') + message('Building with gstreamer support') +else + message('Building without gstreamer support') +endif + +executable('kmscube', sources, dependencies : dep_common, install : true) + + +executable('texturator', files( + 'common.c', + 'drm-legacy.c', + 'drm-common.c', + 'perfcntrs.c', # not used, but required to link + 'texturator.c', +), dependencies : dep_common, install : true) diff -Nru kmscube-0.0.0~git20170617/meson_options.txt kmscube-0.0.0~git20210103/meson_options.txt --- kmscube-0.0.0~git20170617/meson_options.txt 1970-01-01 00:00:00.000000000 +0000 +++ kmscube-0.0.0~git20210103/meson_options.txt 2020-09-02 16:50:08.000000000 +0000 @@ -0,0 +1,5 @@ +option( + 'gstreamer', + type : 'feature', + description : 'Enable support for gstreamer and cube-video' +) diff -Nru kmscube-0.0.0~git20170617/perfcntrs.c kmscube-0.0.0~git20210103/perfcntrs.c --- kmscube-0.0.0~git20170617/perfcntrs.c 1970-01-01 00:00:00.000000000 +0000 +++ kmscube-0.0.0~git20210103/perfcntrs.c 2020-09-02 16:50:08.000000000 +0000 @@ -0,0 +1,477 @@ +/* + * Copyright © 2020 Google, 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, 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. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" + +/* Module to collect a specified set of performance counts, and accumulate + * results, using the GL_AMD_performance_monitor extension. + * + * Call start_perfcntrs() before the draw(s) to measure, and end_perfcntrs() + * after the last draw to measure. This can be done multiple times, with + * the results accumulated. + */ + +/** + * Accumulated counter result: + */ +union counter_result { + uint32_t u32; /* GL_UNSIGNED_INT */ + float f; /* GL_FLOAT, GL_PERCENTAGE_AMD */ + uint64_t u64; /* GL_UNSIGNED_INT64_AMD */ +}; + +/** + * Tracking for a requested counter + */ +struct counter { + union counter_result result; + /* index into perfcntrs.groups[gidx].counters[cidx] + * Note that the group_idx/counter_idx is not necessarily the + * same as the group_id/counter_id. + */ + unsigned gidx; + unsigned cidx; +}; + +/** + * Description of gl counter groups and counters: + */ + +struct gl_counter { + char *name; + GLuint counter_id; + GLuint counter_type; + struct counter *counter; /* NULL if this is not a counter we track */ +}; + +struct gl_counter_group { + char *name; + GLuint group_id; + GLint max_active_counters; + GLint num_counters; + struct gl_counter *counters; + + /* number of counters in this group which are enabled: */ + int num_enabled_counters; +}; + +struct gl_monitor { + GLuint id; + bool valid; + bool active; +}; + +/** + * module state + */ +static struct { + const struct egl *egl; + + /* The extension doesn't let us pause/resume a single counter, so + * instead use a sequence of monitors, one per start_perfcntrs()/ + * end_perfcntrs() pair, so that we don't need to immediately read + * back a result, which could cause a stall. + */ + struct gl_monitor monitors[4]; + unsigned current_monitor; + + /* The requested counters to monitor: + */ + unsigned num_counters; + struct counter *counters; + + /* The description of all counter groups and the counters they + * contain, not just including the ones we monitor. + */ + GLint num_groups; + struct gl_counter_group *groups; + +} perfcntr; + +static void get_groups_and_counters(const struct egl *egl) +{ + int n; + + egl->glGetPerfMonitorGroupsAMD(&perfcntr.num_groups, 0, NULL); + perfcntr.groups = calloc(perfcntr.num_groups, sizeof(struct gl_counter_group)); + + GLuint group_ids[perfcntr.num_groups]; + egl->glGetPerfMonitorGroupsAMD(NULL, perfcntr.num_groups, group_ids); + + for (int i = 0; i < perfcntr.num_groups; i++) { + struct gl_counter_group *g = &perfcntr.groups[i]; + + g->group_id = group_ids[i]; + + egl->glGetPerfMonitorGroupStringAMD(g->group_id, 0, &n, NULL); + g->name = malloc(n+1); + egl->glGetPerfMonitorGroupStringAMD(g->group_id, n+1, NULL, g->name); + + egl->glGetPerfMonitorCountersAMD(g->group_id, &g->num_counters, + &g->max_active_counters, 0, NULL); + + g->counters = calloc(g->num_counters, sizeof(struct gl_counter)); + + GLuint counter_ids[g->num_counters]; + egl->glGetPerfMonitorCountersAMD(g->group_id, NULL, NULL, + g->num_counters, counter_ids); + + printf("GROUP[%u]: name=%s, max_active_counters=%u, num_counters=%u\n", + g->group_id, g->name, g->max_active_counters, g->num_counters); + + for (int j = 0; j < g->num_counters; j++) { + struct gl_counter *c = &g->counters[j]; + + c->counter_id = counter_ids[j]; + + egl->glGetPerfMonitorCounterStringAMD(g->group_id, + c->counter_id, 0, &n, NULL); + c->name = malloc(n+1); + egl->glGetPerfMonitorCounterStringAMD(g->group_id, + c->counter_id, n+1, NULL, c->name); + + egl->glGetPerfMonitorCounterInfoAMD(g->group_id, + c->counter_id, GL_COUNTER_TYPE_AMD, + &c->counter_type); + + printf("\tCOUNTER[%u]: name=%s, counter_type=%04x\n", + c->counter_id, c->name, c->counter_type); + } + } +} + +static void find_counter(const char *name, unsigned *group_idx, unsigned *counter_idx) +{ + for (int i = 0; i < perfcntr.num_groups; i++) { + struct gl_counter_group *g = &perfcntr.groups[i]; + + for (int j = 0; j < g->num_counters; j++) { + struct gl_counter *c = &g->counters[j]; + + if (strcmp(name, c->name) == 0) { + *group_idx = i; + *counter_idx = j; + return; + } + } + } + + errx(-1, "Could not find counter: %s", name); +} + +static void add_counter(const char *name) +{ + int idx = perfcntr.num_counters++; + + perfcntr.counters = realloc(perfcntr.counters, + perfcntr.num_counters * sizeof(struct counter)); + + struct counter *c = &perfcntr.counters[idx]; + memset(c, 0, sizeof(*c)); + + find_counter(name, &c->gidx, &c->cidx); + + struct gl_counter_group *g = &perfcntr.groups[c->gidx]; + if (g->num_enabled_counters >= g->max_active_counters) { + errx(-1, "Too many counters in group '%s'", g->name); + } + + g->num_enabled_counters++; +} + +/* parse list of performance counter names, and find their group+counter */ +static void find_counters(const char *perfcntrs) +{ + char *cnames, *s; + + cnames = strdup(perfcntrs); + while ((s = strstr(cnames, ","))) { + char *name = cnames; + s[0] = '\0'; + cnames = &s[1]; + + add_counter(name); + } + + add_counter(cnames); +} + +void init_perfcntrs(const struct egl *egl, const char *perfcntrs) +{ + if (egl_check(egl, glGetPerfMonitorGroupsAMD) || + egl_check(egl, glGetPerfMonitorCountersAMD) || + egl_check(egl, glGetPerfMonitorGroupStringAMD) || + egl_check(egl, glGetPerfMonitorCounterStringAMD) || + egl_check(egl, glGetPerfMonitorCounterInfoAMD) || + egl_check(egl, glGenPerfMonitorsAMD) || + egl_check(egl, glDeletePerfMonitorsAMD) || + egl_check(egl, glSelectPerfMonitorCountersAMD) || + egl_check(egl, glBeginPerfMonitorAMD) || + egl_check(egl, glEndPerfMonitorAMD) || + egl_check(egl, glGetPerfMonitorCounterDataAMD)) { + errx(-1, "AMD_performance_monitor is not supported"); + } + + get_groups_and_counters(egl); + find_counters(perfcntrs); + + /* setup enabled counters.. do this after realloc() stuff, + * otherwise the counter pointer may not be valid: + */ + for (unsigned i = 0; i < perfcntr.num_counters; i++) { + struct counter *c = &perfcntr.counters[i]; + perfcntr.groups[c->gidx].counters[c->cidx].counter = c; + } + + perfcntr.egl = egl; +} + +/* Create perf-monitor, and configure the counters it will monitor */ +static void init_monitor(struct gl_monitor *m) +{ + const struct egl *egl = perfcntr.egl; + + assert(!m->valid); + assert(!m->active); + + egl->glGenPerfMonitorsAMD(1, &m->id); + + for (int i = 0; i < perfcntr.num_groups; i++) { + struct gl_counter_group *g = &perfcntr.groups[i]; + + if (!g->num_enabled_counters) + continue; + + int idx = 0; + GLuint counters[g->num_enabled_counters]; + + for (int j = 0; j < g->num_counters; j++) { + struct gl_counter *c = &g->counters[j]; + + if (!c->counter) + continue; + + assert(idx < g->num_enabled_counters); + counters[idx++] = c->counter_id; + } + + assert(idx == g->num_enabled_counters); + egl->glSelectPerfMonitorCountersAMD(m->id, GL_TRUE, + g->group_id, g->num_enabled_counters, counters); + } + + m->valid = true; +} + +static struct gl_counter *lookup_counter(GLuint group_id, GLuint counter_id) +{ + for (int i = 0; i < perfcntr.num_groups; i++) { + struct gl_counter_group *g = &perfcntr.groups[i]; + + if (g->group_id != group_id) + continue; + + for (int j = 0; j < g->num_counters; j++) { + struct gl_counter *c = &g->counters[j]; + + if (c->counter_id != counter_id) + continue; + + return c; + } + } + + errx(-1, "invalid counter: group_id=%u, counter_id=%u", + group_id, counter_id); +} + +/* Collect monitor results and delete monitor */ +static void finish_monitor(struct gl_monitor *m) +{ + const struct egl *egl = perfcntr.egl; + + assert(m->valid); + assert(!m->active); + + GLuint result_size; + egl->glGetPerfMonitorCounterDataAMD(m->id, GL_PERFMON_RESULT_SIZE_AMD, + sizeof(GLint), &result_size, NULL); + + GLuint *data = malloc(result_size); + + GLsizei bytes_written; + egl->glGetPerfMonitorCounterDataAMD(m->id, GL_PERFMON_RESULT_AMD, + result_size, data, &bytes_written); + + GLsizei idx = 0; + while ((4 * idx) < bytes_written) { + GLuint group_id = data[idx++]; + GLuint counter_id = data[idx++]; + + struct gl_counter *c = lookup_counter(group_id, counter_id); + + assert(c->counter); + + switch(c->counter_type) { + case GL_UNSIGNED_INT: + c->counter->result.u32 += *(uint32_t *)(&data[idx]); + idx += 1; + break; + case GL_FLOAT: + c->counter->result.f += *(float *)(&data[idx]); + idx += 1; + break; + case GL_UNSIGNED_INT64_AMD: + c->counter->result.u64 += *(uint64_t *)(&data[idx]); + idx += 2; + break; + case GL_PERCENTAGE_AMD: + default: + errx(-1, "TODO unhandled counter type: 0x%04x", + c->counter_type); + break; + } + } + + egl->glDeletePerfMonitorsAMD(1, &m->id); + m->valid = false; +} + +void start_perfcntrs(void) +{ + const struct egl *egl = perfcntr.egl; + + if (!egl) { + return; + } + + struct gl_monitor *m = &perfcntr.monitors[perfcntr.current_monitor]; + + /* once we wrap-around and start re-using existing slots, collect + * previous results and delete the monitor before re-using the slot: + */ + if (m->valid) { + finish_monitor(m); + } + + init_monitor(m); + + egl->glBeginPerfMonitorAMD(m->id); + m->active = true; +} + +void end_perfcntrs(void) +{ + const struct egl *egl = perfcntr.egl; + + if (!egl) { + return; + } + + struct gl_monitor *m = &perfcntr.monitors[perfcntr.current_monitor]; + + assert(m->valid); + assert(m->active); + + /* end collection, but defer collecting results to avoid stall: */ + egl->glEndPerfMonitorAMD(m->id); + m->active = false; + + /* move to next slot: */ + perfcntr.current_monitor = + (perfcntr.current_monitor + 1) % ARRAY_SIZE(perfcntr.monitors); +} + +/* collect any remaining perfcntr results.. this should be called + * before computing the elapsed time (passed to dump_perfcntrs()) + * to ensured queued up draws which are monitored complete, ie. + * so that elapsed time covers the entirety of the monitored + * draws. + */ +void finish_perfcntrs(void) +{ + if (!perfcntr.egl) + return; + + /* collect any remaining results, it really doesn't matter the order */ + for (unsigned i = 0; i < ARRAY_SIZE(perfcntr.monitors); i++) { + struct gl_monitor *m = &perfcntr.monitors[i]; + if (m->valid) { + finish_monitor(m); + } + } +} + +void dump_perfcntrs(unsigned nframes, uint64_t elapsed_time_ns) +{ + if (!perfcntr.egl) { + return; + } + + /* print column headers: */ + printf("FPS"); + for (unsigned i = 0; i < perfcntr.num_counters; i++) { + struct counter *c = &perfcntr.counters[i]; + + printf(",%s", perfcntr.groups[c->gidx].counters[c->cidx].name); + } + printf("\n"); + + /* print results: */ + double secs = elapsed_time_ns / (double)NSEC_PER_SEC; + printf("%f", (double)nframes/secs); + for (unsigned i = 0; i < perfcntr.num_counters; i++) { + struct counter *c = &perfcntr.counters[i]; + + GLuint counter_type = + perfcntr.groups[c->gidx].counters[c->cidx].counter_type; + switch (counter_type) { + case GL_UNSIGNED_INT: + printf(",%u", c->result.u32); + break; + case GL_FLOAT: + printf(",%f", c->result.f); + break; + case GL_UNSIGNED_INT64_AMD: + printf(",%"PRIu64, c->result.u64); + break; + case GL_PERCENTAGE_AMD: + default: + errx(-1, "TODO unhandled counter type: 0x%04x", + counter_type); + break; + } + } + printf("\n"); +} diff -Nru kmscube-0.0.0~git20170617/README.md kmscube-0.0.0~git20210103/README.md --- kmscube-0.0.0~git20170617/README.md 1970-01-01 00:00:00.000000000 +0000 +++ kmscube-0.0.0~git20210103/README.md 2020-09-02 16:50:08.000000000 +0000 @@ -0,0 +1,9 @@ +kmscube +======= + +kmscube is a little demonstration program for how to drive bare metal graphics +without a compositor like X11, wayland or similar, using DRM/KMS (kernel mode +setting), GBM (graphics buffer manager) and EGL for rendering content using +OpenGL or OpenGL ES. + +The upstream of kmscube is available at https://gitlab.freedesktop.org/mesa/kmscube/ \ No newline at end of file diff -Nru kmscube-0.0.0~git20170617/texturator.c kmscube-0.0.0~git20210103/texturator.c --- kmscube-0.0.0~git20170617/texturator.c 1970-01-01 00:00:00.000000000 +0000 +++ kmscube-0.0.0~git20210103/texturator.c 2020-09-02 16:50:08.000000000 +0000 @@ -0,0 +1,977 @@ +/* + * 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 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; +#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); +}