/* * Copyright © 2011 Benjamin Franzke * Copyright © 2010 Intel Corporation * Copyright © 2014 Collabora Ltd. * Copyright © 2021, NVIDIA CORPORATION. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* * SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: LicenseRef-NvidiaProprietary * * NVIDIA CORPORATION, its affiliates and licensors retain all intellectual * property and proprietary rights in and to this material, related * documentation and any modifications thereto. Any use, reproduction, * disclosure or distribution of this material and related documentation * without an express license agreement from NVIDIA CORPORATION or * its affiliates is strictly prohibited. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "linux-explicit-synchronization-unstable-v1-client-protocol.h" #include #include #include #include #include #include #include "balls_nv12_hex.h" #ifndef DRM_FORMAT_MOD_LINEAR #define DRM_FORMAT_MOD_LINEAR 0 #endif #define CHECK(expr) \ do { \ int ret = (expr); \ if (ret != 0) { \ if (errno == 0) { \ fprintf(stderr,"Returned error code: %d\n",ret); \ } else { \ fprintf(stderr,"Returned error code: %d\n%s\n", \ ret,strerror(errno)); \ } \ exit(1); \ } \ } while (0) #ifndef ARRAY_LENGTH #define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) #endif struct buffer; /* Possible options that affect the displayed image */ #define OPT_Y_INVERTED 1 /* contents has y axis inverted */ #define OPT_IMMEDIATE 2 /* create wl_buffer immediately */ #define OPT_USE_GBM 4 /* use gmb buffer instead of nvbuf */ #define OPT_ALLOC_BLOCKLINEAR 8 /* alloc block linear surfaces */ #define BUFFER_FORMAT DRM_FORMAT_ARGB8888 #define NUM_BUFFERS 3 #define MAX_PLANES 3 #define ALIGN(v, a) ((v + a - 1) & ~(a - 1)) const struct drm_format_info { uint32_t drm_format; int num_planes; struct { int w; int h; int bpp; } planes[MAX_PLANES]; } drm_formats[] = { {DRM_FORMAT_XRGB8888, 1, {{1, 1, 32}, {0, 0, 0}, {0, 0, 0}}}, {DRM_FORMAT_ABGR8888, 1, {{1, 1, 32}, {0, 0, 0}, {0, 0, 0}}}, {DRM_FORMAT_ARGB2101010, 1, {{1, 1, 32}, {0, 0, 0}, {0, 0, 0}}}, {DRM_FORMAT_TEGRA_ARGB2101010_709, 1, {{1, 1, 32}, {0, 0, 0}, {0, 0, 0}}}, {DRM_FORMAT_TEGRA_ARGB2101010_2020, 1, {{1, 1, 32}, {0, 0, 0}, {0, 0, 0}}}, {DRM_FORMAT_ABGR2101010, 1, {{1, 1, 32}, {0, 0, 0}, {0, 0, 0}}}, {DRM_FORMAT_TEGRA_ABGR2101010_709, 1, {{1, 1, 32}, {0, 0, 0}, {0, 0, 0}}}, {DRM_FORMAT_TEGRA_ABGR2101010_2020, 1, {{1, 1, 32}, {0, 0, 0}, {0, 0, 0}}}, {DRM_FORMAT_NV12, 2, {{1, 1, 8}, {2, 2, 16}, {0, 0, 0}}}, {DRM_FORMAT_NV21, 2, {{1, 1, 8}, {2, 2, 16}, {0, 0, 0}}}, {DRM_FORMAT_NV16, 2, {{1, 1, 8}, {2, 1, 16}, {0, 0, 0}}}, {DRM_FORMAT_NV24, 2, {{1, 1, 8}, {1, 1, 16}, {0, 0, 0}}}, {DRM_FORMAT_P010, 2, {{1, 1, 16}, {2, 2, 32}, {0, 0, 0}}}, {DRM_FORMAT_P210, 2, {{1, 1, 16}, {2, 1, 32}, {0, 0, 0}}}, {DRM_FORMAT_P012, 2, {{1, 1, 16}, {2, 2, 32}, {0, 0, 0}}}, {DRM_FORMAT_TEGRA_P010_709, 2, {{1, 1, 16}, {2, 2, 32}, {0, 0, 0}}}, {DRM_FORMAT_TEGRA_P010_2020, 2, {{1, 1, 16}, {2, 2, 32}, {0, 0, 0}}}, {DRM_FORMAT_YUV420, 3, {{1, 1, 8}, {2, 2, 8}, {2, 2, 8}}}, {DRM_FORMAT_YVU420, 3, {{1, 1, 8}, {2, 2, 8}, {2, 2, 8}}}, {DRM_FORMAT_YUV444, 3, {{1, 1, 8}, {1, 1, 8}, {1, 1, 8}}}, }; typedef enum _rendering_type { SW_RENDERING, SW_RENDERING_BL, GL_RENDERING } rendering_type; static struct { uint8_t *data; uint32_t width; uint32_t height; uint32_t max_surface_planes; } nv12_format_info = { balls_nv12_hex, BALLS_NV12_IMAGE_WIDTH, BALLS_NV12_IMAGE_HEIGHT, 2 }; struct display { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct xdg_wm_base *wm_base; struct zwp_fullscreen_shell_v1 *fshell; struct zwp_linux_dmabuf_v1 *dmabuf; struct zwp_linux_explicit_synchronization_v1 *explicit_sync; bool use_explicit_sync; int req_dmabuf_immediate; uint64_t *modifiers; int modifiers_count; int drm_fd; struct gbm_device *gbm; rendering_type render_type; struct { EGLDisplay display; EGLContext context; EGLConfig conf; bool has_dma_buf_import_modifiers; bool has_no_config_context; PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dma_buf_modifiers; PFNEGLCREATEIMAGEKHRPROC create_image; PFNEGLDESTROYIMAGEKHRPROC destroy_image; PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; PFNEGLCREATESYNCKHRPROC create_sync; PFNEGLDESTROYSYNCKHRPROC destroy_sync; PFNEGLCLIENTWAITSYNCKHRPROC client_wait_sync; PFNEGLDUPNATIVEFENCEFDANDROIDPROC dup_native_fence_fd; PFNEGLWAITSYNCKHRPROC wait_sync; } egl; }; struct buffer { struct display *display; struct wl_buffer *buffer; int busy; int buf_fds[4]; uint64_t modifier; uint32_t pitch[4]; uint32_t offset[4]; int num_planes; struct drm_device *dev; int drm_fd; struct gbm_bo *gbm_bo; void *gbo_mapping; uint8_t *data; int width; int height; int format; NvBufSurface *surface; EGLImageKHR egl_image; GLuint gl_texture; GLuint gl_fbo; struct zwp_linux_buffer_release_v1 *buffer_release; /* The buffer owns the release_fence_fd, until it passes ownership * to it to EGL (see wait_for_buffer_release_fence). */ int release_fence_fd; }; struct window { struct display *display; int width, height; struct buffer *buffers; struct wl_surface *surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct zwp_linux_surface_synchronization_v1 *surface_sync; struct wl_callback *callback; bool initialized; bool wait_for_configure; struct { GLuint program; GLuint pos; GLuint color; GLuint offset_uniform; GLuint reflection_uniform; } gl; }; static int running = 1; static bool use_gbm = false; static void redraw(void *data, struct wl_callback *callback, uint32_t time); static int get_format_info(unsigned int drm_format, struct drm_format_info *info) { int i; int format_info_count = sizeof(drm_formats) / sizeof(drm_formats[0]); if (use_gbm) { // formats not supported by GBM switch(drm_format) { case DRM_FORMAT_TEGRA_ARGB2101010_709: case DRM_FORMAT_TEGRA_ARGB2101010_2020: case DRM_FORMAT_TEGRA_ABGR2101010_709: case DRM_FORMAT_TEGRA_ABGR2101010_2020: case DRM_FORMAT_NV24: case DRM_FORMAT_P010: case DRM_FORMAT_P210: case DRM_FORMAT_P012: case DRM_FORMAT_TEGRA_P010_709: case DRM_FORMAT_TEGRA_P010_2020: return 0; } } for (i = 0; i < format_info_count; i++) { if (drm_format == drm_formats[i].drm_format) { *info = drm_formats[i]; return 1; } } return 0; } static void buffer_release(void *data, struct wl_buffer *buffer) { struct buffer *mybuf = data; mybuf->busy = 0; } static const struct wl_buffer_listener buffer_listener = { buffer_release }; static void calculateRGB(int x, int y, int width, int height, int *r,int *g, int *b) { int R = 0, G = 255, B = 0; // Top-Bottom Gradient float f = 1.0f - (float)y/height; *r = (int)(f * R); *g = (int)(f * G); *b = (int)(f * B); // Red grid if (!((x % 100)/2) || !((y % 100)/2)) { *r = 255; } // Blue X if (abs(abs(width / 2 - x) - abs(height / 2 - y)) < 10) { *b = 255; } // White Border if (x < 10 || y < 10 || x > width-10 || y > height-10) { *r = *g = *b = 255; } } static void mapBuffer(int *height, int *width, struct buffer *buf, int plane, NvBufSurfaceParams *surfparams) { *height = surfparams->planeParams.height[plane]; *width = surfparams->planeParams.width[plane]; CHECK(NvBufSurfaceMap (buf->surface, 0, plane, NVBUF_MAP_WRITE)); assert(surfparams->mappedAddr.addr[plane]); } static void write_pixel_RGB30(struct buffer *buf, int off, uint16_t a, uint16_t b, uint16_t c, uint16_t d) { b *= 4; c *= 4; d *= 4; b <<= 6; c <<= 6; d <<= 6; if (!use_gbm) { buf->data = (uint8_t*)buf->surface->surfaceList[0].mappedAddr.addr[0]; } buf->data[off] = (d>>6); buf->data[off + 1] = 0x03 & (d>>14); buf->data[off + 1] |= 0xfc & (c>>4); buf->data[off + 2] = 0x0f & (c>>12); buf->data[off + 2] |= 0xf0 & (b>>2); buf->data[off + 3] = 0x3f & (b>>10); buf->data[off + 3] |= 0xc0; } static void reorder_color_components(int format, int r, int g, int b, int a, int orderedComponent[4]) { switch (format) { case DRM_FORMAT_XRGB8888: case DRM_FORMAT_ARGB8888: orderedComponent[0] = b; orderedComponent[1] = g; orderedComponent[2] = r; orderedComponent[3] = a; break; case DRM_FORMAT_ABGR8888: case DRM_FORMAT_XBGR8888: orderedComponent[0] = r; orderedComponent[1] = g; orderedComponent[2] = b; orderedComponent[3] = a; break; case DRM_FORMAT_RGBA8888: case DRM_FORMAT_RGBX8888: orderedComponent[0] = a; orderedComponent[1] = b; orderedComponent[2] = g; orderedComponent[3] = r; break; case DRM_FORMAT_BGRA8888: case DRM_FORMAT_BGRX8888: orderedComponent[0] = a; orderedComponent[1] = r; orderedComponent[2] = g; orderedComponent[3] = b; break; } } static int get_max_alpha(int drm_fd) { /* Getting max alpha based on the DRM backend as drmModeGetProperty() * requires plane id and here we are not sure on which plane the weston * compositor will place the app * */ drmVersion *version; int max_alpha = 0xff; version = drmGetVersion(drm_fd); if (version == NULL) { // Set default alpha to be 0xffff return 0xffff; } if (!strcmp(version->name, "nvidia-drm")) { max_alpha = 0xffff; } drmFreeVersion(version); return max_alpha; } static void fill_content_bl(struct buffer *buf) { switch (buf->format) { case DRM_FORMAT_NV12: { uint32_t imageUVOffset = buf->width * buf->height; Raw2NvBufSurface(nv12_format_info.data, 0, 0, buf->width, buf->height, buf->surface); Raw2NvBufSurface(&nv12_format_info.data[imageUVOffset], 0, 1, buf->width / 2, buf->height / 2, buf->surface); break; } default: fprintf(stderr, "Block linear formats other than NV12 are not supported.\n"); break; } } static void fill_content(int drm_fd, struct buffer *buf, struct drm_format_info format_info) { int x = 0, y = 0, height, width; int r, g, b, a, off; uint32_t stride; bool is_10_bit_yuv_encoding = false; NvBufSurfaceParams *surfparams; uint32_t pitch; surfparams = use_gbm ? NULL : &buf->surface->surfaceList[0]; a = get_max_alpha(drm_fd); // Draw X pattern switch (buf->format) { case DRM_FORMAT_NV12: case DRM_FORMAT_NV16: case DRM_FORMAT_NV24: case DRM_FORMAT_NV21: // Y plane if (use_gbm) { height = buf->height; width = buf->width; buf->data = gbm_bo_map(buf->gbm_bo, x, y, width, height, GBM_BO_TRANSFER_READ_WRITE, &stride, &buf->gbo_mapping); } else { mapBuffer(&height, &width, buf, 0, surfparams); } for (y = 0; y < height; ++y) { for (x = 0; x < width; ++x) { calculateRGB(x, y, width, height, &r, &g, &b); int Y = (int)(r * .299000 + g * .587000 + b * .114000); off = use_gbm ? gbm_bo_get_offset(buf->gbm_bo, 0) : 0; off += buf->pitch[0] * y + x; buf->data = use_gbm ? buf->data : (uint8_t*)surfparams->mappedAddr.addr[0]; buf->data[off] = Y; } } if (!use_gbm) { CHECK(NvBufSurfaceSyncForCpu (buf->surface, 0, 0)); // UV plane mapBuffer(&height, &width, buf, 1, surfparams); } else { height = height / 2; width = width / 2; } for (y = 0; y < height; y++){ for (x = 0; x < width;x++){ calculateRGB(x * format_info.planes[1].w, y * format_info.planes[1].h, width * format_info.planes[1].w, height * format_info.planes[1].h, &r, &g, &b); int U = (int)(r * -.168736 + g * -.331264 + b * .500000 + 128); int V = (int)(r * .500000 + g * -.418688 + b * -.081312 + 128); off = use_gbm ? gbm_bo_get_offset(buf->gbm_bo, 1) : 0; off += buf->pitch[1] * y + (x * format_info.planes[1].bpp / 8); buf->data = use_gbm ? buf->data : (uint8_t*)surfparams->mappedAddr.addr[1]; if (buf->format == DRM_FORMAT_NV21) { buf->data[off] = V; buf->data[off+1] = U; } else { buf->data[off] = U; buf->data[off+1] = V; } } } if (!use_gbm) NvBufSurfaceSyncForCpu (buf->surface, 0 , 1); break; case DRM_FORMAT_XRGB8888: case DRM_FORMAT_ABGR8888: if (use_gbm) { height = buf->height; width = buf->width; buf->data = gbm_bo_map(buf->gbm_bo, x, y, width, height, GBM_BO_TRANSFER_READ_WRITE, &stride, &buf->gbo_mapping); } else { mapBuffer(&height, &width, buf, 0, surfparams); } for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { calculateRGB(x, y, width, height, &r, &g, &b); off = use_gbm ? gbm_bo_get_offset(buf->gbm_bo, 0) : 0; off += buf->pitch[0] * y + x*4; int orderedComponent[4] = {0}; reorder_color_components(buf->format, r, g, b, a, orderedComponent); buf->data = use_gbm ? buf->data : (uint8_t*)surfparams->mappedAddr.addr[0]; for (int i = 0; i < 4; i++) buf->data[off + i] = orderedComponent[i]; } } if (!use_gbm) CHECK(NvBufSurfaceSyncForCpu (buf->surface, 0, 0)); break; case DRM_FORMAT_P010: case DRM_FORMAT_P210: case DRM_FORMAT_TEGRA_P010_709: case DRM_FORMAT_TEGRA_P010_2020: is_10_bit_yuv_encoding = true; case DRM_FORMAT_P012: // Y plane mapBuffer(&height, &width, buf, 0, surfparams); for (y = 0; y < height; y++) { for (x = 0; x< width; x++) { calculateRGB(x, y, width, height, &r, &g, &b); int Y = (int)(r * .299000 + g * .587000 + b * .114000); if (is_10_bit_yuv_encoding) { // 10-bit encoding uses nominal values four times those of the 8-bit encoding. Y *= 4; Y <<= 6; } else { // 12-bit encoding uses nominal values sixteen times those of the 8-bit encoding. Y *= 16; Y <<= 4; } off = surfparams->planeParams.pitch[0] * y + x * (format_info.planes[0].bpp / 8); buf->data = (uint8_t*)surfparams->mappedAddr.addr[0]; buf->data[off] = Y & 0x00ff; buf->data[off+1] = Y >> 8; } } CHECK(NvBufSurfaceSyncForCpu (buf->surface, 0, 0)); // UV plane mapBuffer(&height, &width, buf, 1, surfparams); for (y = 0; y < height; y++){ for (x = 0; x < width;x++){ calculateRGB(x * format_info.planes[1].w, y * format_info.planes[1].h, width * format_info.planes[1].w, height * format_info.planes[1].h, &r, &g, &b); int U = (int)(r * -.168736 + g * -.331264 + b * .500000 + 128); int V = (int)(r * .500000 + g * -.418688 + b * -.081312 + 128); if (is_10_bit_yuv_encoding) { U *= 4; V *= 4; U <<= 6; V <<= 6; } else { U *= 16; V *= 16; U <<= 4; V <<= 4; } off = surfparams->planeParams.pitch[1] * y + (x * format_info.planes[1].bpp / 8); buf->data = (uint8_t*)surfparams->mappedAddr.addr[1]; buf->data[off] = U & 0x00ff; buf->data[off+1] = U >> 8; buf->data[off+2] = V & 0x00ff; buf->data[off+3] = V >> 8; } } CHECK(NvBufSurfaceSyncForCpu (buf->surface, 0, 1)); break; case DRM_FORMAT_ARGB2101010: case DRM_FORMAT_TEGRA_ARGB2101010_709: case DRM_FORMAT_TEGRA_ARGB2101010_2020: if (use_gbm) { height = buf->height; width = buf->width; buf->data = gbm_bo_map(buf->gbm_bo, x, y, width, height, GBM_BO_TRANSFER_READ_WRITE, &stride, &buf->gbo_mapping); pitch = stride; } else { mapBuffer(&height, &width, buf, 0, surfparams); pitch = surfparams->planeParams.pitch[0]; } for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { calculateRGB(x, y, width, height, &r, &g, &b); off = pitch * y + x*4; write_pixel_RGB30(buf, off, a, r, g, b); } } if (!use_gbm) { CHECK(NvBufSurfaceSyncForCpu (buf->surface, 0, 0)); } break; case DRM_FORMAT_ABGR2101010: case DRM_FORMAT_TEGRA_ABGR2101010_709: case DRM_FORMAT_TEGRA_ABGR2101010_2020: if (use_gbm) { height = buf->height; width = buf->width; buf->data = gbm_bo_map(buf->gbm_bo, x, y, width, height, GBM_BO_TRANSFER_READ_WRITE, &stride, &buf->gbo_mapping);\ pitch = stride; } else { mapBuffer(&height, &width, buf, 0, surfparams); pitch = surfparams->planeParams.pitch[0]; } for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { calculateRGB(x, y, width, height, &r, &g, &b); off = pitch * y + x*4; write_pixel_RGB30(buf, off, a, b, g, r); } } if (!use_gbm) CHECK(NvBufSurfaceSyncForCpu (buf->surface, 0, 0)); break; case DRM_FORMAT_YUV420: case DRM_FORMAT_YVU420: case DRM_FORMAT_YUV444: for (int planeIdx = 0; planeIdx < 3; planeIdx++) { mapBuffer(&height, &width, buf, planeIdx, surfparams); for (y = 0; y < height; ++y) { for (x = 0; x < width; ++x) { calculateRGB(x * format_info.planes[planeIdx].w, y * format_info.planes[planeIdx].h, width * format_info.planes[planeIdx].w, height * format_info.planes[planeIdx].h, &r, &g, &b); int componentYUV = 0; // Y if (planeIdx == 0) componentYUV = (int)(r * .299000 + g * .587000 + b * .114000); // U/V else if (planeIdx == 1) { if (buf->format == DRM_FORMAT_YVU420) componentYUV = (int)(r * .500000 + g * -.418688 + b * -.081312 + 128); else componentYUV = (int)(r * -.168736 + g * -.331264 + b * .500000 + 128); } // V/U else { if (buf->format == DRM_FORMAT_YVU420) componentYUV = (int)(r * -.168736 + g * -.331264 + b * .500000 + 128); else componentYUV = (int)(r * .500000 + g * -.418688 + b * -.081312 + 128); } off = surfparams->planeParams.pitch[planeIdx] * y + x; buf->data = (uint8_t*)surfparams->mappedAddr.addr[planeIdx]; buf->data[off] = componentYUV; } } CHECK(NvBufSurfaceSyncForCpu (buf->surface, 0, planeIdx)); } break; } } static void create_succeeded(void *data, struct zwp_linux_buffer_params_v1 *params, struct wl_buffer *new_buffer) { struct buffer *buffer = data; buffer->buffer = new_buffer; /* When not using explicit synchronization listen to wl_buffer.release * for release notifications, otherwise we are going to use * zwp_linux_buffer_release_v1. */ if (!buffer->display->use_explicit_sync) { wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); } zwp_linux_buffer_params_v1_destroy(params); } static void create_failed(void *data, struct zwp_linux_buffer_params_v1 *params) { struct buffer *buffer = data; buffer->buffer = NULL; running = 0; zwp_linux_buffer_params_v1_destroy(params); fprintf(stderr, "Error: zwp_linux_buffer_params.create failed.\n"); } static const struct zwp_linux_buffer_params_v1_listener params_listener = { create_succeeded, create_failed }; static void destroy_dmabuf_buffer(int drm_fd, struct buffer *buffer) { int i; if (!use_gbm) { for (i = 0; i < buffer->num_planes; i++) { CHECK(NvBufSurfaceUnMap(buffer->surface, 0, i)); } NvBufSurfaceDestroy (buffer->surface); } else { gbm_bo_unmap(buffer->gbm_bo, buffer->gbo_mapping); gbm_bo_destroy(buffer->gbm_bo); } } static bool create_fbo_for_buffer(struct display *display, struct buffer *buffer) { static const int general_attribs = 4; static const int plane_attribs = 5; static const int entries_per_attrib = 2; EGLint attribs[(general_attribs + plane_attribs * 4) * entries_per_attrib + 1]; unsigned int atti = 0; attribs[atti++] = EGL_WIDTH; attribs[atti++] = buffer->width; attribs[atti++] = EGL_HEIGHT; attribs[atti++] = buffer->height; attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; attribs[atti++] = buffer->format; #define ADD_PLANE_ATTRIBS(plane_idx) { \ attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _FD_EXT; \ attribs[atti++] = buffer->buf_fds[plane_idx]; \ attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _OFFSET_EXT; \ attribs[atti++] = (int) buffer->offset[plane_idx]; \ attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _PITCH_EXT; \ attribs[atti++] = (int) buffer->pitch[plane_idx]; \ if (display->egl.has_dma_buf_import_modifiers) { \ attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_LO_EXT; \ attribs[atti++] = buffer->modifier & 0xFFFFFFFF; \ attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_HI_EXT; \ attribs[atti++] = buffer->modifier >> 32; \ } \ } if (buffer->num_planes > 0) ADD_PLANE_ATTRIBS(0); if (buffer->num_planes > 1) ADD_PLANE_ATTRIBS(1); if (buffer->num_planes > 2) ADD_PLANE_ATTRIBS(2); if (buffer->num_planes > 3) ADD_PLANE_ATTRIBS(3); #undef ADD_PLANE_ATTRIBS attribs[atti] = EGL_NONE; assert(atti < ARRAY_LENGTH(attribs)); buffer->egl_image = display->egl.create_image(display->egl.display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); if (buffer->egl_image == EGL_NO_IMAGE_KHR) { fprintf(stderr, "EGLImageKHR creation failed\n"); return false; } eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, display->egl.context); glGenTextures(1, &buffer->gl_texture); glBindTexture(GL_TEXTURE_2D, buffer->gl_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); display->egl.image_target_texture_2d(GL_TEXTURE_2D, buffer->egl_image); glGenFramebuffers(1, &buffer->gl_fbo); glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, buffer->gl_texture, 0); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { fprintf(stderr, "FBO creation failed\n"); return false; } return true; } static int create_dmabuf_buffer(struct display *display, struct buffer *buffer, int width, int height, int format, uint32_t opts) { struct zwp_linux_buffer_params_v1 *params; uint64_t modifier = DRM_FORMAT_MOD_LINEAR; uint32_t flags = 0; struct drm_format_info format_info; int i; NvBufSurfaceCreateParams nvbuf_params = {0}; NvBufSurfaceParams *surfparams; uint32_t colorFormat; buffer->display = display; buffer->release_fence_fd = -1; memset(&format_info, 0, sizeof(format_info)); if (!get_format_info(format, &format_info)) assert(!"buffer format not supported\n"); switch (format) { case DRM_FORMAT_NV12: colorFormat = use_gbm ? GBM_FORMAT_NV12 : NVBUF_COLOR_FORMAT_NV12; break; case DRM_FORMAT_NV21: colorFormat = use_gbm ? GBM_FORMAT_NV21 : NVBUF_COLOR_FORMAT_NV21; break; case DRM_FORMAT_NV16: colorFormat = use_gbm ? GBM_FORMAT_NV16 : NVBUF_COLOR_FORMAT_NV16; break; case DRM_FORMAT_NV24: colorFormat = NVBUF_COLOR_FORMAT_NV24; break; case DRM_FORMAT_P010: colorFormat = NVBUF_COLOR_FORMAT_NV12_10LE; break; case DRM_FORMAT_P210: colorFormat = NVBUF_COLOR_FORMAT_NV16_10LE; break; case DRM_FORMAT_P012: colorFormat = NVBUF_COLOR_FORMAT_NV12_12LE; break; case DRM_FORMAT_TEGRA_P010_709: colorFormat = NVBUF_COLOR_FORMAT_NV12_10LE_709; break; case DRM_FORMAT_TEGRA_P010_2020: colorFormat = NVBUF_COLOR_FORMAT_NV12_10LE_2020; break; case DRM_FORMAT_ARGB2101010: colorFormat = use_gbm ? DRM_FORMAT_ARGB2101010 : NVBUF_COLOR_FORMAT_BGRA_10_10_10_2_709; break; case DRM_FORMAT_TEGRA_ARGB2101010_709: colorFormat = NVBUF_COLOR_FORMAT_BGRA_10_10_10_2_709; break; case DRM_FORMAT_TEGRA_ARGB2101010_2020: colorFormat = NVBUF_COLOR_FORMAT_BGRA_10_10_10_2_2020; break; case DRM_FORMAT_ABGR2101010: colorFormat = use_gbm ? DRM_FORMAT_ABGR2101010 : NVBUF_COLOR_FORMAT_RGBA_10_10_10_2_709; break; case DRM_FORMAT_TEGRA_ABGR2101010_709: colorFormat = NVBUF_COLOR_FORMAT_RGBA_10_10_10_2_709; break; case DRM_FORMAT_TEGRA_ABGR2101010_2020: colorFormat = NVBUF_COLOR_FORMAT_RGBA_10_10_10_2_2020; break; case DRM_FORMAT_YUV420: colorFormat = use_gbm ? DRM_FORMAT_YUV420 : NVBUF_COLOR_FORMAT_YUV420; break; case DRM_FORMAT_YVU420: colorFormat = use_gbm ? DRM_FORMAT_YVU420 : NVBUF_COLOR_FORMAT_YVU420; break; case DRM_FORMAT_YUV444: colorFormat = use_gbm ? DRM_FORMAT_YUV444 : NVBUF_COLOR_FORMAT_YUV444; break; case DRM_FORMAT_ABGR8888: colorFormat = use_gbm ? DRM_FORMAT_ABGR8888 : NVBUF_COLOR_FORMAT_ABGR; break; default: colorFormat = use_gbm ? GBM_FORMAT_XRGB8888 : NVBUF_COLOR_FORMAT_xRGB; break; } if (use_gbm) { if (opts & OPT_ALLOC_BLOCKLINEAR) { #ifdef HAVE_GBM_BO_CREATE_WITH_MODIFIERS2 buffer->gbm_bo = gbm_bo_create_with_modifiers2(display->gbm, width, height, colorFormat, display->modifiers, display->modifiers_count, GBM_BO_USE_RENDERING); #else buffer->gbm_bo = gbm_bo_create_with_modifiers(display->gbm, width, height, colorFormat, display->modifiers, display->modifiers_count); #endif if (buffer->gbm_bo) { modifier = gbm_bo_get_modifier(buffer->gbm_bo); buffer->modifier = modifier; } } else { buffer->gbm_bo = gbm_bo_create(display->gbm, width, height, colorFormat, GBM_BO_USE_LINEAR); } if (!buffer->gbm_bo) { fprintf(stderr, "error: failed to create gbm buffer with format %X\n", colorFormat); return -1; } // populate the planes info buffer->num_planes = gbm_bo_get_plane_count(buffer->gbm_bo); buffer->width = gbm_bo_get_width(buffer->gbm_bo); buffer->height = gbm_bo_get_height(buffer->gbm_bo); for (i = 0; i < buffer->num_planes; i++) { buffer->pitch[i] = gbm_bo_get_stride_for_plane(buffer->gbm_bo, i); buffer->offset[i] = gbm_bo_get_offset(buffer->gbm_bo, i); } } else { nvbuf_params.width = width; nvbuf_params.height = height; if (opts & OPT_ALLOC_BLOCKLINEAR) { nvbuf_params.layout = NVBUF_LAYOUT_BLOCK_LINEAR; } else { nvbuf_params.layout = NVBUF_LAYOUT_PITCH; } nvbuf_params.memType = NVBUF_MEM_DEFAULT; nvbuf_params.colorFormat = colorFormat; CHECK(NvBufSurfaceCreate(&buffer->surface, 1, &nvbuf_params)); buffer->surface->numFilled = 1; // populate the planes info surfparams = &buffer->surface->surfaceList[0]; buffer->width = surfparams->width; buffer->height = surfparams->height; buffer->num_planes = surfparams->planeParams.num_planes; for (i = 0; i < buffer->num_planes; i++) { buffer->buf_fds[i] = (int)surfparams->bufferDesc; buffer->pitch[i] = surfparams->planeParams.pitch[i]; buffer->offset[i] = surfparams->planeParams.offset[i]; } if (opts & OPT_ALLOC_BLOCKLINEAR) { modifier = surfparams->paramex->planeParamsex.drmModifier[0]; buffer->modifier = modifier; } } buffer->format = format; if (display->render_type == SW_RENDERING) { fill_content(display->drm_fd, buffer, format_info); } else if (display->render_type == SW_RENDERING_BL) { fill_content_bl(buffer); } if (opts & OPT_Y_INVERTED) flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); for (i = 0; i < buffer->num_planes; i++ ) { if (use_gbm) { int ret; union gbm_bo_handle handle = gbm_bo_get_handle_for_plane(buffer->gbm_bo, i); if (handle.s32 == -1) { fprintf(stderr, "error: failed to get gbm_bo_handle\n"); gbm_bo_destroy(buffer->gbm_bo); return -1; } ret = drmPrimeHandleToFD(display->drm_fd, handle.u32, DRM_RDWR, &buffer->buf_fds[i]); if (ret < 0 || buffer->buf_fds[i] < 0) { fprintf(stderr, "error: failed to get dmabuf_fd\n"); gbm_bo_destroy(buffer->gbm_bo); return -1; } } zwp_linux_buffer_params_v1_add(params, buffer->buf_fds[i], i, buffer->offset[i], buffer->pitch[i], modifier >> 32, modifier & 0xffffffff); } zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, buffer); if (display->req_dmabuf_immediate) { buffer->buffer = zwp_linux_buffer_params_v1_create_immed(params, buffer->width, buffer->height, format, flags); /* When not using explicit synchronization listen to * wl_buffer.release for release notifications, otherwise we * are going to use zwp_linux_buffer_release_v1. */ if (!buffer->display->use_explicit_sync) { wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); } } else { zwp_linux_buffer_params_v1_create(params, buffer->width, buffer->height, format, flags); } if (display->render_type == GL_RENDERING) { if (!create_fbo_for_buffer(display, buffer)) return -1; } return 0; } static void xdg_surface_handle_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct window *window = data; xdg_surface_ack_configure(surface, serial); if (window->initialized && window->wait_for_configure) redraw(window, NULL, 0); window->wait_for_configure = false; } static const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure, }; static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { } static void xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = 0; } static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, }; static const char *vert_shader_text = "uniform float offset;\n" "uniform mat4 reflection;\n" "attribute vec4 pos;\n" "attribute vec4 color;\n" "varying vec4 v_color;\n" "void main() {\n" " gl_Position = reflection * (pos + vec4(offset, offset, 0.0, 0.0));\n" " v_color = color;\n" "}\n"; static const char *frag_shader_text = "precision highp float;\n" "varying vec4 v_color;\n" "void main() {\n" " gl_FragColor = v_color;\n" "}\n"; static GLuint create_shader(const char *source, GLenum shader_type) { GLuint shader; GLint status; shader = glCreateShader(shader_type); assert(shader != 0); glShaderSource(shader, 1, (const char **) &source, NULL); glCompileShader(shader); glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (!status) { char log[1000]; GLsizei len; glGetShaderInfoLog(shader, 1000, &len, log); fprintf(stderr, "Error: compiling %s: %.*s\n", shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", len, log); return 0; } return shader; } static GLuint create_and_link_program(GLuint vert, GLuint frag) { GLint status; GLuint program = glCreateProgram(); glAttachShader(program, vert); glAttachShader(program, frag); glLinkProgram(program); glGetProgramiv(program, GL_LINK_STATUS, &status); if (!status) { char log[1000]; GLsizei len; glGetProgramInfoLog(program, 1000, &len, log); fprintf(stderr, "Error: linking:\n%.*s\n", len, log); return 0; } return program; } static bool window_set_up_gl(struct window *window) { GLuint vert = create_shader(vert_shader_text, GL_VERTEX_SHADER); GLuint frag = create_shader(frag_shader_text, GL_FRAGMENT_SHADER); window->gl.program = create_and_link_program(vert, frag); glDeleteShader(vert); glDeleteShader(frag); window->gl.pos = glGetAttribLocation(window->gl.program, "pos"); window->gl.color = glGetAttribLocation(window->gl.program, "color"); glUseProgram(window->gl.program); window->gl.offset_uniform = glGetUniformLocation(window->gl.program, "offset"); window->gl.reflection_uniform = glGetUniformLocation(window->gl.program, "reflection"); return window->gl.program != 0; } static void destroy_window(struct display *display, struct window *window) { int i; if (window->gl.program) glDeleteProgram(window->gl.program); if (window->callback) wl_callback_destroy(window->callback); if (window->buffers) { for (i = 0; i < NUM_BUFFERS; i++) { if (!window->buffers[i].buffer) continue; wl_buffer_destroy(window->buffers[i].buffer); destroy_dmabuf_buffer(display->drm_fd, &window->buffers[i]); } free(window->buffers); } if (window->xdg_toplevel) xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) xdg_surface_destroy(window->xdg_surface); if (window->surface_sync) zwp_linux_surface_synchronization_v1_destroy(window->surface_sync); wl_surface_destroy(window->surface); free(window); } static struct window * create_window(struct display *display, int width, int height, int format, uint32_t opts) { struct window *window; int i; int ret; window = calloc(1, sizeof *window); if (!window) return NULL; window->callback = NULL; window->display = display; window->width = width; window->height = height; window->surface = wl_compositor_create_surface(display->compositor); if (display->wm_base) { window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, window->surface); assert(window->xdg_surface); xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); assert(window->xdg_toplevel); xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window); xdg_toplevel_set_title(window->xdg_toplevel, "simple-dmabuf"); window->wait_for_configure = true; wl_surface_commit(window->surface); } else if (display->fshell) { zwp_fullscreen_shell_v1_present_surface(display->fshell, window->surface, ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, NULL); } else { assert(0); } window->buffers = calloc(NUM_BUFFERS, sizeof(*(window->buffers))); if (!window->buffers) { destroy_window(display, window); return NULL; } for (i = 0; i < NUM_BUFFERS; ++i) { ret = create_dmabuf_buffer(display, &window->buffers[i], width, height, format, opts); if (ret < 0) { destroy_window(display, window); return NULL; } } if (display->explicit_sync) { window->surface_sync = zwp_linux_explicit_synchronization_v1_get_synchronization( display->explicit_sync, window->surface); assert(window->surface_sync); } if (display->render_type == GL_RENDERING) { if (!window_set_up_gl(window)) { destroy_window(display, window); return NULL; } } return window; } static int create_egl_fence_fd(struct window *window) { struct display *d = window->display; EGLSyncKHR sync = d->egl.create_sync(d->egl.display, EGL_SYNC_NATIVE_FENCE_ANDROID, NULL); int fd; assert(sync != EGL_NO_SYNC_KHR); /* We need to flush before we can get the fence fd. */ glFlush(); fd = d->egl.dup_native_fence_fd(d->egl.display, sync); assert(fd >= 0); d->egl.destroy_sync(d->egl.display, sync); return fd; } static struct buffer * window_next_buffer(struct window *window) { int i; for (i = 0; i < NUM_BUFFERS; i++) if (!window->buffers[i].busy) return &window->buffers[i]; return NULL; } static const struct wl_callback_listener frame_listener; struct matrix { float d[16]; }; /* m <- n * m, that is, m is multiplied on the LEFT. */ static void matrix_multiply(struct matrix *m, const struct matrix *n) { struct matrix tmp; const float *row, *column; int i, j, k; for (i = 0; i < 4; i++) { row = m->d + i * 4; for (j = 0; j < 4; j++) { tmp.d[4 * i + j] = 0; column = n->d + j; for (k = 0; k < 4; k++) tmp.d[4 * i + j] += row[k] * column[k * 4]; } } memcpy(m, &tmp, sizeof tmp); } static void matrix_init(struct matrix *matrix) { static const struct matrix identity = { .d = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }, }; memcpy(matrix, &identity, sizeof identity); } static void matrix_scale(struct matrix *matrix, float x, float y,float z) { struct matrix scale = { .d = { x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1 }, }; matrix_multiply(matrix, &scale); } /* Renders a square moving from the lower left corner to the * upper right corner of the window. The square's vertices have * the following colors: * * green +-----+ yellow * | | * | | * red +-----+ blue */ static void render(struct window *window, struct buffer *buffer) { /* Complete a movement iteration in 5000 ms. */ static const uint64_t iteration_ms = 5000; static const GLfloat verts[4][2] = { { -0.5, -0.5 }, { -0.5, 0.5 }, { 0.5, -0.5 }, { 0.5, 0.5 } }; GLfloat colors[4][4] = { { 1, 0, 0, 1}, { 0, 1, 0, 1}, { 0, 0, 1, 1}, { 1, 1, 0, 1} }; GLfloat offset; struct timeval tv; uint64_t time_ms; struct matrix reflection; gettimeofday(&tv, NULL); time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* Split time_ms in repeating windows of [0, iteration_ms) and map them * to offsets in the [-0.5, 0.5) range. */ offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5; matrix_init(&reflection); /* perform a reflection about x-axis to keep the same orientation of * the vertices colors, as outlined in the comment at the beginning * of this function. * * We need to render upside-down, because rendering through an FBO * causes the bottom of the image to be written to the top pixel row of * the buffer, y-flipping the image. * * Reflection is a specialized version of scaling with the * following matrix: * * [1, 0, 0] * [0, -1, 0] * [0, 0, 1] */ matrix_scale(&reflection, 1, -1, 1); /* Direct all GL draws to the buffer through the FBO */ glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); glViewport(0, 0, window->width, window->height); glUniform1f(window->gl.offset_uniform, offset); glUniformMatrix4fv(window->gl.reflection_uniform, 1, GL_FALSE, (GLfloat *) reflection.d); glClearColor(0.0,0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); glVertexAttribPointer(window->gl.color, 4, GL_FLOAT, GL_FALSE, 0, colors); glEnableVertexAttribArray(window->gl.pos); glEnableVertexAttribArray(window->gl.color); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(window->gl.pos); glDisableVertexAttribArray(window->gl.color); } static void buffer_fenced_release(void *data, struct zwp_linux_buffer_release_v1 *release, int32_t fence) { struct buffer *buffer = data; assert(release == buffer->buffer_release); assert(buffer->release_fence_fd == -1); buffer->busy = 0; buffer->release_fence_fd = fence; zwp_linux_buffer_release_v1_destroy(buffer->buffer_release); buffer->buffer_release = NULL; } static void buffer_immediate_release(void *data, struct zwp_linux_buffer_release_v1 *release) { struct buffer *buffer = data; assert(release == buffer->buffer_release); assert(buffer->release_fence_fd == -1); buffer->busy = 0; zwp_linux_buffer_release_v1_destroy(buffer->buffer_release); buffer->buffer_release = NULL; } static const struct zwp_linux_buffer_release_v1_listener buffer_release_listener = { buffer_fenced_release, buffer_immediate_release, }; static void wait_for_buffer_release_fence(struct buffer *buffer) { struct display *d = buffer->display; EGLint attrib_list[] = { EGL_SYNC_NATIVE_FENCE_FD_ANDROID, buffer->release_fence_fd, EGL_NONE, }; EGLSyncKHR sync = d->egl.create_sync(d->egl.display, EGL_SYNC_NATIVE_FENCE_ANDROID, attrib_list); int ret; assert(sync); /* EGLSyncKHR takes ownership of the fence fd. */ buffer->release_fence_fd = -1; if (d->egl.wait_sync) ret = d->egl.wait_sync(d->egl.display, sync, 0); else ret = d->egl.client_wait_sync(d->egl.display, sync, 0, EGL_FOREVER_KHR); assert(ret == EGL_TRUE); ret = d->egl.destroy_sync(d->egl.display, sync); assert(ret == EGL_TRUE); } static void redraw(void *data, struct wl_callback *callback, uint32_t time) { struct window *window = data; struct buffer *buffer; buffer = window_next_buffer(window); if (!buffer) { fprintf(stderr, !callback ? "Failed to create the first buffer.\n" : "All buffers busy at redraw(). Server bug?\n"); abort(); } if (buffer->display->render_type == GL_RENDERING) { if (buffer->release_fence_fd >= 0) wait_for_buffer_release_fence(buffer); render(window, buffer); if (window->display->use_explicit_sync) { int fence_fd = create_egl_fence_fd(window); zwp_linux_surface_synchronization_v1_set_acquire_fence( window->surface_sync, fence_fd); close(fence_fd); buffer->buffer_release = zwp_linux_surface_synchronization_v1_get_release(window->surface_sync); zwp_linux_buffer_release_v1_add_listener( buffer->buffer_release, &buffer_release_listener, buffer); } else { glFlush(); } } /* XXX: would be nice to draw something that changes here... */ wl_surface_attach(window->surface, buffer->buffer, 0, 0); wl_surface_damage(window->surface, 0, 0, window->width, window->height); if (callback) wl_callback_destroy(callback); window->callback = wl_surface_frame(window->surface); wl_callback_add_listener(window->callback, &frame_listener, window); wl_surface_commit(window->surface); buffer->busy = 1; } static const struct wl_callback_listener frame_listener = { redraw }; static void dmabuf_modifiers(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { struct display *d = data; switch (format) { case DRM_FORMAT_NV12: case DRM_FORMAT_NV16: case DRM_FORMAT_NV21: case DRM_FORMAT_NV24: case DRM_FORMAT_XRGB8888: case DRM_FORMAT_ABGR8888: case DRM_FORMAT_ARGB2101010: case DRM_FORMAT_ABGR2101010: case DRM_FORMAT_TEGRA_ABGR2101010_709: case DRM_FORMAT_TEGRA_ABGR2101010_2020: case DRM_FORMAT_P010: case DRM_FORMAT_P210: case DRM_FORMAT_P012: case DRM_FORMAT_TEGRA_P010_709: case DRM_FORMAT_TEGRA_P010_2020: case DRM_FORMAT_YUV420: case DRM_FORMAT_YVU420: case DRM_FORMAT_YUV444: ++d->modifiers_count; d->modifiers = realloc(d->modifiers, d->modifiers_count * sizeof(*d->modifiers)); d->modifiers[d->modifiers_count - 1] = ((uint64_t)modifier_hi << 32) | modifier_lo; break; default: break; } } static void dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format) { /* XXX: deprecated */ } static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { dmabuf_format, dmabuf_modifiers }; static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { xdg_wm_base_pong(shell, serial); } static const struct xdg_wm_base_listener wm_base_listener = { xdg_wm_base_ping, }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { struct display *d = data; if (strcmp(interface, "wl_compositor") == 0) { d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); } else if (strcmp(interface, "xdg_wm_base") == 0) { d->wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { d->fshell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1); } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { if (version < 3) return; d->dmabuf = wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, 3); zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d); } else if (strcmp(interface, "zwp_linux_explicit_synchronization_v1") == 0) { d->explicit_sync = wl_registry_bind( registry, id, &zwp_linux_explicit_synchronization_v1_interface, 1); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static bool check_egl_extension(const char *extensions, const char *extension) { size_t extlen = strlen(extension); const char *end = extensions + strlen(extensions); while (extensions < end) { size_t n = 0; /* Skip whitespaces, if any */ if (*extensions == ' ') { extensions++; continue; } n = strcspn(extensions, " "); /* Compare strings */ if (n == extlen && strncmp(extension, extensions, n) == 0) return true; /* Found */ extensions += n; } /* Not found */ return false; } static inline void * platform_get_egl_proc_address(const char *address) { const char *extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (extensions && (check_egl_extension(extensions, "EGL_EXT_platform_wayland") || check_egl_extension(extensions, "EGL_KHR_platform_wayland"))) { return (void *) eglGetProcAddress(address); } return NULL; } static inline EGLDisplay platform_get_egl_display(EGLenum platform, void *native_display, const EGLint *attrib_list) { static PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display = NULL; if (!get_platform_display) { get_platform_display = (PFNEGLGETPLATFORMDISPLAYEXTPROC) platform_get_egl_proc_address( "eglGetPlatformDisplayEXT"); } if (get_platform_display) return get_platform_display(platform, native_display, attrib_list); return eglGetDisplay((EGLNativeDisplayType) native_display); } static bool display_set_up_egl(char* drm_render_node, struct display *display) { static EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_PROTECTED_CONTENT_EXT, EGL_FALSE, EGL_NONE }; EGLint major, minor,ret, count; const char *egl_extensions = NULL; const char *gl_extensions = NULL; EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_ALPHA_SIZE, 1, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; EGLint device_platform_attribs[] = { EGL_DRM_MASTER_FD_EXT, display->drm_fd, EGL_NONE }; EGLDeviceEXT *egl_devs, egl_dev = EGL_NO_DEVICE_EXT; int num_devices, i; const char *extensions; PFNEGLQUERYDEVICESEXTPROC query_devices; PFNEGLQUERYDEVICESTRINGEXTPROC query_device_string; const char *drm_device_file; extensions = (const char *)eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (!extensions) { fprintf(stderr, "retrieving EGL extension string failed\n"); goto error; } if (!check_egl_extension(extensions, "EGL_EXT_device_base") && (!check_egl_extension(extensions, "EGL_EXT_device_query") || !check_egl_extension(extensions, "EGL_EXT_device_enumeration"))) { fprintf(stderr, "EGL_EXT_device_base not supported\n"); goto error; } query_devices = (void *) eglGetProcAddress("eglQueryDevicesEXT"); query_device_string = (void *) eglGetProcAddress("eglQueryDeviceStringEXT"); if (!query_devices || !query_device_string) goto error; if (!query_devices(0, NULL, &num_devices)) { fprintf(stderr, "failed to get max egl devices\n"); goto error; } egl_devs = malloc(num_devices * sizeof(*egl_devs)); if (!egl_devs) goto error; if (!query_devices(num_devices, egl_devs, &num_devices)) { fprintf(stderr, "failed to get egl devices\n"); free(egl_devs); goto error; } for (i = 0; i < num_devices; i++) { drm_device_file = query_device_string(egl_devs[i], EGL_DRM_DEVICE_FILE_EXT); if (drm_device_file == NULL) continue; if (strcmp(drm_device_file, drm_render_node) == 0) { egl_dev = egl_devs[i]; break; } } free(egl_devs); if (egl_dev == EGL_NO_DEVICE_EXT) { fprintf(stderr, "failed to query DRM device name\n"); goto error; } if (i == num_devices) { fprintf(stderr, "failed to find egl device %s\n", drm_render_node); goto error; } display->egl.display = platform_get_egl_display(EGL_PLATFORM_DEVICE_EXT, (void*)egl_dev, device_platform_attribs); if (eglInitialize(display->egl.display, &major, &minor) == EGL_FALSE) { fprintf(stderr, "Failed to initialize EGLDisplay\n"); goto error; } if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { fprintf(stderr, "Failed to bind OpenGL ES API\n"); goto error; } egl_extensions = eglQueryString(display->egl.display, EGL_EXTENSIONS); assert(egl_extensions != NULL); if (!check_egl_extension(egl_extensions, "EGL_EXT_image_dma_buf_import")) { fprintf(stderr, "EGL_EXT_image_dma_buf_import not supported\n"); goto error; } if (!check_egl_extension(egl_extensions, "EGL_KHR_surfaceless_context")) { fprintf(stderr, "EGL_KHR_surfaceless_context not supported\n"); goto error; } if (check_egl_extension(egl_extensions, "EGL_KHR_no_config_context")) { display->egl.has_no_config_context = true; } if (display->egl.has_no_config_context) { display->egl.conf = EGL_NO_CONFIG_KHR; } else { fprintf(stderr, "Warning: EGL_KHR_no_config_context not supported\n"); ret = eglChooseConfig(display->egl.display, config_attribs, &display->egl.conf, 1, &count); assert(ret && count >= 1); } display->egl.context = eglCreateContext(display->egl.display, display->egl.conf, EGL_NO_CONTEXT, context_attribs); if (display->egl.context == EGL_NO_CONTEXT) { fprintf(stderr, "Failed to create EGLContext\n"); goto error; } eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, display->egl.context); gl_extensions = (const char *) glGetString(GL_EXTENSIONS); assert(gl_extensions != NULL); if (!check_egl_extension(gl_extensions, "GL_OES_EGL_image")) { fprintf(stderr, "GL_OES_EGL_image not supported\n"); goto error; } if (check_egl_extension(egl_extensions, "EGL_EXT_image_dma_buf_import_modifiers")) { display->egl.has_dma_buf_import_modifiers = true; display->egl.query_dma_buf_modifiers = (void *) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); assert(display->egl.query_dma_buf_modifiers); } display->egl.create_image = (void *) eglGetProcAddress("eglCreateImageKHR"); assert(display->egl.create_image); display->egl.destroy_image = (void *) eglGetProcAddress("eglDestroyImageKHR"); assert(display->egl.destroy_image); display->egl.image_target_texture_2d = (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); assert(display->egl.image_target_texture_2d); if (check_egl_extension(egl_extensions, "EGL_KHR_fence_sync") && check_egl_extension(egl_extensions, "EGL_ANDROID_native_fence_sync")) { display->egl.create_sync = (void *) eglGetProcAddress("eglCreateSyncKHR"); assert(display->egl.create_sync); display->egl.destroy_sync = (void *) eglGetProcAddress("eglDestroySyncKHR"); assert(display->egl.destroy_sync); display->egl.client_wait_sync = (void *) eglGetProcAddress("eglClientWaitSyncKHR"); assert(display->egl.client_wait_sync); display->egl.dup_native_fence_fd = (void *) eglGetProcAddress("eglDupNativeFenceFDANDROID"); assert(display->egl.dup_native_fence_fd); } if (check_egl_extension(egl_extensions, "EGL_KHR_wait_sync")) { display->egl.wait_sync = (void *) eglGetProcAddress("eglWaitSyncKHR"); assert(display->egl.wait_sync); } return true; error: return false; } static bool display_update_supported_modifiers_for_egl(struct display *d) { uint64_t *egl_modifiers = NULL; int num_egl_modifiers = 0; EGLBoolean ret; int i; bool try_modifiers = d->egl.has_dma_buf_import_modifiers; if (try_modifiers) { ret = d->egl.query_dma_buf_modifiers(d->egl.display, BUFFER_FORMAT, 0, /* max_modifiers */ NULL, /* modifiers */ NULL, /* external_only */ &num_egl_modifiers); if (ret == EGL_FALSE) { fprintf(stderr, "Failed to query num EGL modifiers for format\n"); goto error; } } if (!num_egl_modifiers) try_modifiers = false; /* If EGL doesn't support modifiers, don't use them at all. */ if (!try_modifiers) { d->modifiers_count = 0; free(d->modifiers); d->modifiers = NULL; return true; } egl_modifiers = calloc(1, (num_egl_modifiers * sizeof(*egl_modifiers))); ret = d->egl.query_dma_buf_modifiers(d->egl.display, BUFFER_FORMAT, num_egl_modifiers, egl_modifiers, NULL, /* external_only */ &num_egl_modifiers); if (ret == EGL_FALSE) { fprintf(stderr, "Failed to query EGL modifiers for format\n"); goto error; } /* Poor person's set intersection: d->modifiers INTERSECT * egl_modifiers. If a modifier is not supported, replace it with * DRM_FORMAT_MOD_INVALID in the d->modifiers array. */ for (i = 0; i < d->modifiers_count; ++i) { uint64_t mod = d->modifiers[i]; bool egl_supported = false; int j; for (j = 0; j < num_egl_modifiers; ++j) { if (egl_modifiers[j] == mod) { egl_supported = true; break; } } if (!egl_supported) d->modifiers[i] = DRM_FORMAT_MOD_INVALID; } free(egl_modifiers); return true; error: free(egl_modifiers); return false; } static struct display * create_display(char *drm_render_node, int opts, int format) { struct display *display; display = malloc(sizeof *display); if (display == NULL) { fprintf(stderr, "out of memory\n"); return NULL; } memset(display, 0, sizeof(*display)); if (opts & OPT_ALLOC_BLOCKLINEAR) { if (opts & OPT_USE_GBM) { if ((format == DRM_FORMAT_XRGB8888) || (format == DRM_FORMAT_ABGR8888) || (format == DRM_FORMAT_ARGB2101010) || (format == DRM_FORMAT_ABGR2101010)) { display->render_type = GL_RENDERING; } else { fprintf(stderr, "Rendering for multi-planar block linear buffers from GBM is not supported\n"); return NULL; } } else { // NvBufSurface if ((format == DRM_FORMAT_XRGB8888) || (format == DRM_FORMAT_ABGR8888) || (format == DRM_FORMAT_ARGB2101010) || (format == DRM_FORMAT_ABGR2101010)) { display->render_type = GL_RENDERING; } else if (format == DRM_FORMAT_NV12) { display->render_type = SW_RENDERING_BL; } else { fprintf(stderr, "Rednering for given format with block linear is not supported\n"); free(display); return NULL; } } } else { display->render_type = SW_RENDERING; } display->drm_fd = open(drm_render_node, O_RDWR); if (display->drm_fd < 0) { fprintf(stderr, "Couldn't open %s\n", drm_render_node); free(display); return NULL; } display->display = wl_display_connect(NULL); assert(display->display); display->req_dmabuf_immediate = opts & OPT_IMMEDIATE; display->registry = wl_display_get_registry(display->display); wl_registry_add_listener(display->registry, ®istry_listener, display); wl_display_roundtrip(display->display); if (display->dmabuf == NULL) { close(display->drm_fd); fprintf(stderr, "No zwp_linux_dmabuf global\n"); return NULL; } wl_display_roundtrip(display->display); if (opts & OPT_USE_GBM) { use_gbm = true; display->gbm = gbm_create_device(display->drm_fd); } if (display->render_type == GL_RENDERING) { display_set_up_egl(drm_render_node, display); display_update_supported_modifiers_for_egl(display); /* We use explicit sync if the gl renderer is used */ display->use_explicit_sync = display->explicit_sync && display->egl.dup_native_fence_fd; } // TODO check whether requested format is supported. // TODO check whether linear modifier is supported. return display; } static void destroy_display(struct display *display) { if (display->dmabuf) zwp_linux_dmabuf_v1_destroy(display->dmabuf); if (display->wm_base) xdg_wm_base_destroy(display->wm_base); if (display->fshell) zwp_fullscreen_shell_v1_release(display->fshell); if (display->compositor) wl_compositor_destroy(display->compositor); if (use_gbm) gbm_device_destroy(display->gbm); if (display->drm_fd >= 0) close(display->drm_fd); if (display->egl.context != EGL_NO_CONTEXT) eglDestroyContext(display->egl.display, display->egl.context); if (display->egl.display != EGL_NO_DISPLAY) eglTerminate(display->egl.display); wl_registry_destroy(display->registry); wl_display_flush(display->display); wl_display_disconnect(display->display); free(display); } static void signal_int(int signum) { running = 0; } static void print_usage_and_exit(void) { printf("usage flags:\n" "\t'--use-gbm'\n\t\tto use gbm buf instead of nvbuf utils\n" "\t'--import-immediate=<>'\n\t\t0 to import dmabuf via roundtrip," "\n\t\t1 to enable import without roundtrip\n" "\t'--y-inverted=<>'\n\t\t0 to not pass Y_INVERTED flag," "\n\t\t1 to pass Y_INVERTED flag\n" "\t'--import-format='\n\t\tXRGB to import dmabuf as XRGB8888," "\n\t\tABGR to import dmabuf as ABGR8888\n" "\n\t\tNV12 to import dmabuf as NV12\n" "\n\t\tNV21 to import dmabuf as NV21\n" "\n\t\tNV16 to import dmabuf as NV16\n" "\n\t\tNV24 to import dmabuf as NV24\n" "\n\t\tP010 to import dmabuf as P010\n" "\n\t\tP012 to import dmabuf as P012\n" "\n\t\tP210 to import dmabuf as P210\n" "\n\t\tYUV420 to import dmabuf as YUV420\n" "\n\t\tYVU420 to import dmabuf as YVU420\n" "\n\t\tYUV444 to import dmabuf as YUV444\n" "\n\t\tABGR2101010 to import dmabuf as ABGR2101010\n" "\n\t\tARGB2101010 to import dmabuf as ARGB2101010\n" "\t'--loop='\n\t\tNumber of iterations to execute," "\n\t\tDefault is set to 1\n" "\t'--block-linear'\n\t\tAllocate buffers with block linear modifier," "\n\t\tOnly the NV12 is supported on multi-planar formats." "\n"); exit(0); } static int is_true(const char* c) { if (!strcmp(c, "1")) return 1; if (!strcmp(c, "0")) return 0; print_usage_and_exit(); return 0; } static int parse_import_format(const char* c) { if (!strcmp(c, "XRGB")) return DRM_FORMAT_XRGB8888; if (!strcmp(c, "ABGR")) return DRM_FORMAT_ABGR8888; if (!strcmp(c, "ARGB2101010")) return DRM_FORMAT_ARGB2101010; if (!strcmp(c, "TEGRA_ARGB2101010_709")) return DRM_FORMAT_TEGRA_ARGB2101010_709; if (!strcmp(c, "TEGRA_ARGB2101010_2020")) return DRM_FORMAT_TEGRA_ARGB2101010_2020; if (!strcmp(c, "ABGR2101010")) return DRM_FORMAT_ABGR2101010; if (!strcmp(c, "TEGRA_ABGR2101010_709")) return DRM_FORMAT_TEGRA_ABGR2101010_709; if (!strcmp(c, "TEGRA_ABGR2101010_2020")) return DRM_FORMAT_TEGRA_ABGR2101010_2020; if (!strcmp(c, "NV12")) return DRM_FORMAT_NV12; if (!strcmp(c, "NV21")) return DRM_FORMAT_NV21; if (!strcmp(c, "NV16")) return DRM_FORMAT_NV16; if (!strcmp(c, "NV24")) return DRM_FORMAT_NV24; if (!strcmp(c, "P010")) return DRM_FORMAT_P010; if (!strcmp(c, "P210")) return DRM_FORMAT_P210; if (!strcmp(c, "P012")) return DRM_FORMAT_P012; if (!strcmp(c, "TEGRA_P010_709")) return DRM_FORMAT_TEGRA_P010_709; if (!strcmp(c, "TEGRA_P010_2020")) return DRM_FORMAT_TEGRA_P010_2020; if (!strcmp(c, "YUV420")) return DRM_FORMAT_YUV420; if (!strcmp(c, "YVU420")) return DRM_FORMAT_YVU420; if (!strcmp(c, "YUV444")) return DRM_FORMAT_YUV444; print_usage_and_exit(); return 0; } // This routine returns 0 (SUCCESS) if DRM node is valid by checking if // DRM resources available static int drm_util_mode_get_resources(int fd) { drmModeRes *res_info = drmModeGetResources (fd); if (res_info == NULL) { return -1; } else if (res_info->count_connectors < 1) { drmModeFreeResources (res_info); return -1; } drmModeFreeResources (res_info); return 0; } static char* drm_util_find_device(void) { DIR *dir; struct dirent *dent; dir = opendir("/dev/dri"); if (!dir) { fprintf(stderr, "Failed to open /dev/dri\n"); return NULL; } while ((dent = readdir(dir))) { char path[256]; int fd = -1; if (strncmp(dent->d_name, "card", 4)) continue; memset(path, 0, sizeof(path)); strcpy(path, "/dev/dri/"); strcat(path, dent->d_name); fd = open(path, O_RDWR|O_CLOEXEC); if (fd < 0) continue; if (drm_util_mode_get_resources(fd) < 0) { close(fd); continue; } close(fd); char *driNode = (char*)malloc(sizeof(char) * strlen(path)); strncpy(driNode, path, strlen(path)); closedir(dir); return driNode; } closedir(dir); fprintf(stderr, "Could not find valid DRM node\n"); return NULL; } int main(int argc, char **argv) { struct sigaction sigint; struct display *display; struct window *window; int i, opts = 0, loops = 1; int import_format = DRM_FORMAT_XRGB8888; int c, option_index, ret = 0; char *drm_render_node = NULL; int width = 256; int height = 256; static struct option long_options[] = { {"use-gbm", no_argument , 0, 'g' }, {"import-format", required_argument, 0, 'f' }, {"import-immediate", required_argument, 0, 'i' }, {"y-inverted", required_argument, 0, 'y' }, {"help", no_argument , 0, 'h' }, {"loop", required_argument, 0, 'l' }, {"block-linear", no_argument, 0, 'b' }, {0, 0, 0, 0} }; drm_render_node = drm_util_find_device(); while ((c = getopt_long(argc, argv, "hf:i:y:l:g:b:", long_options, &option_index)) != -1) { switch (c) { case 'f': import_format = parse_import_format(optarg); break; case 'i': if (is_true(optarg)) opts |= OPT_IMMEDIATE; break; case 'y': if (is_true(optarg)) opts |= OPT_Y_INVERTED; break; case 'l': loops = atoi(optarg); break; case 'g': opts |= OPT_USE_GBM; break; case 'b': opts |= OPT_ALLOC_BLOCKLINEAR; break; default: if (drm_render_node) { free(drm_render_node); drm_render_node = NULL; } print_usage_and_exit(); } } display = create_display(drm_render_node, opts, import_format); if (drm_render_node) { free(drm_render_node); drm_render_node = NULL; } if (!display) return 1; if (opts & OPT_ALLOC_BLOCKLINEAR) { if (import_format == DRM_FORMAT_NV12) { width = nv12_format_info.width; height = nv12_format_info.height; } } window = create_window(display, width, height, import_format, opts); if (!window) { destroy_display(display); return 1; } sigint.sa_handler = signal_int; sigemptyset(&sigint.sa_mask); sigint.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sigint, NULL); /* Here we retrieve the linux-dmabuf objects if executed without immed, * or error */ wl_display_roundtrip(display->display); if (!running) return 1; window->initialized = true; if (!window->wait_for_configure) redraw(window, NULL, 0); for (i = 0; i < loops; i++) { ret = wl_display_dispatch(display->display); if(ret < 0) break; } if (ret < 0) fprintf(stderr,"\nTEST FAILED\n"); else fprintf(stdout,"\nTEST PASSED\n"); fprintf(stdout, "simple-dmabuf exiting\n"); destroy_window(display, window); destroy_display(display); return 0; }