Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2017 The Chromium OS Authors. All rights reserved. |
| 3 | * Use of this source code is governed by a BSD-style license that can be |
| 4 | * found in the LICENSE file. |
| 5 | */ |
| 6 | |
| 7 | /* |
| 8 | The mapped_texture_test test consists of: |
| 9 | * Importing external buffers as EGL Images |
| 10 | * Drawing an ellipse using the CPU |
| 11 | * Binding CPU drawn buffer as a texture and sampling from it |
| 12 | * Using KMS to scanout the resultant framebuffer |
| 13 | */ |
| 14 | |
| 15 | #include <getopt.h> |
| 16 | |
| 17 | #include "bs_drm.h" |
| 18 | |
| 19 | // double buffering |
| 20 | #define NUM_BUFFERS 2 |
| 21 | |
| 22 | struct offscreen_buffer { |
| 23 | struct gbm_bo *bo; |
| 24 | GLuint tex; |
| 25 | EGLImageKHR image; |
| 26 | const struct bs_draw_format *draw_format; |
| 27 | }; |
| 28 | |
| 29 | struct framebuffer { |
| 30 | struct gbm_bo *bo; |
| 31 | uint32_t fb_id; |
| 32 | EGLImageKHR image; |
| 33 | struct bs_egl_fb *egl_fb; |
| 34 | }; |
| 35 | |
| 36 | struct gl_resources { |
| 37 | GLuint program; |
| 38 | GLuint vbo; |
| 39 | }; |
| 40 | |
| 41 | // clang-format off |
| 42 | static const GLfloat vertices[] = { |
| 43 | // x y u v |
| 44 | -0.25f, -0.25f, 0.0f, 0.0f, // Bottom left |
| 45 | -0.25f, 0.25f, 0.0f, 1.0f, // Top left |
| 46 | 0.25f, 0.25f, 1.0f, 1.0f, // Top right |
| 47 | 0.25f, -0.25f, 1.0f, 0.0f, // Bottom Right |
| 48 | }; |
| 49 | |
| 50 | static const int binding_xy = 0; |
| 51 | static const int binding_uv = 1; |
| 52 | |
| 53 | static const GLubyte indices[] = { |
| 54 | 0, 1, 2, |
| 55 | 0, 2, 3 |
| 56 | }; |
| 57 | |
| 58 | // clang-format on |
| 59 | |
| 60 | static const GLchar *vert = |
| 61 | "attribute vec2 xy;\n" |
| 62 | "attribute vec2 uv;\n" |
| 63 | "varying vec2 tex_coordinate;\n" |
| 64 | "void main() {\n" |
| 65 | " gl_Position = vec4(xy, 0, 1);\n" |
| 66 | " tex_coordinate = uv;\n" |
| 67 | "}\n"; |
| 68 | |
| 69 | static const GLchar *frag = |
| 70 | "precision mediump float;\n" |
| 71 | "uniform sampler2D ellipse;\n" |
| 72 | "varying vec2 tex_coordinate;\n" |
| 73 | "void main() {\n" |
| 74 | " gl_FragColor = texture2D(ellipse, tex_coordinate);\n" |
| 75 | "}\n"; |
| 76 | |
| 77 | static bool create_framebuffer(int display_fd, struct gbm_device *gbm, struct bs_egl *egl, |
| 78 | uint32_t width, uint32_t height, struct framebuffer *fb) |
| 79 | { |
| 80 | fb->bo = gbm_bo_create(gbm, width, height, GBM_FORMAT_XRGB8888, |
| 81 | GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); |
| 82 | if (!fb->bo) { |
| 83 | bs_debug_error("failed to create a gbm buffer."); |
| 84 | goto delete_gl_fb; |
| 85 | } |
| 86 | |
| 87 | fb->fb_id = bs_drm_fb_create_gbm(fb->bo); |
| 88 | if (!fb->fb_id) { |
| 89 | bs_debug_error("failed to create framebuffer from buffer object"); |
| 90 | goto delete_gl_image; |
| 91 | } |
| 92 | |
| 93 | fb->image = bs_egl_image_create_gbm(egl, fb->bo); |
| 94 | if (fb->image == EGL_NO_IMAGE_KHR) { |
| 95 | bs_debug_error("failed to make image from buffer object"); |
| 96 | goto delete_fb; |
| 97 | } |
| 98 | |
| 99 | fb->egl_fb = bs_egl_fb_new(egl, fb->image); |
| 100 | if (!fb->egl_fb) { |
| 101 | bs_debug_error("failed to make rednering framebuffer for buffer object"); |
| 102 | goto delete_gbm_bo; |
| 103 | } |
| 104 | |
| 105 | return true; |
| 106 | |
| 107 | delete_gl_fb: |
| 108 | bs_egl_fb_destroy(&fb->egl_fb); |
| 109 | delete_gl_image: |
| 110 | bs_egl_image_destroy(egl, &fb->image); |
| 111 | delete_fb: |
| 112 | drmModeRmFB(display_fd, fb->fb_id); |
| 113 | delete_gbm_bo: |
| 114 | gbm_bo_destroy(fb->bo); |
| 115 | return false; |
| 116 | } |
| 117 | |
| 118 | static bool add_offscreen_texture(struct gbm_device *gbm, struct bs_egl *egl, |
| 119 | struct offscreen_buffer *buffer, uint32_t width, uint32_t height, |
| 120 | uint32_t flags) |
| 121 | { |
| 122 | buffer->bo = |
| 123 | gbm_bo_create(gbm, width, height, bs_get_pixel_format(buffer->draw_format), flags); |
| 124 | if (!buffer->bo) { |
| 125 | bs_debug_error("failed to allocate offscreen buffer object: format=%s \n", |
| 126 | bs_get_format_name(buffer->draw_format)); |
| 127 | goto destroy_offscreen_buffer; |
| 128 | } |
| 129 | |
| 130 | buffer->image = bs_egl_image_create_gbm(egl, buffer->bo); |
| 131 | if (buffer->image == EGL_NO_IMAGE_KHR) { |
| 132 | bs_debug_error("failed to create offscreen egl image"); |
| 133 | goto destroy_offscreen_bo; |
| 134 | } |
| 135 | |
| 136 | glActiveTexture(GL_TEXTURE1); |
| 137 | glGenTextures(1, &buffer->tex); |
| 138 | glBindTexture(GL_TEXTURE_2D, buffer->tex); |
| 139 | |
| 140 | if (!bs_egl_target_texture2D(egl, buffer->image)) { |
| 141 | bs_debug_error("failed to import egl image as texture"); |
| 142 | goto destroy_offscreen_image; |
| 143 | } |
| 144 | |
| 145 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| 146 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| 147 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| 148 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| 149 | |
| 150 | return true; |
| 151 | |
| 152 | destroy_offscreen_image: |
| 153 | glDeleteTextures(1, &buffer->tex); |
| 154 | bs_egl_image_destroy(egl, &buffer->image); |
| 155 | destroy_offscreen_bo: |
| 156 | gbm_bo_destroy(buffer->bo); |
| 157 | destroy_offscreen_buffer: |
| 158 | return false; |
| 159 | } |
| 160 | |
| 161 | static bool init_gl(struct bs_egl_fb *fb, uint32_t width, uint32_t height, |
| 162 | struct gl_resources *resources) |
| 163 | { |
| 164 | struct bs_gl_program_create_binding bindings[] = { |
| 165 | { binding_xy, "xy" }, { binding_uv, "uv" }, { 2, NULL }, |
| 166 | }; |
| 167 | |
| 168 | resources->program = bs_gl_program_create_vert_frag_bind(vert, frag, bindings); |
| 169 | if (!resources->program) { |
| 170 | bs_debug_error("failed to compile shader program"); |
| 171 | return false; |
| 172 | } |
| 173 | |
| 174 | glGenBuffers(1, &resources->vbo); |
| 175 | glBindBuffer(GL_ARRAY_BUFFER, resources->vbo); |
| 176 | glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); |
| 177 | |
| 178 | glBindFramebuffer(GL_FRAMEBUFFER, bs_egl_fb_name(fb)); |
| 179 | glViewport(0, 0, (GLint)width, (GLint)height); |
| 180 | |
Gurchetan Singh | 7018a3c | 2017-04-10 17:30:12 -0700 | [diff] [blame] | 181 | glClearColor(1.0f, 1.0f, 1.0f, 1.0f); |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 182 | glClear(GL_COLOR_BUFFER_BIT); |
| 183 | |
| 184 | glUseProgram(resources->program); |
| 185 | |
| 186 | glUniform1i(glGetUniformLocation(resources->program, "ellipse"), 1); |
| 187 | glEnableVertexAttribArray(binding_xy); |
| 188 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0); |
| 189 | |
| 190 | glEnableVertexAttribArray(binding_uv); |
| 191 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), |
| 192 | (void *)(2 * sizeof(GLfloat))); |
| 193 | return true; |
| 194 | } |
| 195 | |
Gurchetan Singh | 7018a3c | 2017-04-10 17:30:12 -0700 | [diff] [blame] | 196 | static void draw_textured_quad(GLuint tex) |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 197 | { |
| 198 | glClear(GL_COLOR_BUFFER_BIT); |
| 199 | glActiveTexture(GL_TEXTURE1); |
| 200 | glBindTexture(GL_TEXTURE_2D, tex); |
| 201 | glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, indices); |
| 202 | } |
| 203 | |
| 204 | static const struct option longopts[] = { |
| 205 | { "help", no_argument, NULL, 'h' }, |
| 206 | { "format", required_argument, NULL, 'f' }, |
| 207 | { "dma-buf", no_argument, NULL, 'b' }, |
| 208 | { "gem", no_argument, NULL, 'g' }, |
| 209 | { "dumb", no_argument, NULL, 'd' }, |
| 210 | { "tiled", no_argument, NULL, 't' }, |
| 211 | { 0, 0, 0, 0 }, |
| 212 | }; |
| 213 | |
| 214 | static void print_help(const char *argv0) |
| 215 | { |
| 216 | printf("Usage: %s [OPTIONS]\n", argv0); |
| 217 | printf(" -h, --help Print help.\n"); |
| 218 | printf(" -f, --format FOURCC format of texture (defaults to ARGB8888)\n"); |
Shirish S | 37b5745 | 2017-07-10 10:35:02 +0530 | [diff] [blame] | 219 | printf(" -b, --dma-buf Use dma-buf mmap.\n"); |
| 220 | printf(" -g, --gem Use GEM map(by default).\n"); |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 221 | printf(" -d, --dumb Use dump map.\n"); |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 222 | } |
| 223 | |
| 224 | static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, |
| 225 | void *data) |
| 226 | { |
| 227 | int *waiting_for_flip = data; |
| 228 | *waiting_for_flip = 0; |
| 229 | } |
| 230 | |
| 231 | void flush_egl(struct bs_egl *egl, EGLImageKHR image) |
| 232 | { |
| 233 | bs_egl_image_flush_external(egl, image); |
| 234 | EGLSyncKHR sync = bs_egl_create_sync(egl, EGL_SYNC_FENCE_KHR, NULL); |
| 235 | bs_egl_wait_sync(egl, sync, 0, EGL_FOREVER_KHR); |
| 236 | bs_egl_destroy_sync(egl, sync); |
| 237 | } |
| 238 | |
| 239 | int main(int argc, char **argv) |
| 240 | { |
| 241 | int ret = 1; |
| 242 | int display_fd = bs_drm_open_main_display(); |
| 243 | if (display_fd < 0) { |
| 244 | bs_debug_error("failed to open card for display"); |
| 245 | goto out; |
| 246 | } |
| 247 | |
| 248 | struct offscreen_buffer buffer; |
| 249 | buffer.draw_format = bs_get_draw_format_from_name("ARGB8888"); |
| 250 | struct bs_mapper *mapper = NULL; |
Gurchetan Singh | 18a0599 | 2017-11-08 15:43:57 -0800 | [diff] [blame] | 251 | uint32_t flags = GBM_BO_USE_TEXTURING; |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 252 | |
| 253 | int c; |
Gurchetan Singh | 18a0599 | 2017-11-08 15:43:57 -0800 | [diff] [blame] | 254 | while ((c = getopt_long(argc, argv, "f:bgdh", longopts, NULL)) != -1) { |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 255 | switch (c) { |
| 256 | case 'f': |
| 257 | if (!bs_parse_draw_format(optarg, &buffer.draw_format)) { |
| 258 | printf("choose the default format ARGB8888\n"); |
| 259 | } |
| 260 | printf("format=%s\n", bs_get_format_name(buffer.draw_format)); |
| 261 | break; |
| 262 | case 'b': |
| 263 | mapper = bs_mapper_dma_buf_new(); |
Gurchetan Singh | 18a0599 | 2017-11-08 15:43:57 -0800 | [diff] [blame] | 264 | flags |= GBM_BO_USE_LINEAR; |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 265 | printf("using dma-buf mmap\n"); |
| 266 | break; |
| 267 | case 'g': |
| 268 | mapper = bs_mapper_gem_new(); |
Gurchetan Singh | 18a0599 | 2017-11-08 15:43:57 -0800 | [diff] [blame] | 269 | flags |= GBM_BO_USE_SW_WRITE_OFTEN; |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 270 | printf("using GEM map\n"); |
| 271 | break; |
| 272 | case 'd': |
| 273 | mapper = bs_mapper_dumb_new(display_fd); |
Gurchetan Singh | 18a0599 | 2017-11-08 15:43:57 -0800 | [diff] [blame] | 274 | flags |= GBM_BO_USE_LINEAR; |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 275 | printf("using dumb map\n"); |
| 276 | break; |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 277 | case 'h': |
| 278 | default: |
| 279 | print_help(argv[0]); |
| 280 | goto destroy_display_fd; |
| 281 | } |
| 282 | } |
| 283 | |
Shirish S | 37b5745 | 2017-07-10 10:35:02 +0530 | [diff] [blame] | 284 | // Use gem map mapper by default, in case any arguments aren't selected. |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 285 | if (!mapper) { |
Shirish S | 37b5745 | 2017-07-10 10:35:02 +0530 | [diff] [blame] | 286 | mapper = bs_mapper_gem_new(); |
Gurchetan Singh | 18a0599 | 2017-11-08 15:43:57 -0800 | [diff] [blame] | 287 | flags |= GBM_BO_USE_SW_WRITE_OFTEN; |
Shirish S | 37b5745 | 2017-07-10 10:35:02 +0530 | [diff] [blame] | 288 | printf("using GEM map\n"); |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 289 | } |
| 290 | |
| 291 | if (!mapper) { |
| 292 | bs_debug_error("failed to create mapper object"); |
| 293 | goto destroy_display_fd; |
| 294 | } |
| 295 | |
| 296 | uint32_t width; |
| 297 | uint32_t height; |
| 298 | |
| 299 | struct gbm_device *gbm = gbm_create_device(display_fd); |
| 300 | if (!gbm) { |
| 301 | bs_debug_error("failed to create gbm device"); |
| 302 | goto destroy_mapper; |
| 303 | } |
| 304 | |
| 305 | struct bs_drm_pipe pipe = { 0 }; |
| 306 | if (!bs_drm_pipe_make(display_fd, &pipe)) { |
| 307 | bs_debug_error("failed to make pipe"); |
| 308 | goto destroy_gbm_device; |
| 309 | } |
| 310 | |
| 311 | drmModeConnector *connector = drmModeGetConnector(display_fd, pipe.connector_id); |
| 312 | drmModeModeInfo *mode = &connector->modes[0]; |
| 313 | width = mode->hdisplay; |
| 314 | height = mode->vdisplay; |
| 315 | |
| 316 | struct bs_egl *egl = bs_egl_new(); |
Dominik Behr | 515ead5 | 2019-06-03 15:23:01 -0700 | [diff] [blame] | 317 | if (!bs_egl_setup(egl, NULL)) { |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 318 | bs_debug_error("failed to setup egl context"); |
| 319 | goto destroy_gbm_device; |
| 320 | } |
| 321 | |
| 322 | struct framebuffer fbs[NUM_BUFFERS] = {}; |
| 323 | uint32_t front_buffer = 0; |
| 324 | for (size_t i = 0; i < NUM_BUFFERS; i++) { |
| 325 | if (!create_framebuffer(display_fd, gbm, egl, width, height, &fbs[i])) { |
| 326 | bs_debug_error("failed to create framebuffer"); |
| 327 | goto delete_framebuffers; |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | if (!add_offscreen_texture(gbm, egl, &buffer, width / 4, height / 4, flags)) { |
| 332 | bs_debug_error("failed to create offscreen texture"); |
| 333 | goto destroy_offscreen_buffer; |
| 334 | } |
| 335 | |
| 336 | const struct framebuffer *back_fb = &fbs[front_buffer ^ 1]; |
| 337 | struct gl_resources resources; |
| 338 | if (!init_gl(back_fb->egl_fb, width, height, &resources)) { |
| 339 | bs_debug_error("failed to initialize GL resources.\n"); |
| 340 | goto destroy_gl_resources; |
| 341 | } |
| 342 | |
| 343 | flush_egl(egl, back_fb->image); |
| 344 | |
| 345 | ret = drmModeSetCrtc(display_fd, pipe.crtc_id, fbs[front_buffer].fb_id, 0 /* x */, |
| 346 | 0 /* y */, &pipe.connector_id, 1 /* connector count */, mode); |
| 347 | if (ret) { |
| 348 | bs_debug_error("failed to set crtc: %d", ret); |
| 349 | goto destroy_gl_resources; |
| 350 | } |
| 351 | |
| 352 | drmEventContext evctx = { |
| 353 | .version = DRM_EVENT_CONTEXT_VERSION, .page_flip_handler = page_flip_handler, |
| 354 | }; |
| 355 | fd_set fds; |
| 356 | |
| 357 | // The test takes about 2 seconds to complete. |
| 358 | const size_t test_frames = 120; |
| 359 | for (size_t i = 0; i < test_frames; i++) { |
| 360 | int waiting_for_flip = 1; |
| 361 | |
| 362 | const struct framebuffer *back_fb = &fbs[front_buffer ^ 1]; |
| 363 | glBindFramebuffer(GL_FRAMEBUFFER, bs_egl_fb_name(back_fb->egl_fb)); |
| 364 | if (!bs_draw_ellipse(mapper, buffer.bo, buffer.draw_format, |
| 365 | (float)i / test_frames)) { |
| 366 | bs_debug_error("failed to draw to buffer"); |
| 367 | goto destroy_gl_resources; |
| 368 | } |
| 369 | |
Gurchetan Singh | 7018a3c | 2017-04-10 17:30:12 -0700 | [diff] [blame] | 370 | draw_textured_quad(buffer.tex); |
Dongseong Hwang | 20c494c | 2016-04-27 11:26:19 +0300 | [diff] [blame] | 371 | |
| 372 | flush_egl(egl, back_fb->image); |
| 373 | |
| 374 | ret = drmModePageFlip(display_fd, pipe.crtc_id, back_fb->fb_id, |
| 375 | DRM_MODE_PAGE_FLIP_EVENT, &waiting_for_flip); |
| 376 | if (ret) { |
| 377 | bs_debug_error("failed to queue page flip"); |
| 378 | goto destroy_gl_resources; |
| 379 | } |
| 380 | |
| 381 | while (waiting_for_flip) { |
| 382 | FD_ZERO(&fds); |
| 383 | FD_SET(0, &fds); |
| 384 | FD_SET(display_fd, &fds); |
| 385 | int ret = select(display_fd + 1, &fds, NULL, NULL, NULL); |
| 386 | if (ret < 0) { |
| 387 | bs_debug_error("select err: %s", strerror(errno)); |
| 388 | goto destroy_gl_resources; |
| 389 | } else if (FD_ISSET(0, &fds)) { |
| 390 | bs_debug_error("exit due to user-input"); |
| 391 | goto destroy_gl_resources; |
| 392 | } else if (FD_ISSET(display_fd, &fds)) { |
| 393 | drmHandleEvent(display_fd, &evctx); |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | front_buffer ^= 1; |
| 398 | } |
| 399 | |
| 400 | destroy_gl_resources: |
| 401 | glBindBuffer(GL_ARRAY_BUFFER, 0); |
| 402 | glUseProgram(0); |
| 403 | glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| 404 | glBindTexture(GL_TEXTURE_2D, 0); |
| 405 | glDeleteProgram(resources.program); |
| 406 | glDeleteBuffers(1, &resources.vbo); |
| 407 | destroy_offscreen_buffer: |
| 408 | glDeleteTextures(1, &buffer.tex); |
| 409 | bs_egl_image_destroy(egl, &buffer.image); |
| 410 | gbm_bo_destroy(buffer.bo); |
| 411 | delete_framebuffers: |
| 412 | for (size_t i = 0; i < NUM_BUFFERS; i++) { |
| 413 | bs_egl_fb_destroy(&fbs[i].egl_fb); |
| 414 | bs_egl_image_destroy(egl, &fbs[i].image); |
| 415 | drmModeRmFB(display_fd, fbs[i].fb_id); |
| 416 | gbm_bo_destroy(fbs[i].bo); |
| 417 | } |
| 418 | bs_egl_destroy(&egl); |
| 419 | destroy_gbm_device: |
| 420 | gbm_device_destroy(gbm); |
| 421 | destroy_mapper: |
| 422 | bs_mapper_destroy(mapper); |
| 423 | destroy_display_fd: |
| 424 | close(display_fd); |
| 425 | out: |
| 426 | return ret; |
| 427 | } |