diff options
author | Eric Anholt <eric@anholt.net> | 2018-06-01 17:10:14 -0700 |
---|---|---|
committer | Eric Anholt <eric@anholt.net> | 2018-07-12 17:23:40 -0700 |
commit | f8c24083a0124773bc84e14d7930eaa6f35ddfb8 (patch) | |
tree | 72f0d3c83048ffe923e9bf9081f46a2324ddf4b5 | |
parent | 1aab91f18378888c3160207c22ca86046ebe43e1 (diff) |
egl: Add a test for rendering jobs from multiple processes.rpi2-piglit
-rw-r--r-- | tests/egl/CMakeLists.gl.txt | 1 | ||||
-rw-r--r-- | tests/egl/egl-multiprocess-render.c | 327 |
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); +} |