blob: 6521de6b66ae4b33e795fe6a133d3185a569dfa7 [file] [log] [blame]
/*
* 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;
}