summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Anholt <eric@anholt.net>2018-06-01 17:10:14 -0700
committerEric Anholt <eric@anholt.net>2018-07-12 17:23:40 -0700
commitf8c24083a0124773bc84e14d7930eaa6f35ddfb8 (patch)
tree72f0d3c83048ffe923e9bf9081f46a2324ddf4b5
parent1aab91f18378888c3160207c22ca86046ebe43e1 (diff)
egl: Add a test for rendering jobs from multiple processes.rpi2-piglit
-rw-r--r--tests/egl/CMakeLists.gl.txt1
-rw-r--r--tests/egl/egl-multiprocess-render.c327
2 files changed, 328 insertions, 0 deletions
diff --git a/tests/egl/CMakeLists.gl.txt b/tests/egl/CMakeLists.gl.txt
index 6ba88427d..c247c79bb 100644
--- a/tests/egl/CMakeLists.gl.txt
+++ b/tests/egl/CMakeLists.gl.txt
@@ -33,6 +33,7 @@ IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
target_link_libraries(egl-invalid-attr pthread)
piglit_add_executable (egl-context-priority egl-context-priority.c)
target_link_libraries(egl-context-priority pthread)
+ piglit_add_executable (egl-multiprocess-render egl-multiprocess-render.c)
ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
# vim: ft=cmake:
diff --git a/tests/egl/egl-multiprocess-render.c b/tests/egl/egl-multiprocess-render.c
new file mode 100644
index 000000000..cc9f668f2
--- /dev/null
+++ b/tests/egl/egl-multiprocess-render.c
@@ -0,0 +1,327 @@
+/*
+ * Copyright © 2009 Intel Corporation
+ * Copyright © 2018 Broadcom
+ *
+ * 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.
+ *
+ * Authors:
+ * Eric Anholt <eric@anholt.net>
+ *
+ */
+
+/** @file egl-multiprocess-render.c
+ *
+ * Test that rendering from multiple contexts in separate processes
+ * completes correctly.
+ *
+ * With the AMDGPU scheduler, it has proven easy for drivers to make
+ * mistakes in their seqno handling such that jobs may be considered
+ * done before they actually are. For a tiled renderer where the
+ * binning and rendering are separate jobs, this means we may try to
+ * render before the bin is complete, which is easy to probe with a
+ * test like this.
+ *
+ * We use separate processes so that each DRI driver opens its own fd,
+ * and gets its own AMDGPU scheduler entity (job queue). If we're on
+ * the same fd, our jobs would get scheduled in order by default,
+ * anyway.
+ */
+
+#include "piglit-util-gl.h"
+#include "piglit-util-egl.h"
+#include <sys/wait.h>
+#include <unistd.h>
+
+int piglit_width = 256, piglit_height = 256;
+
+#define CHILD_READY 0xab
+#define PARENT_READY 0xcd
+
+#define ITERS 1000
+
+struct rect {
+ int x, y, w, h;
+};
+
+struct process_state {
+ int index;
+ int parent_ready_pipe[2];
+ int child_ready_pipe[2];
+ struct rect *rects;
+ int pid;
+};
+
+struct process_state *child_state;
+
+void
+child_func(struct process_state *child_state)
+{
+ EGLDisplay dpy;
+ EGLContext ctx;
+ GLuint oqs[ITERS];
+ uint8_t data;
+ int bytes;
+ EGLint attr[] = { EGL_NONE };
+ EGLint major, minor;
+ bool ok;
+ GLuint tex, fbo;
+ GLenum status;
+
+ const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+ if (!strstr(exts, "EGL_MESA_platform_surfaceless"))
+ piglit_report_result(PIGLIT_SKIP);
+
+ dpy = piglit_egl_get_default_display(EGL_PLATFORM_SURFACELESS_MESA);
+
+ ok = eglInitialize(dpy, &major, &minor);
+ if (!ok) {
+ fprintf(stderr, "Child %d to initialize EGL\n",
+ child_state->index);
+ exit(1);
+ }
+
+ eglBindAPI(EGL_OPENGL_API);
+
+ ctx = eglCreateContext(dpy, EGL_NO_CONFIG_MESA, EGL_NO_CONTEXT, attr);
+ if (!ctx) {
+ fprintf(stderr, "Child %d failed to create context\n",
+ child_state->index);
+ exit(1);
+ }
+
+ ok = eglMakeCurrent(dpy, NULL, NULL, ctx);
+ if (!ok) {
+ fprintf(stderr, "Child %d to make context current\n",
+ child_state->index);
+ exit(1);
+ }
+
+ piglit_dispatch_default_init(PIGLIT_DISPATCH_GL);
+
+ piglit_require_extension("GL_ARB_occlusion_query");
+
+ /* Set up a FBO to render to since we're surfaceless. */
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glGenTextures(1, &tex);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
+ piglit_width, piglit_height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ glFramebufferTexture2D(GL_FRAMEBUFFER,
+ GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D,
+ tex,
+ 0);
+ status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE) {
+ fprintf(stderr,
+ "Child %d framebuffer status is incomplete",
+ child_state->index);
+ exit(1);
+ }
+
+ glGenQueries(ITERS, oqs);
+
+ glViewport(0, 0, piglit_width, piglit_height);
+ piglit_ortho_projection(piglit_width, piglit_height, GL_FALSE);
+ glClearColor(0.5, 0.5, 0.5, 0.5);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ /* Warm up the GL */
+ glColor4f(0.0, 1.0, 0.0, 0.0);
+ glBeginQuery(GL_SAMPLES_PASSED, oqs[0]);
+ piglit_draw_rect(0, 0, 1, 1);
+ glEndQuery(GL_SAMPLES_PASSED);
+ glFinish();
+
+ data = CHILD_READY;
+ bytes = write(child_state->child_ready_pipe[1], &data, 1);
+ if (bytes != 1) {
+ fprintf(stderr, "Child %d failed to signal itself ready.\n",
+ child_state->index);
+ exit(1);
+ }
+
+ bytes = read(child_state->parent_ready_pipe[0], &data, 1);
+ if (bytes != 1 || data != PARENT_READY) {
+ fprintf(stderr, "Child %d failed to get parent ready.\n",
+ child_state->index);
+ exit(1);
+ }
+
+ fprintf(stderr, "Child %d starting\n", child_state->index);
+
+ for (int i = 0; i < ITERS; i++) {
+ struct rect *rect = &child_state->rects[i];
+
+ glBeginQuery(GL_SAMPLES_PASSED, oqs[i]);
+ piglit_draw_rect(rect->x, rect->y, rect->w, rect->h);
+ glEndQuery(GL_SAMPLES_PASSED);
+
+ /* Flush to the kernel, since we're trying to put pressure on
+ * the kernel's job scheduling.
+ */
+ glFlush();
+
+ /* Wait for some previous rendering to complete, so
+ * we're spending time running jobs in the kernel,
+ * instead of just spending time allocating and
+ * freeing BOs in the kernel.
+ */
+ if (i >= 5) {
+ GLuint result;
+
+ glGetQueryObjectuiv(oqs[i - 5], GL_QUERY_RESULT,
+ &result);
+ }
+ }
+
+ for (int i = 0; i < ITERS; i++) {
+ struct rect *rect = &child_state->rects[i];
+ GLuint result;
+
+ glGetQueryObjectuiv(oqs[i], GL_QUERY_RESULT, &result);
+
+ if (result != rect->w * rect->h) {
+ fprintf(stderr,
+ "rect %d (%dx%d @ %d,%d) drew %d samples\n",
+ i,
+ rect->w, rect->h,
+ rect->x, rect->y,
+ result);
+ exit(1);
+ }
+ }
+
+ fprintf(stderr, "Child %d finished\n", child_state->index);
+
+ /* Exit here, so that we don't end up with multiple
+ * conflicting piglit results reported in the log.
+ */
+ exit(0);
+}
+
+#define NPROC 4
+
+int
+main(int argc, char **argv)
+{
+ struct process_state procs[NPROC];
+
+ for (int i = 0; i < NPROC; i++) {
+ int ret;
+
+ procs[i].index = i;
+
+ ret = pipe(procs[i].parent_ready_pipe);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to create pipe: %s\n",
+ strerror(errno));
+ piglit_report_result(PIGLIT_FAIL);
+ }
+
+ ret = pipe(procs[i].child_ready_pipe);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to create pipe: %s\n",
+ strerror(errno));
+ piglit_report_result(PIGLIT_FAIL);
+ }
+
+ /* Set up the rects to be drawn by each process. */
+ procs[i].rects = calloc(ITERS, sizeof(*procs[i].rects));
+ for (int j = 0; j < ITERS; j++) {
+ procs[i].rects[j].x = rand() % piglit_width;
+ procs[i].rects[j].y = rand() % piglit_height;
+ procs[i].rects[j].w = (rand() %
+ (piglit_width -
+ procs[i].rects[j].x) + 1);
+ procs[i].rects[j].h = (rand() %
+ (piglit_height -
+ procs[i].rects[j].y) + 1);
+ }
+ }
+
+ /* Fork off the children and get them started in display setup. */
+ for (int i = 0; i < NPROC; i++) {
+ procs[i].pid = fork();
+ if (procs[i].pid == 0)
+ child_func(&procs[i]);
+ else if (procs[i].pid == -1) {
+ fprintf(stderr, "Failed to create child process: %s\n",
+ strerror(errno));
+ piglit_report_result(PIGLIT_FAIL);
+ }
+ }
+
+ /* Wait for the children to come up. */
+ for (int i = 0; i < NPROC; i++) {
+ uint8_t data;
+ int bytes = read(procs[i].child_ready_pipe[0], &data, 1);
+
+ if (bytes != 1 || data != CHILD_READY) {
+ fprintf(stderr,
+ "Parent failed to get child %d ready.\n", i);
+
+ /* XXX: waitpid to see if they skipped. */
+ piglit_report_result(PIGLIT_FAIL);
+ }
+ }
+
+ /* Signal the children to start drawing. */
+ for (int i = 0; i < NPROC; i++) {
+ uint8_t data = PARENT_READY;
+ int bytes = write(procs[i].parent_ready_pipe[1], &data, 1);
+
+ if (bytes != 1) {
+ fprintf(stderr,
+ "Parent failed to signal ready to child %d\n", i);
+ piglit_report_result(PIGLIT_FAIL);
+ }
+ }
+
+ /* Collect error status from the children. */
+ for (int i = 0; i < NPROC; i++) {
+ int wstatus;
+ int ret = waitpid(procs[i].pid, &wstatus, 0);
+
+ if (ret < 0) {
+ fprintf(stderr,
+ "Error waiting for child %d (pid %d) to exit\n",
+ i, procs[i].pid);
+ piglit_report_result(PIGLIT_FAIL);
+ }
+
+ if (!WIFEXITED(wstatus)) {
+ fprintf(stderr, "Child %d exited uncleanly\n", i);
+ piglit_report_result(PIGLIT_FAIL);
+ }
+
+ if (WEXITSTATUS(wstatus) != 0) {
+ fprintf(stderr, "Child %d exited with error %d\n", i,
+ WEXITSTATUS(wstatus));
+ piglit_report_result(PIGLIT_FAIL);
+ }
+ }
+
+ piglit_report_result(PIGLIT_PASS);
+}