Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2019 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 | #define _GNU_SOURCE |
| 8 | |
| 9 | #include <EGL/egl.h> |
| 10 | #include <EGL/eglext.h> |
| 11 | #include <GLES2/gl2.h> |
| 12 | #include <GLES2/gl2ext.h> |
| 13 | #include <fcntl.h> |
| 14 | #include <linux/dma-buf.h> |
| 15 | #include <linux/udmabuf.h> |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 16 | #include <math.h> |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 17 | #include <stdint.h> |
| 18 | #include <stdlib.h> |
| 19 | #include <sys/ioctl.h> |
| 20 | #include <sys/mman.h> |
| 21 | #include <time.h> |
| 22 | |
| 23 | #include "bs_drm.h" |
| 24 | |
| 25 | #define SEC_TO_NS 1000000000L |
| 26 | #define NS_TO_MS 1 / 1000000L |
| 27 | |
| 28 | #define HANDLE_EINTR_AND_EAGAIN(x) \ |
| 29 | ({ \ |
| 30 | int result; \ |
| 31 | do { \ |
| 32 | result = (x); \ |
| 33 | } while (result != -1 && (errno == EINTR || errno == EAGAIN)); \ |
| 34 | result; \ |
| 35 | }) |
| 36 | |
| 37 | // The purpose of this test is to assess the impact on the performance of |
| 38 | // compositing when using udmabuf to avoid copies. To accomplish this, we |
| 39 | // compare two paths: |
| 40 | // |
| 41 | // 1) Drawing the square to a shared memory buffer with the CPU, converting that |
| 42 | // to a dma-buf using udmabuf_create, and importing that dma-buf in GL to |
| 43 | // composite on to a scanout buffer. |
| 44 | // |
| 45 | // 2) Drawing the square to a shared memory buffer with the CPU, uploading that |
| 46 | // as a GL texture, and using that texture to composite onto a scanout buffer. |
| 47 | // |
| 48 | // For each path and for each frame, we time drawing the square with the CPU, |
| 49 | // and we time how long it takes GL to finish rendering. |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 50 | |
| 51 | // Duration to display frames for in seconds. |
| 52 | static const int kTestCaseDurationSeconds = 20; |
| 53 | // Name of memfd file created. |
| 54 | static const char* kMemFDCreateName = "dmabuf_test"; |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 55 | // Critical value for the standard normal distribution corresponding to a 95% confidence level. |
| 56 | static const double kZCriticalValue = 1.960; |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 57 | |
| 58 | // Represents a buffer that can be composited into and will be scanned out from. |
| 59 | struct Buffer { |
| 60 | struct gbm_bo* bo; |
| 61 | struct bs_egl_fb* gl_fb; |
| 62 | uint32_t fb_id; |
| 63 | EGLImageKHR egl_image; |
| 64 | }; |
| 65 | |
| 66 | // An implementation of double buffering: we composite into buffers[back_buffer] while |
| 67 | // the other buffer is being scanned out. |
| 68 | struct BufferQueue { |
| 69 | struct Buffer buffers[2]; |
| 70 | size_t back_buffer; |
| 71 | }; |
| 72 | |
| 73 | // Position and velocity of the square. |
| 74 | struct MotionContext { |
| 75 | int x; |
| 76 | int y; |
| 77 | int x_v; |
| 78 | int y_v; |
| 79 | }; |
| 80 | |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 81 | struct SharedMemoryBuffer { |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 82 | int memfd; |
| 83 | uint32_t* mapped_rgba_data; |
| 84 | }; |
| 85 | |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 86 | // Represents a shared-memory buffer imported into GL. |
| 87 | // |image_bo|, |image|, and |dmabuf_fd| are only used in the zero-copy path. |
| 88 | struct ImportedBuffer { |
| 89 | GLuint image_texture; |
| 90 | struct gbm_bo* image_bo; |
| 91 | EGLImageKHR image; |
| 92 | int dmabuf_fd; |
| 93 | }; |
| 94 | |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 95 | // Context required for a page flip and memory cleanup when finished. |
| 96 | struct PageFlipContext { |
| 97 | struct BufferQueue queue; |
| 98 | struct MotionContext motion_context; |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 99 | |
| 100 | struct SharedMemoryBuffer shm_buffer; |
| 101 | struct ImportedBuffer imported_buffer; |
| 102 | |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 103 | struct bs_egl* egl; |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 104 | GLuint vertex_attributes; |
| 105 | bool use_zero_copy; |
| 106 | int frames; |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 107 | uint32_t width; |
| 108 | uint32_t height; |
| 109 | uint32_t crtc_id; |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 110 | double sum_of_times; // Sum of timings on each frame. |
| 111 | double sum_of_squared_times; // Sum of squared timings on each frame. |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 112 | }; |
| 113 | |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 114 | double standard_error(double stddev, size_t n) |
| 115 | { |
| 116 | return kZCriticalValue * (stddev / sqrt(n)); |
| 117 | } |
| 118 | |
| 119 | // Aligns num up to the nearest multiple of |multiple|. |
| 120 | uint32_t align(uint32_t num, int multiple) |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 121 | { |
| 122 | assert(multiple); |
| 123 | return ((num + multiple - 1) / multiple) * multiple; |
| 124 | } |
| 125 | |
| 126 | /* |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 127 | * Upload pixel data as a GL texture. |
| 128 | */ |
| 129 | void upload_texture(uint32_t* arr, size_t width, size_t height) |
| 130 | { |
| 131 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, arr); |
| 132 | } |
| 133 | |
| 134 | /* |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 135 | * Draw a randomly colored square, moving from top left to bottom right |
| 136 | * behind a black background. Position of the square is set by |motion_context|. |
| 137 | * |arr| points to the RGBA pixel data. |
| 138 | */ |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 139 | void draw_square(size_t width, size_t height, struct MotionContext* motion_context, uint32_t* arr) |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 140 | { |
| 141 | size_t j_left_bound = motion_context->x; |
| 142 | size_t j_right_bound = j_left_bound + 50; |
| 143 | size_t i_top_bound = motion_context->y; |
| 144 | size_t i_bottom_bound = i_top_bound + 50; |
| 145 | uint32_t color = drand48() * 0xFFFFFFFF; |
| 146 | |
| 147 | if (i_bottom_bound >= height) |
| 148 | motion_context->y_v = -16; |
| 149 | |
| 150 | if (j_right_bound >= width) |
| 151 | motion_context->x_v = -16; |
| 152 | |
| 153 | if (j_left_bound <= 1) |
| 154 | motion_context->x_v = 16; |
| 155 | |
| 156 | if (i_top_bound <= 1) |
| 157 | motion_context->y_v = 16; |
| 158 | |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 159 | uint32_t* dst = (uint32_t*)arr + i_top_bound * width; |
| 160 | for (size_t row = i_top_bound; (row < i_bottom_bound) && (row < height); row++) { |
| 161 | for (size_t col = j_left_bound; (col < j_right_bound) && (col < width); col++) { |
| 162 | dst[col] = color; |
| 163 | } |
| 164 | dst += width; |
| 165 | } |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 166 | } |
| 167 | |
| 168 | int create_udmabuf(int fd, size_t length) |
| 169 | { |
| 170 | int udmabuf_dev_fd = HANDLE_EINTR_AND_EAGAIN(open("/dev/udmabuf", O_RDWR)); |
| 171 | |
| 172 | struct udmabuf_create create; |
| 173 | create.memfd = fd; |
| 174 | create.flags = UDMABUF_FLAGS_CLOEXEC; |
| 175 | create.offset = 0; |
| 176 | create.size = length; |
| 177 | |
| 178 | int dmabuf_fd = HANDLE_EINTR_AND_EAGAIN(ioctl(udmabuf_dev_fd, UDMABUF_CREATE, &create)); |
| 179 | if (dmabuf_fd < 0) { |
| 180 | bs_debug_error("error creating udmabuf"); |
| 181 | exit(EXIT_FAILURE); |
| 182 | } |
| 183 | |
| 184 | close(udmabuf_dev_fd); |
| 185 | return dmabuf_fd; |
| 186 | } |
| 187 | |
| 188 | /* |
| 189 | * Create a region of shared memory of size |length|. |
| 190 | * The region is sealed with F_SEAL_SHRINK. |
| 191 | */ |
| 192 | int create_memfd(size_t length) |
| 193 | { |
| 194 | int fd = memfd_create(kMemFDCreateName, MFD_ALLOW_SEALING); |
| 195 | if (fd == -1) { |
| 196 | bs_debug_error("memfd_create() error: %s", strerror(errno)); |
| 197 | exit(EXIT_FAILURE); |
| 198 | } |
| 199 | |
| 200 | int res = HANDLE_EINTR_AND_EAGAIN(ftruncate(fd, length)); |
| 201 | if (res == -1) { |
| 202 | bs_debug_error("ftruncate() error: %s", strerror(errno)); |
| 203 | exit(EXIT_FAILURE); |
| 204 | } |
| 205 | |
| 206 | // udmabuf_create requires that file descriptors be sealed with |
| 207 | // F_SEAL_SHRINK. |
| 208 | if (fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK) < 0) { |
| 209 | bs_debug_error("fcntl() error: %s", strerror(errno)); |
| 210 | exit(EXIT_FAILURE); |
| 211 | } |
| 212 | |
| 213 | return fd; |
| 214 | } |
| 215 | |
| 216 | GLuint setup_shaders_and_geometry(int width, int height) |
| 217 | { |
| 218 | const GLchar* vert = |
| 219 | "attribute vec2 pos;\n" |
| 220 | "varying vec2 tex_pos;\n" |
| 221 | "void main() {\n" |
| 222 | " gl_Position = vec4(pos, 0, 1);\n" |
| 223 | " tex_pos = vec2((pos.x + 1.0) / 2.0, (pos.y + 1.0) / 2.0);\n" |
| 224 | "}\n"; |
| 225 | |
| 226 | const GLchar* frag = |
| 227 | "precision mediump float;\n" |
| 228 | "uniform sampler2D tex;\n" |
| 229 | "varying vec2 tex_pos;\n" |
| 230 | "void main() {\n" |
| 231 | " gl_FragColor = texture2D(tex, tex_pos);\n" |
| 232 | "}\n"; |
| 233 | |
| 234 | const GLfloat verts[] = { |
| 235 | -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, |
| 236 | }; |
| 237 | |
| 238 | struct bs_gl_program_create_binding bindings[] = { |
| 239 | { 0, "pos" }, |
| 240 | { 0, NULL }, |
| 241 | }; |
| 242 | |
| 243 | // Compile and link GL program. |
| 244 | GLuint program = bs_gl_program_create_vert_frag_bind(vert, frag, bindings); |
| 245 | if (!program) { |
| 246 | bs_debug_error("failed to compile shader program"); |
| 247 | exit(EXIT_FAILURE); |
| 248 | } |
| 249 | |
| 250 | glUseProgram(program); |
| 251 | glViewport(0, 0, width, height); |
| 252 | |
| 253 | GLuint buffer = 0; |
| 254 | glGenBuffers(1, &buffer); |
| 255 | glBindBuffer(GL_ARRAY_BUFFER, buffer); |
| 256 | glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); |
| 257 | |
| 258 | glUniform1i(glGetUniformLocation(program, "tex"), 0); |
| 259 | GLint pos_attrib_index = glGetAttribLocation(program, "pos"); |
| 260 | glEnableVertexAttribArray(pos_attrib_index); |
| 261 | glVertexAttribPointer(pos_attrib_index, 2, GL_FLOAT, GL_FALSE, 0, 0); |
| 262 | glDeleteProgram(program); |
| 263 | return buffer; |
| 264 | } |
| 265 | |
| 266 | struct bs_egl_fb* create_gl_framebuffer(struct bs_egl* egl, EGLImageKHR egl_image) |
| 267 | { |
| 268 | struct bs_egl_fb* fb = bs_egl_fb_new(egl, egl_image); |
| 269 | if (!fb) { |
| 270 | bs_egl_image_destroy(egl, &egl_image); |
| 271 | bs_debug_error("failed to make rendering framebuffer for buffer object"); |
| 272 | exit(EXIT_FAILURE); |
| 273 | } |
| 274 | return fb; |
| 275 | } |
| 276 | |
| 277 | EGLImageKHR import_source_buffer(struct bs_egl* egl, struct gbm_bo* bo, GLuint image_texture) |
| 278 | { |
| 279 | EGLImageKHR image = bs_egl_image_create_gbm(egl, bo); |
| 280 | if (image == EGL_NO_IMAGE_KHR) { |
| 281 | bs_debug_error("failed to make image from buffer object"); |
| 282 | exit(EXIT_FAILURE); |
| 283 | } |
| 284 | |
| 285 | glBindTexture(GL_TEXTURE_2D, image_texture); |
| 286 | |
| 287 | if (!bs_egl_target_texture2D(egl, image)) { |
| 288 | bs_debug_error("failed to import egl color_image as a texture"); |
| 289 | exit(EXIT_FAILURE); |
| 290 | } |
| 291 | return image; |
| 292 | } |
| 293 | |
| 294 | /* |
| 295 | * Initialize GL pipeline. |
| 296 | * width: width of display |
| 297 | * height: height of display |
| 298 | * |
| 299 | */ |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 300 | GLuint init_gl(struct PageFlipContext* context, uint32_t width, uint32_t height) |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 301 | { |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 302 | context->vertex_attributes = setup_shaders_and_geometry(width, height); |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 303 | |
| 304 | GLuint image_texture = 0; |
| 305 | glGenTextures(1, &image_texture); |
| 306 | glBindTexture(GL_TEXTURE_2D, image_texture); |
| 307 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); |
| 308 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); |
| 309 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| 310 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 311 | return image_texture; |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 312 | } |
| 313 | |
| 314 | /* |
| 315 | * Call on each frame. |
| 316 | * This function is called with alternating fb's. |
| 317 | */ |
| 318 | void draw_gl(GLuint fb) |
| 319 | { |
| 320 | // Bind the screen framebuffer to GL. |
| 321 | glBindFramebuffer(GL_FRAMEBUFFER, fb); |
| 322 | glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| 323 | // Block until rendering is complete. |
| 324 | // We can easily measure how long rendering takes if this function |
| 325 | // blocks. |
| 326 | glFinish(); |
| 327 | } |
| 328 | |
| 329 | /* |
| 330 | * Called at the end of each page flip. |
| 331 | * Schedules a new page flip alternating between |
| 332 | * the two buffers. |
| 333 | */ |
| 334 | static void draw_and_swap_frame(int display_fd, unsigned int frame, unsigned int sec, |
| 335 | unsigned int usec, void* data) |
| 336 | { |
| 337 | struct PageFlipContext* context = data; |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 338 | struct BufferQueue* queue = &context->queue; |
| 339 | struct Buffer buf = queue->buffers[queue->back_buffer]; |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 340 | struct bs_egl* egl = context->egl; |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 341 | struct SharedMemoryBuffer shm_buffer = context->shm_buffer; |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 342 | int crtc_id = context->crtc_id; |
| 343 | uint32_t width = context->width; |
| 344 | uint32_t height = context->height; |
| 345 | int err; |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 346 | struct timespec start, finish; |
| 347 | clock_gettime(CLOCK_MONOTONIC, &start); |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 348 | if (context->use_zero_copy) { |
| 349 | int dmabuf_fd = context->imported_buffer.dmabuf_fd; |
| 350 | struct dma_buf_sync sync_start = { 0 }; |
| 351 | sync_start.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_RW; |
| 352 | int rv = HANDLE_EINTR_AND_EAGAIN(ioctl(dmabuf_fd, DMA_BUF_IOCTL_SYNC, &sync_start)); |
| 353 | if (rv != 0) { |
| 354 | bs_debug_error("error with dma_buf start sync"); |
| 355 | exit(EXIT_FAILURE); |
| 356 | } |
| 357 | |
| 358 | draw_square(width, height, &context->motion_context, shm_buffer.mapped_rgba_data); |
| 359 | |
| 360 | struct dma_buf_sync sync_end = { 0 }; |
| 361 | sync_end.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_RW; |
| 362 | rv = HANDLE_EINTR_AND_EAGAIN(ioctl(dmabuf_fd, DMA_BUF_IOCTL_SYNC, &sync_end)); |
| 363 | if (rv != 0) { |
| 364 | bs_debug_error("error with dma_buf end sync"); |
| 365 | exit(EXIT_FAILURE); |
| 366 | } |
| 367 | } else { |
| 368 | draw_square(width, height, &context->motion_context, shm_buffer.mapped_rgba_data); |
| 369 | // TODO(crbug.com/1069612): Experiment a third path which uses |
| 370 | // glTexSubImage2D instead of glTexImage2D() on each frame. It |
| 371 | // should be faster. |
| 372 | upload_texture(shm_buffer.mapped_rgba_data, width, height); |
| 373 | } |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 374 | draw_gl(bs_egl_fb_name(buf.gl_fb)); |
| 375 | clock_gettime(CLOCK_MONOTONIC, &finish); |
| 376 | |
| 377 | double ns_diff = |
| 378 | (SEC_TO_NS * (finish.tv_sec - start.tv_sec)) + finish.tv_nsec - start.tv_nsec; |
| 379 | double ms_to_draw_and_render = (ns_diff)*NS_TO_MS; |
| 380 | |
| 381 | context->sum_of_times += ms_to_draw_and_render; |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 382 | context->sum_of_squared_times += ms_to_draw_and_render * ms_to_draw_and_render; |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 383 | |
| 384 | bs_egl_image_flush_external(egl, buf.egl_image); |
| 385 | err = drmModePageFlip(display_fd, crtc_id, buf.fb_id, DRM_MODE_PAGE_FLIP_EVENT, context); |
| 386 | |
| 387 | if (err) { |
| 388 | bs_debug_error("failed page flip: %s", strerror(errno)); |
| 389 | exit(EXIT_FAILURE); |
| 390 | } |
| 391 | |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 392 | queue->back_buffer = (queue->back_buffer + 1) % 2; |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 393 | context->motion_context.x += context->motion_context.x_v; |
| 394 | context->motion_context.y += context->motion_context.y_v; |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 395 | context->frames++; |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 396 | } |
| 397 | |
| 398 | struct BufferQueue init_buffers(struct gbm_device* gbm, struct bs_egl* egl, uint32_t width, |
| 399 | uint32_t height) |
| 400 | { |
| 401 | struct BufferQueue queue; |
| 402 | memset(&queue, 0, sizeof(struct BufferQueue)); |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 403 | for (size_t i = 0; i < 2; i++) { |
| 404 | struct gbm_bo* screen_bo = gbm_bo_create(gbm, width, height, GBM_FORMAT_ARGB8888, |
| 405 | GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT); |
| 406 | if (!screen_bo) { |
| 407 | bs_debug_error("failed to create screen bo"); |
| 408 | exit(EXIT_FAILURE); |
| 409 | } |
| 410 | |
| 411 | EGLImageKHR egl_image = bs_egl_image_create_gbm(egl, screen_bo); |
| 412 | if (egl_image == EGL_NO_IMAGE_KHR) { |
| 413 | bs_debug_error("failed to make image from buffer object"); |
| 414 | exit(EXIT_FAILURE); |
| 415 | } |
| 416 | |
| 417 | uint32_t fb_id = bs_drm_fb_create_gbm(screen_bo); |
| 418 | if (!fb_id) { |
| 419 | bs_debug_error("failed to make drm fb from image"); |
| 420 | exit(EXIT_FAILURE); |
| 421 | } |
| 422 | |
| 423 | queue.buffers[i].egl_image = egl_image; |
| 424 | queue.buffers[i].bo = screen_bo; |
| 425 | queue.buffers[i].fb_id = fb_id; |
| 426 | queue.buffers[i].gl_fb = create_gl_framebuffer(egl, egl_image); |
| 427 | } |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 428 | queue.back_buffer = 1; |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 429 | return queue; |
| 430 | } |
| 431 | |
| 432 | struct PageFlipContext init_page_flip_context(struct gbm_device* gbm, struct bs_egl* egl, |
| 433 | int display_fd) |
| 434 | { |
| 435 | struct bs_drm_pipe pipe = { 0 }; |
| 436 | if (!bs_drm_pipe_make(display_fd, &pipe)) { |
| 437 | bs_debug_error("failed to make pipe: %s", strerror(errno)); |
| 438 | exit(EXIT_FAILURE); |
| 439 | } |
| 440 | |
| 441 | drmModeConnector* connector = drmModeGetConnector(display_fd, pipe.connector_id); |
| 442 | drmModeModeInfo* mode = &connector->modes[0]; |
| 443 | |
| 444 | struct PageFlipContext context; |
| 445 | memset(&context, 0, sizeof(struct PageFlipContext)); |
| 446 | |
| 447 | context.crtc_id = pipe.crtc_id; |
| 448 | context.height = mode->vdisplay; |
| 449 | context.width = mode->hdisplay; |
| 450 | context.egl = egl; |
| 451 | context.motion_context = (struct MotionContext){ 1, 1, 16, 16 }; |
| 452 | context.queue = init_buffers(gbm, egl, mode->hdisplay, mode->vdisplay); |
| 453 | context.sum_of_times = 0; |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 454 | context.sum_of_squared_times = 0; |
| 455 | context.frames = 0; |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 456 | |
| 457 | // Set display mode which also flips the page. |
| 458 | int ret_display = |
| 459 | drmModeSetCrtc(display_fd, pipe.crtc_id, context.queue.buffers[0].fb_id, 0 /* x */, |
| 460 | 0 /* y */, &pipe.connector_id, 1 /* connector count */, mode); |
| 461 | |
| 462 | if (ret_display) { |
| 463 | bs_debug_error("failed to set crtc: %s", strerror(errno)); |
| 464 | exit(EXIT_FAILURE); |
| 465 | } |
| 466 | return context; |
| 467 | } |
| 468 | |
| 469 | struct gbm_bo* import_dmabuf(struct gbm_device* gbm, int dmabuf_fd, uint32_t width, uint32_t height) |
| 470 | { |
| 471 | // Import buffer object from shared dma_buf. |
| 472 | struct gbm_import_fd_modifier_data gbm_import_data; |
| 473 | gbm_import_data.width = width; |
| 474 | gbm_import_data.height = height; |
| 475 | gbm_import_data.format = GBM_FORMAT_ARGB8888; |
| 476 | gbm_import_data.num_fds = 1; |
| 477 | gbm_import_data.fds[0] = dmabuf_fd; |
| 478 | gbm_import_data.strides[0] = width * 4; |
| 479 | gbm_import_data.offsets[0] = 0; |
| 480 | gbm_import_data.modifier = 0; |
| 481 | |
| 482 | struct gbm_bo* image_bo = |
| 483 | gbm_bo_import(gbm, GBM_BO_IMPORT_FD_MODIFIER, &gbm_import_data, GBM_BO_USE_RENDERING); |
| 484 | |
| 485 | if (!image_bo) { |
| 486 | bs_debug_error("failed to make image bo"); |
| 487 | exit(EXIT_FAILURE); |
| 488 | } |
| 489 | return image_bo; |
| 490 | } |
| 491 | |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 492 | void destroy_shm_buffer(struct SharedMemoryBuffer buf, uint32_t length) |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 493 | { |
| 494 | munmap(buf.mapped_rgba_data, length); |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 495 | close(buf.memfd); |
| 496 | } |
| 497 | |
| 498 | void destroy_imported_buffer(struct ImportedBuffer buf, struct bs_egl* egl) |
| 499 | { |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 500 | glDeleteTextures(1, &buf.image_texture); |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 501 | if (buf.image != EGL_NO_IMAGE_KHR) |
| 502 | bs_egl_image_destroy(egl, &buf.image); |
| 503 | if (buf.image_bo) |
| 504 | gbm_bo_destroy(buf.image_bo); |
| 505 | if (buf.dmabuf_fd >= 0) |
| 506 | close(buf.dmabuf_fd); |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 507 | } |
| 508 | |
| 509 | void destroy_buffers(struct BufferQueue queue, struct bs_egl* egl) |
| 510 | { |
| 511 | for (size_t i = 0; i < 2; i++) { |
| 512 | bs_egl_image_destroy(egl, &queue.buffers[i].egl_image); |
| 513 | bs_egl_fb_destroy(&queue.buffers[i].gl_fb); |
| 514 | gbm_bo_destroy(queue.buffers[i].bo); |
| 515 | } |
| 516 | } |
| 517 | |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 518 | void print_results(double sum_of_squares, double sum, int frames, bool use_zero_copy) |
| 519 | { |
| 520 | double avg = sum / frames; |
| 521 | double stddev = sqrt((sum_of_squares - (frames * (avg * avg))) / (frames - 1)); |
| 522 | double std_err = standard_error(stddev, frames); |
| 523 | |
| 524 | double begin_range = avg - std_err; |
| 525 | double end_range = avg + std_err; |
| 526 | if (use_zero_copy) |
| 527 | printf("Using udmabuf (zero-copy path):\n"); |
| 528 | else |
| 529 | printf("Using glTexImage2D (one-copy path):\n"); |
| 530 | |
| 531 | printf(" n = %d frames\n", frames); |
| 532 | printf(" CI(t) = (%.2f ms, %.2f ms)\n", begin_range, end_range); |
| 533 | printf(" Sum(t) = %.2f ms\n", sum); |
| 534 | } |
| 535 | |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 536 | int main(int argc, char** argv) |
| 537 | { |
| 538 | struct timespec clock_resolution; |
| 539 | clock_getres(CLOCK_MONOTONIC, &clock_resolution); |
| 540 | // Make sure that the clock resolution is at least 1ms. |
| 541 | assert(clock_resolution.tv_sec == 0 && clock_resolution.tv_nsec <= 1000000); |
| 542 | |
| 543 | int display_fd = bs_drm_open_main_display(); |
| 544 | if (display_fd < 0) { |
| 545 | bs_debug_error("failed to open card for display"); |
| 546 | exit(EXIT_FAILURE); |
| 547 | } |
| 548 | struct gbm_device* gbm = gbm_create_device(display_fd); |
| 549 | if (!gbm) { |
| 550 | bs_debug_error("failed to create gbm device"); |
| 551 | exit(EXIT_FAILURE); |
| 552 | } |
| 553 | struct bs_egl* egl = bs_egl_new(); |
| 554 | if (!bs_egl_setup(egl, NULL)) { |
| 555 | bs_debug_error("failed to setup egl context"); |
| 556 | exit(EXIT_FAILURE); |
| 557 | } |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 558 | |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 559 | struct PageFlipContext context = init_page_flip_context(gbm, egl, display_fd); |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 560 | |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 561 | const uint32_t width = context.width; |
| 562 | const uint32_t height = context.height; |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 563 | uint32_t length = align(width * height * 4, getpagesize()); |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 564 | int memfd = create_memfd(length); |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 565 | |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 566 | context.imported_buffer.image_texture = init_gl(&context, width, height); |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 567 | context.shm_buffer.memfd = memfd; |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 568 | |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 569 | context.shm_buffer.mapped_rgba_data = |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 570 | mmap(NULL, length, PROT_WRITE | PROT_READ, MAP_SHARED, context.shm_buffer.memfd, 0); |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 571 | |
| 572 | draw_and_swap_frame(display_fd, 0, 0, 0, &context); |
| 573 | |
| 574 | int ret; |
| 575 | fd_set fds; |
| 576 | time_t start, cur; |
| 577 | struct timeval v; |
| 578 | drmEventContext ev; |
| 579 | |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 580 | printf("n = Number of frames\n"); |
| 581 | printf( |
| 582 | "CI(t) = 95%% Z confidence interval for the mean time to draw and composite a " |
| 583 | "frame\n"); |
| 584 | printf("Sum(t) = Total drawing and compositing time\n\n"); |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 585 | |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 586 | for (size_t i = 0; i < 2; i++) { |
| 587 | context.use_zero_copy = i; |
| 588 | context.frames = 0; |
| 589 | context.sum_of_times = 0; |
| 590 | context.sum_of_squared_times = 0; |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 591 | |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 592 | if (context.use_zero_copy) { |
| 593 | context.imported_buffer.dmabuf_fd = create_udmabuf(memfd, length); |
| 594 | context.imported_buffer.image_bo = |
| 595 | import_dmabuf(gbm, context.imported_buffer.dmabuf_fd, width, height); |
| 596 | |
| 597 | context.imported_buffer.image = |
| 598 | import_source_buffer(context.egl, context.imported_buffer.image_bo, |
| 599 | context.imported_buffer.image_texture); |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 600 | } |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 601 | srand(time(&start)); |
| 602 | FD_ZERO(&fds); |
| 603 | memset(&v, 0, sizeof(v)); |
| 604 | memset(&ev, 0, sizeof(ev)); |
| 605 | ev.version = 2; |
| 606 | ev.page_flip_handler = draw_and_swap_frame; |
| 607 | |
| 608 | // Display for kTestCaseDurationSeconds seconds. |
| 609 | while (time(&cur) < start + kTestCaseDurationSeconds) { |
| 610 | FD_SET(0, &fds); |
| 611 | FD_SET(display_fd, &fds); |
| 612 | v.tv_sec = start + kTestCaseDurationSeconds - cur; |
| 613 | |
| 614 | ret = HANDLE_EINTR_AND_EAGAIN(select(display_fd + 1, &fds, NULL, NULL, &v)); |
| 615 | if (ret < 0) { |
| 616 | bs_debug_error("select() failed on page flip: %s", strerror(errno)); |
| 617 | exit(EXIT_FAILURE); |
| 618 | } else if (FD_ISSET(0, &fds)) { |
| 619 | fprintf(stderr, "exit due to user-input\n"); |
| 620 | break; |
| 621 | } else if (FD_ISSET(display_fd, &fds)) { |
| 622 | drmHandleEvent(display_fd, &ev); |
| 623 | } |
| 624 | } |
| 625 | |
| 626 | print_results(context.sum_of_squared_times, context.sum_of_times, context.frames, |
| 627 | context.use_zero_copy); |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 628 | } |
| 629 | |
Paulo Warren | 447fbdb | 2020-04-08 16:27:20 -0400 | [diff] [blame] | 630 | destroy_imported_buffer(context.imported_buffer, egl); |
| 631 | destroy_shm_buffer(context.shm_buffer, length); |
| 632 | glDeleteBuffers(1, &context.vertex_attributes); |
Paulo Warren | 7d6d0f8 | 2020-01-30 14:52:59 -0500 | [diff] [blame] | 633 | destroy_buffers(context.queue, egl); |
| 634 | bs_egl_destroy(&egl); |
| 635 | gbm_device_destroy(gbm); |
| 636 | close(display_fd); |
| 637 | } |