| /* |
| * Copyright 2016 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "bs_drm.h" |
| |
| static const useconds_t test_case_display_usec = 2000000; |
| |
| struct test_case { |
| uint32_t format; /* format for allocating buffer object from GBM*/ |
| uint32_t fb_format; /* format used to create DRM framebuffer, 0 indicates same as format */ |
| enum gbm_bo_flags usage; |
| }; |
| |
| const char *format_to_string(uint32_t format) |
| { |
| switch (format) { |
| case GBM_FORMAT_XRGB8888: |
| return "GBM_FORMAT_XRGB8888"; |
| case GBM_FORMAT_ARGB8888: |
| return "GBM_FORMAT_ARGB8888"; |
| default: |
| return "GBM_FORMAT_????????"; |
| } |
| } |
| |
| static void test_case_print(FILE *out, const struct test_case *tcase) |
| { |
| fprintf(out, "format=%s usage=", format_to_string(tcase->format)); |
| bool first = true; |
| if (tcase->usage & GBM_BO_USE_SCANOUT) { |
| fprintf(out, "GBM_BO_USE_SCANOUT"); |
| first = false; |
| } |
| if (tcase->usage & GBM_BO_USE_RENDERING) { |
| fprintf(out, "%sGBM_BO_USE_RENDERING", first ? "" : " | "); |
| first = false; |
| } |
| if (tcase->usage & GBM_BO_USE_LINEAR) { |
| fprintf(out, "%sGBM_BO_USE_LINEAR", first ? "" : " | "); |
| first = false; |
| } |
| if (tcase->fb_format) |
| fprintf(out, " fb_format=%s", format_to_string(tcase->fb_format)); |
| } |
| |
| static void test_case_colors(const struct test_case *tcase, |
| float *colors /* sizeof(colors) / sizeof(colors[0]) == 9 */) |
| { |
| colors[0] = 0.0f; |
| colors[1] = 1.0f; |
| colors[2] = 0.0f; |
| |
| colors[3] = tcase->usage & GBM_BO_USE_SCANOUT ? 1.0f : 0.0f; |
| colors[4] = tcase->usage & GBM_BO_USE_RENDERING ? 0.66f : 0.0f; |
| colors[5] = tcase->usage & GBM_BO_USE_LINEAR ? 1.0f : 0.0f; |
| |
| switch (tcase->format) { |
| case GBM_FORMAT_XRGB8888: |
| colors[6] = 1.0f; |
| colors[7] = 1.0f; |
| break; |
| case GBM_FORMAT_ARGB8888: |
| colors[7] = 1.0f; |
| colors[8] = 1.0f; |
| break; |
| default: |
| colors[6] = 0.33f; |
| colors[7] = 0.33f; |
| colors[8] = 0.33f; |
| break; |
| } |
| } |
| |
| static void bo_lines(uint32_t height, float *lines /* sizeof(lines) / sizeof(lines[0]) == 9 */) |
| { |
| /* |
| The screen is divided into sections using 3 lines as shown. |
| *----------* |
| |\ | / | |
| | \|/ | |
| | X | |
| | /|\ | |
| |/ | \ | |
| *----------* |
| |
| Lines are evaluated as positive or negative in the linear equation: |
| Ax + By - C |
| |
| Where the coffecicents A, B, and C appear in the array in the following order: |
| [ A, B, C ] |
| */ |
| // negative left of the following lines' intersection |
| lines[0] = 1; |
| lines[1] = 0; |
| lines[2] = height / 2; |
| |
| // negative on lower-right triangle section |
| lines[3] = 1; |
| lines[4] = -1; |
| lines[5] = 0; |
| |
| // negative on upper-left triangle section |
| lines[6] = 1; |
| lines[7] = 1; |
| lines[8] = height; |
| } |
| |
| static bool test_case_draw_gl(struct bs_egl *egl, const struct test_case *tcase, struct gbm_bo *bo) |
| { |
| bool success = true; |
| uint32_t width = gbm_bo_get_width(bo); |
| uint32_t height = gbm_bo_get_height(bo); |
| |
| EGLImageKHR image = bs_egl_image_create_gbm(egl, bo); |
| if (image == EGL_NO_IMAGE_KHR) { |
| success = false; |
| bs_debug_error("failed to make image from buffer object"); |
| goto out; |
| } |
| |
| struct bs_egl_fb *fb = bs_egl_fb_new(egl, image); |
| if (!fb) { |
| success = false; |
| bs_debug_error("failed to make rednering framebuffer for buffer object"); |
| bs_egl_image_destroy(egl, &image); |
| goto image_destroy; |
| } |
| |
| const GLchar *vert = |
| "attribute vec2 vPosition;\n" |
| "void main() {\n" |
| " gl_Position = vec4(vPosition, 0, 1);\n" |
| "}\n"; |
| |
| const GLchar *frag = |
| "precision mediump float;\n" |
| "uniform vec3 uColors[3];\n" |
| "uniform vec3 uLines[3];\n" |
| "void main() {\n" |
| " bool left = dot(uLines[0].xy, gl_FragCoord.xy) < uLines[0].z;\n" |
| " bool lower_right = dot(uLines[1].xy, gl_FragCoord.xy) < uLines[1].z;\n" |
| " bool upper_left = dot(uLines[2].xy, gl_FragCoord.xy) < uLines[2].z;\n" |
| " if (left && upper_left)\n" |
| " gl_FragColor = vec4(uColors[0], 1.0);\n" |
| " else if ((left && !upper_left) || (!left && lower_right))\n" |
| " gl_FragColor = vec4(uColors[1], 1.0);\n" |
| " else\n" |
| " gl_FragColor = vec4(uColors[2], 1.0);\n" |
| "}\n"; |
| |
| struct bs_gl_program_create_binding bindings[] = { |
| { 0, "vPosition" }, { 0, NULL }, |
| }; |
| |
| GLuint program = bs_gl_program_create_vert_frag_bind(vert, frag, bindings); |
| if (!program) { |
| success = false; |
| bs_debug_error("failed to compile test case shader program"); |
| goto fb_destroy; |
| } |
| GLint colors_location = glGetUniformLocation(program, "uColors"); |
| GLint lines_location = glGetUniformLocation(program, "uLines"); |
| if (colors_location == -1 || lines_location == -1) { |
| success = false; |
| bs_debug_error("failed to retrieve uniform location"); |
| goto delete_program; |
| } |
| |
| GLfloat colors[9]; |
| test_case_colors(tcase, colors); |
| |
| float lines[9]; |
| bo_lines(height, lines); |
| |
| // clang-format off |
| const GLfloat verts[] = { |
| -1.0f, -1.0f, |
| 2.0f, -1.0f, |
| -1.0f, 2.0f, |
| 2.0f, 2.0f, |
| }; |
| // clang-format on |
| |
| glBindFramebuffer(GL_FRAMEBUFFER, bs_egl_fb_name(fb)); |
| glViewport(0, 0, (GLint)width, (GLint)height); |
| |
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
| glClear(GL_COLOR_BUFFER_BIT); |
| |
| glUseProgram(program); |
| glUniform3fv(colors_location, 3, colors); |
| glUniform3fv(lines_location, 3, lines); |
| |
| glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts); |
| glEnableVertexAttribArray(0); |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| |
| glFinish(); |
| |
| glUseProgram(0); |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| |
| delete_program: |
| glDeleteProgram(program); |
| fb_destroy: |
| bs_egl_fb_destroy(&fb); |
| image_destroy: |
| bs_egl_image_destroy(egl, &image); |
| out: |
| return success; |
| } |
| |
| static bool test_case_draw_dma_buf(const struct test_case *tcase, struct bs_mapper *mapper, |
| struct gbm_bo *bo) |
| { |
| int bo_fd = gbm_bo_get_fd(bo); |
| if (bo_fd < 0) { |
| bs_debug_error("failed to get fd of bo"); |
| return false; |
| } |
| uint32_t width = gbm_bo_get_width(bo); |
| uint32_t height = gbm_bo_get_height(bo); |
| uint32_t stride; |
| void *map_data; |
| uint8_t *ptr = bs_mapper_map(mapper, bo, 0, &map_data, &stride); |
| if (ptr == MAP_FAILED) { |
| bs_debug_error("failed to mmap gbm bo"); |
| return false; |
| } |
| |
| float colors_float[9]; |
| test_case_colors(tcase, colors_float); |
| uint8_t colors[9]; |
| for (size_t i = 0; i < 9; i++) |
| colors[i] = (uint8_t)(colors_float[i] * 255.0f); |
| |
| float lines[9]; |
| bo_lines(height, lines); |
| |
| for (uint32_t y = 0; y < height; y++) { |
| uint8_t *row_ptr = &ptr[y * stride]; |
| for (uint32_t x = 0; x < width; x++) { |
| bool left = lines[0] * (float)x + lines[1] * (float)y < lines[2]; |
| bool lower_right = lines[3] * (float)x + lines[4] * (float)y < lines[5]; |
| bool upper_left = lines[6] * (float)x + lines[7] * (float)y < lines[8]; |
| |
| int color_index = 0; |
| if (left && upper_left) |
| color_index = 0; |
| else if ((left && !upper_left) || (!left && lower_right)) |
| color_index = 1; |
| else |
| color_index = 2; |
| |
| row_ptr[x * 4 + 0] = colors[color_index * 3 + 2]; |
| row_ptr[x * 4 + 1] = colors[color_index * 3 + 1]; |
| row_ptr[x * 4 + 2] = colors[color_index * 3 + 0]; |
| row_ptr[x * 4 + 3] = 0; |
| } |
| } |
| |
| bs_mapper_unmap(mapper, bo, map_data); |
| |
| return true; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| const struct test_case tcases[] = { |
| { GBM_FORMAT_XRGB8888, 0, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING }, |
| { GBM_FORMAT_XRGB8888, 0, GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR }, |
| { GBM_FORMAT_ARGB8888, GBM_FORMAT_XRGB8888, |
| GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING }, |
| { GBM_FORMAT_ARGB8888, GBM_FORMAT_XRGB8888, |
| GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR }, |
| }; |
| const size_t tcase_count = BS_ARRAY_LEN(tcases); |
| |
| int display_fd = bs_drm_open_main_display(); |
| if (display_fd < 0) { |
| bs_debug_error("failed to open card for display"); |
| return 1; |
| } |
| |
| struct gbm_device *gbm = gbm_create_device(display_fd); |
| if (!gbm) { |
| bs_debug_error("failed to create gbm device"); |
| return 1; |
| } |
| |
| struct bs_drm_pipe pipe = { 0 }; |
| if (!bs_drm_pipe_make(display_fd, &pipe)) { |
| bs_debug_error("failed to make pipe"); |
| return 1; |
| } |
| |
| drmModeConnector *connector = drmModeGetConnector(display_fd, pipe.connector_id); |
| drmModeModeInfo *mode = &connector->modes[0]; |
| uint32_t width = mode->hdisplay; |
| uint32_t height = mode->vdisplay; |
| |
| struct bs_egl *egl = bs_egl_new(); |
| if (!bs_egl_setup(egl, NULL)) { |
| bs_debug_error("failed to setup egl context"); |
| return 1; |
| } |
| |
| uint32_t fbs[BS_ARRAY_LEN(tcases)] = { 0 }; |
| struct bs_mapper *mapper = bs_mapper_dma_buf_new(); |
| if (mapper == NULL) { |
| bs_debug_error("failed to create mapper object"); |
| return 1; |
| } |
| |
| bool all_pass = true; |
| for (size_t tcase_index = 0; tcase_index < tcase_count; tcase_index++) { |
| const struct test_case *tcase = &tcases[tcase_index]; |
| |
| struct gbm_bo *bo = gbm_bo_create(gbm, width, height, tcase->format, tcase->usage); |
| |
| if (!bo) { |
| all_pass = false; |
| printf("failed test case: "); |
| test_case_print(stdout, tcase); |
| printf("\n"); |
| continue; |
| } |
| |
| struct bs_drm_fb_builder *fb_builder = bs_drm_fb_builder_new(); |
| bs_drm_fb_builder_gbm_bo(fb_builder, bo); |
| if (tcase->fb_format) |
| bs_drm_fb_builder_format(fb_builder, tcase->fb_format); |
| fbs[tcase_index] = bs_drm_fb_builder_create_fb(fb_builder); |
| bs_drm_fb_builder_destroy(&fb_builder); |
| if (!fbs[tcase_index]) { |
| bs_debug_error("failed to create framebuffer from buffer object"); |
| return 1; |
| } |
| |
| if (tcase->usage & GBM_BO_USE_LINEAR) { |
| if (!test_case_draw_dma_buf(tcase, mapper, bo)) { |
| bs_debug_error("failed to draw to buffer using vgem"); |
| return 1; |
| } |
| } else if (tcase->usage & GBM_BO_USE_RENDERING) { |
| if (!test_case_draw_gl(egl, tcase, bo)) { |
| bs_debug_error("failed to draw to buffer using GL"); |
| return 1; |
| } |
| } |
| |
| // Reference held in kernel by the frame buffer. |
| gbm_bo_destroy(bo); |
| } |
| |
| bs_mapper_destroy(mapper); |
| |
| for (size_t tcase_index = 0; tcase_index < tcase_count; tcase_index++) { |
| const struct test_case *tcase = &tcases[tcase_index]; |
| uint32_t fb_id = fbs[tcase_index]; |
| |
| if (fb_id == 0) |
| continue; |
| |
| printf("displaying test case: "); |
| test_case_print(stdout, tcase); |
| printf("\n"); |
| |
| int ret = drmModeSetCrtc(display_fd, pipe.crtc_id, fb_id, 0 /* x */, 0 /* y */, |
| &pipe.connector_id, 1 /* connector count */, mode); |
| if (ret) { |
| bs_debug_error("failed to set crtc: %d", ret); |
| return 1; |
| } |
| usleep(test_case_display_usec); |
| } |
| |
| for (size_t tcase_index = 0; tcase_index < tcase_count; tcase_index++) |
| if (fbs[tcase_index] != 0) |
| drmModeRmFB(display_fd, fbs[tcase_index]); |
| |
| bs_egl_destroy(&egl); |
| |
| return all_pass ? 0 : 2; |
| } |