| /* |
| * Copyright 2017 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. |
| */ |
| |
| /* |
| The mapped_texture_test test consists of: |
| * Importing external buffers as EGL Images |
| * Drawing an ellipse using the CPU |
| * Binding CPU drawn buffer as a texture and sampling from it |
| * Using KMS to scanout the resultant framebuffer |
| */ |
| |
| #include <getopt.h> |
| |
| #include "bs_drm.h" |
| |
| // double buffering |
| #define NUM_BUFFERS 2 |
| |
| struct offscreen_buffer { |
| struct gbm_bo *bo; |
| GLuint tex; |
| EGLImageKHR image; |
| const struct bs_draw_format *draw_format; |
| }; |
| |
| struct framebuffer { |
| struct gbm_bo *bo; |
| uint32_t fb_id; |
| EGLImageKHR image; |
| struct bs_egl_fb *egl_fb; |
| }; |
| |
| struct gl_resources { |
| GLuint program; |
| GLuint vbo; |
| }; |
| |
| // clang-format off |
| static const GLfloat vertices[] = { |
| // x y u v |
| -0.25f, -0.25f, 0.0f, 0.0f, // Bottom left |
| -0.25f, 0.25f, 0.0f, 1.0f, // Top left |
| 0.25f, 0.25f, 1.0f, 1.0f, // Top right |
| 0.25f, -0.25f, 1.0f, 0.0f, // Bottom Right |
| }; |
| |
| static const int binding_xy = 0; |
| static const int binding_uv = 1; |
| |
| static const GLubyte indices[] = { |
| 0, 1, 2, |
| 0, 2, 3 |
| }; |
| |
| // clang-format on |
| |
| static const GLchar *vert = |
| "attribute vec2 xy;\n" |
| "attribute vec2 uv;\n" |
| "varying vec2 tex_coordinate;\n" |
| "void main() {\n" |
| " gl_Position = vec4(xy, 0, 1);\n" |
| " tex_coordinate = uv;\n" |
| "}\n"; |
| |
| static const GLchar *frag = |
| "precision mediump float;\n" |
| "uniform sampler2D ellipse;\n" |
| "varying vec2 tex_coordinate;\n" |
| "void main() {\n" |
| " gl_FragColor = texture2D(ellipse, tex_coordinate);\n" |
| "}\n"; |
| |
| static bool create_framebuffer(int display_fd, struct gbm_device *gbm, struct bs_egl *egl, |
| uint32_t width, uint32_t height, struct framebuffer *fb) |
| { |
| fb->bo = gbm_bo_create(gbm, width, height, GBM_FORMAT_XRGB8888, |
| GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); |
| if (!fb->bo) { |
| bs_debug_error("failed to create a gbm buffer."); |
| goto delete_gl_fb; |
| } |
| |
| fb->fb_id = bs_drm_fb_create_gbm(fb->bo); |
| if (!fb->fb_id) { |
| bs_debug_error("failed to create framebuffer from buffer object"); |
| goto delete_gl_image; |
| } |
| |
| fb->image = bs_egl_image_create_gbm(egl, fb->bo); |
| if (fb->image == EGL_NO_IMAGE_KHR) { |
| bs_debug_error("failed to make image from buffer object"); |
| goto delete_fb; |
| } |
| |
| fb->egl_fb = bs_egl_fb_new(egl, fb->image); |
| if (!fb->egl_fb) { |
| bs_debug_error("failed to make rednering framebuffer for buffer object"); |
| goto delete_gbm_bo; |
| } |
| |
| return true; |
| |
| delete_gl_fb: |
| bs_egl_fb_destroy(&fb->egl_fb); |
| delete_gl_image: |
| bs_egl_image_destroy(egl, &fb->image); |
| delete_fb: |
| drmModeRmFB(display_fd, fb->fb_id); |
| delete_gbm_bo: |
| gbm_bo_destroy(fb->bo); |
| return false; |
| } |
| |
| static bool add_offscreen_texture(struct gbm_device *gbm, struct bs_egl *egl, |
| struct offscreen_buffer *buffer, uint32_t width, uint32_t height, |
| uint32_t flags) |
| { |
| buffer->bo = |
| gbm_bo_create(gbm, width, height, bs_get_pixel_format(buffer->draw_format), flags); |
| if (!buffer->bo) { |
| bs_debug_error("failed to allocate offscreen buffer object: format=%s \n", |
| bs_get_format_name(buffer->draw_format)); |
| goto destroy_offscreen_buffer; |
| } |
| |
| buffer->image = bs_egl_image_create_gbm(egl, buffer->bo); |
| if (buffer->image == EGL_NO_IMAGE_KHR) { |
| bs_debug_error("failed to create offscreen egl image"); |
| goto destroy_offscreen_bo; |
| } |
| |
| glActiveTexture(GL_TEXTURE1); |
| glGenTextures(1, &buffer->tex); |
| glBindTexture(GL_TEXTURE_2D, buffer->tex); |
| |
| if (!bs_egl_target_texture2D(egl, buffer->image)) { |
| bs_debug_error("failed to import egl image as texture"); |
| goto destroy_offscreen_image; |
| } |
| |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| |
| return true; |
| |
| destroy_offscreen_image: |
| glDeleteTextures(1, &buffer->tex); |
| bs_egl_image_destroy(egl, &buffer->image); |
| destroy_offscreen_bo: |
| gbm_bo_destroy(buffer->bo); |
| destroy_offscreen_buffer: |
| return false; |
| } |
| |
| static bool init_gl(struct bs_egl_fb *fb, uint32_t width, uint32_t height, |
| struct gl_resources *resources) |
| { |
| struct bs_gl_program_create_binding bindings[] = { |
| { binding_xy, "xy" }, { binding_uv, "uv" }, { 2, NULL }, |
| }; |
| |
| resources->program = bs_gl_program_create_vert_frag_bind(vert, frag, bindings); |
| if (!resources->program) { |
| bs_debug_error("failed to compile shader program"); |
| return false; |
| } |
| |
| glGenBuffers(1, &resources->vbo); |
| glBindBuffer(GL_ARRAY_BUFFER, resources->vbo); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); |
| |
| glBindFramebuffer(GL_FRAMEBUFFER, bs_egl_fb_name(fb)); |
| glViewport(0, 0, (GLint)width, (GLint)height); |
| |
| glClearColor(1.0f, 1.0f, 1.0f, 1.0f); |
| glClear(GL_COLOR_BUFFER_BIT); |
| |
| glUseProgram(resources->program); |
| |
| glUniform1i(glGetUniformLocation(resources->program, "ellipse"), 1); |
| glEnableVertexAttribArray(binding_xy); |
| glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0); |
| |
| glEnableVertexAttribArray(binding_uv); |
| glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), |
| (void *)(2 * sizeof(GLfloat))); |
| return true; |
| } |
| |
| static void draw_textured_quad(GLuint tex) |
| { |
| glClear(GL_COLOR_BUFFER_BIT); |
| glActiveTexture(GL_TEXTURE1); |
| glBindTexture(GL_TEXTURE_2D, tex); |
| glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, indices); |
| } |
| |
| static const struct option longopts[] = { |
| { "help", no_argument, NULL, 'h' }, |
| { "format", required_argument, NULL, 'f' }, |
| { "dma-buf", no_argument, NULL, 'b' }, |
| { "gem", no_argument, NULL, 'g' }, |
| { "dumb", no_argument, NULL, 'd' }, |
| { "tiled", no_argument, NULL, 't' }, |
| { 0, 0, 0, 0 }, |
| }; |
| |
| static void print_help(const char *argv0) |
| { |
| printf("Usage: %s [OPTIONS]\n", argv0); |
| printf(" -h, --help Print help.\n"); |
| printf(" -f, --format FOURCC format of texture (defaults to ARGB8888)\n"); |
| printf(" -b, --dma-buf Use dma-buf mmap.\n"); |
| printf(" -g, --gem Use GEM map(by default).\n"); |
| printf(" -d, --dumb Use dump map.\n"); |
| } |
| |
| static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, |
| void *data) |
| { |
| int *waiting_for_flip = data; |
| *waiting_for_flip = 0; |
| } |
| |
| void flush_egl(struct bs_egl *egl, EGLImageKHR image) |
| { |
| bs_egl_image_flush_external(egl, image); |
| EGLSyncKHR sync = bs_egl_create_sync(egl, EGL_SYNC_FENCE_KHR, NULL); |
| bs_egl_wait_sync(egl, sync, 0, EGL_FOREVER_KHR); |
| bs_egl_destroy_sync(egl, sync); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int ret = 1; |
| int display_fd = bs_drm_open_main_display(); |
| if (display_fd < 0) { |
| bs_debug_error("failed to open card for display"); |
| goto out; |
| } |
| |
| struct offscreen_buffer buffer; |
| buffer.draw_format = bs_get_draw_format_from_name("ARGB8888"); |
| struct bs_mapper *mapper = NULL; |
| uint32_t flags = GBM_BO_USE_TEXTURING; |
| |
| int c; |
| while ((c = getopt_long(argc, argv, "f:bgdh", longopts, NULL)) != -1) { |
| switch (c) { |
| case 'f': |
| if (!bs_parse_draw_format(optarg, &buffer.draw_format)) { |
| printf("choose the default format ARGB8888\n"); |
| } |
| printf("format=%s\n", bs_get_format_name(buffer.draw_format)); |
| break; |
| case 'b': |
| mapper = bs_mapper_dma_buf_new(); |
| flags |= GBM_BO_USE_LINEAR; |
| printf("using dma-buf mmap\n"); |
| break; |
| case 'g': |
| mapper = bs_mapper_gem_new(); |
| flags |= GBM_BO_USE_SW_WRITE_OFTEN; |
| printf("using GEM map\n"); |
| break; |
| case 'd': |
| mapper = bs_mapper_dumb_new(display_fd); |
| flags |= GBM_BO_USE_LINEAR; |
| printf("using dumb map\n"); |
| break; |
| case 'h': |
| default: |
| print_help(argv[0]); |
| goto destroy_display_fd; |
| } |
| } |
| |
| // Use gem map mapper by default, in case any arguments aren't selected. |
| if (!mapper) { |
| mapper = bs_mapper_gem_new(); |
| flags |= GBM_BO_USE_SW_WRITE_OFTEN; |
| printf("using GEM map\n"); |
| } |
| |
| if (!mapper) { |
| bs_debug_error("failed to create mapper object"); |
| goto destroy_display_fd; |
| } |
| |
| uint32_t width; |
| uint32_t height; |
| |
| struct gbm_device *gbm = gbm_create_device(display_fd); |
| if (!gbm) { |
| bs_debug_error("failed to create gbm device"); |
| goto destroy_mapper; |
| } |
| |
| struct bs_drm_pipe pipe = { 0 }; |
| if (!bs_drm_pipe_make(display_fd, &pipe)) { |
| bs_debug_error("failed to make pipe"); |
| goto destroy_gbm_device; |
| } |
| |
| drmModeConnector *connector = drmModeGetConnector(display_fd, pipe.connector_id); |
| drmModeModeInfo *mode = &connector->modes[0]; |
| width = mode->hdisplay; |
| height = mode->vdisplay; |
| |
| struct bs_egl *egl = bs_egl_new(); |
| if (!bs_egl_setup(egl, NULL)) { |
| bs_debug_error("failed to setup egl context"); |
| goto destroy_gbm_device; |
| } |
| |
| struct framebuffer fbs[NUM_BUFFERS] = {}; |
| uint32_t front_buffer = 0; |
| for (size_t i = 0; i < NUM_BUFFERS; i++) { |
| if (!create_framebuffer(display_fd, gbm, egl, width, height, &fbs[i])) { |
| bs_debug_error("failed to create framebuffer"); |
| goto delete_framebuffers; |
| } |
| } |
| |
| if (!add_offscreen_texture(gbm, egl, &buffer, width / 4, height / 4, flags)) { |
| bs_debug_error("failed to create offscreen texture"); |
| goto destroy_offscreen_buffer; |
| } |
| |
| const struct framebuffer *back_fb = &fbs[front_buffer ^ 1]; |
| struct gl_resources resources; |
| if (!init_gl(back_fb->egl_fb, width, height, &resources)) { |
| bs_debug_error("failed to initialize GL resources.\n"); |
| goto destroy_gl_resources; |
| } |
| |
| flush_egl(egl, back_fb->image); |
| |
| ret = drmModeSetCrtc(display_fd, pipe.crtc_id, fbs[front_buffer].fb_id, 0 /* x */, |
| 0 /* y */, &pipe.connector_id, 1 /* connector count */, mode); |
| if (ret) { |
| bs_debug_error("failed to set crtc: %d", ret); |
| goto destroy_gl_resources; |
| } |
| |
| drmEventContext evctx = { |
| .version = DRM_EVENT_CONTEXT_VERSION, .page_flip_handler = page_flip_handler, |
| }; |
| fd_set fds; |
| |
| // The test takes about 2 seconds to complete. |
| const size_t test_frames = 120; |
| for (size_t i = 0; i < test_frames; i++) { |
| int waiting_for_flip = 1; |
| |
| const struct framebuffer *back_fb = &fbs[front_buffer ^ 1]; |
| glBindFramebuffer(GL_FRAMEBUFFER, bs_egl_fb_name(back_fb->egl_fb)); |
| if (!bs_draw_ellipse(mapper, buffer.bo, buffer.draw_format, |
| (float)i / test_frames)) { |
| bs_debug_error("failed to draw to buffer"); |
| goto destroy_gl_resources; |
| } |
| |
| draw_textured_quad(buffer.tex); |
| |
| flush_egl(egl, back_fb->image); |
| |
| ret = drmModePageFlip(display_fd, pipe.crtc_id, back_fb->fb_id, |
| DRM_MODE_PAGE_FLIP_EVENT, &waiting_for_flip); |
| if (ret) { |
| bs_debug_error("failed to queue page flip"); |
| goto destroy_gl_resources; |
| } |
| |
| while (waiting_for_flip) { |
| FD_ZERO(&fds); |
| FD_SET(0, &fds); |
| FD_SET(display_fd, &fds); |
| int ret = select(display_fd + 1, &fds, NULL, NULL, NULL); |
| if (ret < 0) { |
| bs_debug_error("select err: %s", strerror(errno)); |
| goto destroy_gl_resources; |
| } else if (FD_ISSET(0, &fds)) { |
| bs_debug_error("exit due to user-input"); |
| goto destroy_gl_resources; |
| } else if (FD_ISSET(display_fd, &fds)) { |
| drmHandleEvent(display_fd, &evctx); |
| } |
| } |
| |
| front_buffer ^= 1; |
| } |
| |
| destroy_gl_resources: |
| glBindBuffer(GL_ARRAY_BUFFER, 0); |
| glUseProgram(0); |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); |
| glBindTexture(GL_TEXTURE_2D, 0); |
| glDeleteProgram(resources.program); |
| glDeleteBuffers(1, &resources.vbo); |
| destroy_offscreen_buffer: |
| glDeleteTextures(1, &buffer.tex); |
| bs_egl_image_destroy(egl, &buffer.image); |
| gbm_bo_destroy(buffer.bo); |
| delete_framebuffers: |
| for (size_t i = 0; i < NUM_BUFFERS; i++) { |
| bs_egl_fb_destroy(&fbs[i].egl_fb); |
| bs_egl_image_destroy(egl, &fbs[i].image); |
| drmModeRmFB(display_fd, fbs[i].fb_id); |
| gbm_bo_destroy(fbs[i].bo); |
| } |
| bs_egl_destroy(&egl); |
| destroy_gbm_device: |
| gbm_device_destroy(gbm); |
| destroy_mapper: |
| bs_mapper_destroy(mapper); |
| destroy_display_fd: |
| close(display_fd); |
| out: |
| return ret; |
| } |