天天看點

EGL Off-Screen rendering using GBM

https://blog.csdn.net/weixin_42263483/article/details/100576577

最近研究EGL的off-screen rendering,特别是使用GBM這塊,網上資料很少。綜合搜集到的資料,整合了一個小demo。很多注意事項都寫在代碼注釋裡面了。

EGL Spec的介紹,沒有提及如何使用GBM進行off-screen rendering。因為這屬于native platform的實作。

EGLSurfaces:

1. windows: used for onscreen rendering

2. pbuffers: used for offscreen rendering

3. pixmaps: used for offscreen rendering into buffers that may be accessed through native APIs.

EGL windows and pixmaps are tied to platform windows and pixmaps.

EGL supports off-screen rendering surfaces in pbuffers. Pbuffers differ from windows in the followings ways:

1. Pbuffers are typically allocated in offscreen(non-visible) graphics memory and are intended only for accelerated

   offscreen rendering.

2. Pbuffers are EGL resources and have no associated native window or native window type. It may not by possible to

   render to pbuffers using native rendering APIs.

更多參考資料:

https://www.khronos.org/registry/EGL/extensions/MESA/EGL_MESA_platform_gbm.txt

This extension defines how to create EGL resources from native GBM resources using the functions defined by EGL_EXT_platform_base.

(GBM is a Generic Buffer Manager for Linux).

https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_platform_gbm.txt  (EGL 1.5 is required.)

This extension defines how to create EGL resources from native GBM resources using the EGL 1.5 platform functionality

(GBM is a Generic Buffer Manager for Linux).

EGL/GBM does not support Pbuffers. It doesn't support Pixmaps either.

To create an offscreen surface with EGL/GBM, you must pass a gbm_surface to eglCreateWindowSurface. "Window" is a misnomer.

No real "window" gets created. The resultant buffer will remain offscreen unless you use the kernel's KMS APIs to post it to the display.

// eglGetDisplay((EGLNativeDisplayType)my_gbm_device);

// eglCreateWindowSurface(egl_dpy, egl_config, (EGLNativeWindowType)my_gbm_surface, NULL);

or

// eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, my_gbm_device, NULL);

// eglCreatePlatformWindowSurfaceEXT(egl_dpy, egl_config, my_gbm_surface, NULL);

http://virtuousgeek.org/blog/index.php/jbarnes/2011/10/31/writing_stanalone_programs_with_egl_and_

https://github.com/eyelash/tutorials/blob/master/drm-gbm.c

https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglIntro.xhtml

————————————————

/*******************************************************************************************
 * Compile CMD: gcc -g -o SimpleGBMDemo SimpleGBMDemo.c -ldrm -lgbm -lEGL -lGL -I/usr/include/libdrm
 * 
 * Dependency: mesa-dev, libdrm-dev, libgbm-dev
 * 
 * On-Screen  rendering: rendering into a window.
 * Off-Screen rendering: off-screen interface is used for rendering into user-allocated memory 
 *                       without any sort of window system or operating system dependencies.
 * 
 * Generic Buffer Management (GBM) is an API that provides a mechanism for allocating buffers for 
 * graphics rendering tied to Mesa. GBM is intended to be used as a native platform for EGL on drm 
 * or openwfd. The handle it creates can be used to initialize EGL and to create render target buffers.
 * 
 * EGL is a application API.
 * DRI is 'Direct Rendering Infrastructure'. It's part of Linux's graphic acceleration driver stack.
 * Hardward <--> Linux Kernel DRM driver <--DRI Protocol--> userspace Driver <--APP APIs--> Applications.
 * 
 * WARNING: Nvidia proprietary driver doesn't support DRM/DRI. So this demo not work on Nvidia GPU.
 * 
 * *****************************************************************************************/
 
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <gbm.h>
#include <EGL/egl.h>
#include <GL/gl.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
 
#define EXIT(msg) { fputs (msg, stderr); exit (EXIT_FAILURE); }
 
void assertEGLError(const char *msg)
{
    EGLint error = eglGetError();
    if(error != EGL_SUCCESS) {
        printf("EGL error 0x%x at %s\n", error, msg);
    }
}
 
static int g_device_fd;
 
static uint32_t connector_id;
static drmModeModeInfo mode_info;
static drmModeCrtc *crtc;
static struct gbm_device *gbm_device;
 
static EGLDisplay display;
static EGLContext context;
static struct gbm_surface *gbm_surface;
static EGLSurface egl_surface;
 
static struct gbm_bo *previous_bo = NULL;
static uint32_t previous_fb;
 
static void swap_buffers () {
	eglSwapBuffers (display, egl_surface);
	struct gbm_bo *bo = gbm_surface_lock_front_buffer (gbm_surface);
	uint32_t handle = gbm_bo_get_handle (bo).u32;
	uint32_t pitch = gbm_bo_get_stride (bo);
	uint32_t fb;
	drmModeAddFB (g_device_fd, mode_info.hdisplay, mode_info.vdisplay, 24, 32, pitch, handle, &fb);
	drmModeSetCrtc (g_device_fd, crtc->crtc_id, fb, 0, 0, &connector_id, 1, &mode_info);
	
	if (previous_bo) {
		drmModeRmFB (g_device_fd, previous_fb);
		gbm_surface_release_buffer (gbm_surface, previous_bo);
	}
	previous_bo = bo;
	previous_fb = fb;
}
 
static void draw (float progress) {
	glClearColor (1.0f-progress, progress, 0.0, 1.0);
	glClear (GL_COLOR_BUFFER_BIT);
	swap_buffers ();
}
 
static void clean_up () {
	// set the previous crtc
	drmModeSetCrtc (g_device_fd, crtc->crtc_id, crtc->buffer_id, crtc->x, crtc->y, &connector_id, 1, &crtc->mode);
	drmModeFreeCrtc (crtc);
	
	if (previous_bo) {
		drmModeRmFB (g_device_fd, previous_fb);
		gbm_surface_release_buffer (gbm_surface, previous_bo);
	}
	
	eglDestroySurface (display, egl_surface);
	gbm_surface_destroy (gbm_surface);
	eglDestroyContext (display, context);
	eglTerminate (display);
	gbm_device_destroy (gbm_device);
}
 
/* On-Screen Rendering */
void print_default_display_info()
{
	int major, minor;
    EGLDisplay defaultDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    assertEGLError("eglGetDisplay(EGL_DEFAULT_DISPLAY)");
    eglInitialize (defaultDisplay, &major, &minor);
    assertEGLError("eglInitialize");
    printf("*************EGL %d.%d when eglGetDisplay(EGL_DEFAULT_DISPLAY)********t*****\n", major, minor);
	printf(" EGL_CLIENT_APIS: %s\n", eglQueryString(defaultDisplay, EGL_CLIENT_APIS));
	printf(" EGL_VENDOR: %s\n", eglQueryString(defaultDisplay,  EGL_VENDOR));
	printf(" EGL_VERSION: %s\n", eglQueryString(defaultDisplay,  EGL_VERSION));
	printf(" EGL_EXTENSIONS: %s\n", eglQueryString(defaultDisplay,  EGL_EXTENSIONS));
    eglTerminate (defaultDisplay);
    assertEGLError("eglTerminate");
    printf("\n");
}
 
static void find_display_configuration () 
{
	drmModeRes *resources = drmModeGetResources (g_device_fd);
	/* It will crash if GPU driver doesn't support DRM/DRI. */
	assert(!(resources == NULL));
	
	// find a connector
	drmModeConnector *connector = NULL;
	for (int i=0; i < resources->count_connectors; i++) {
		drmModeConnector *temp_connector = drmModeGetConnector (g_device_fd, resources->connectors[i]);
		// pick the first connected connector
		if (temp_connector->connection == DRM_MODE_CONNECTED) {
			connector = temp_connector;
			break;
		}
		drmModeFreeConnector (connector);
	}
	if (!connector) EXIT ("no connector found\n");
	
	// save the connector_id
	connector_id = connector->connector_id;
	// save the first mode
	mode_info = connector->modes[0];
	printf ("resolution: %ix%i\n", mode_info.hdisplay, mode_info.vdisplay);
	
	// find an encoder
	drmModeEncoder *encoder = NULL;
	if (connector->encoder_id) {
		encoder = drmModeGetEncoder (g_device_fd, connector->encoder_id);
	}
	
	if (!encoder) EXIT ("no encoder found\n");
	// find a CRTC
	if (encoder->crtc_id) {
		crtc = drmModeGetCrtc (g_device_fd, encoder->crtc_id);
	}
	
	// clean up
	drmModeFreeEncoder (encoder);
	drmModeFreeConnector (connector);
	drmModeFreeResources (resources);
}
 
int main () 
{
	/* For comparing with Off-Screen Rendering Info */
	print_default_display_info();
	
	/* Off-Screen Rendering */
	printf("************/dev/dri/card0************\n");
	g_device_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK);
	
	find_display_configuration ();
	
	/* Create EGL Context using GBM */
	int major, minor;
	gbm_device = gbm_create_device (g_device_fd);
	display = eglGetDisplay (gbm_device);
	eglInitialize (display, &major, &minor);
	
    printf("*************EGL %d.%d when eglGetDisplay(GBM)*************\n", major, minor);
	printf(" EGL_CLIENT_APIS: %s\n", eglQueryString(display, EGL_CLIENT_APIS));
	printf(" EGL_VENDOR: %s\n", eglQueryString(display,  EGL_VENDOR));
	printf(" EGL_VERSION: %s\n", eglQueryString(display,  EGL_VERSION));
	printf(" EGL_EXTENSIONS: %s\n", eglQueryString(display,  EGL_EXTENSIONS));
	
	eglBindAPI (EGL_OPENGL_API);
	EGLint attributes[] = {
		EGL_RED_SIZE, 8,
		EGL_GREEN_SIZE, 8,
		EGL_BLUE_SIZE, 8,
		EGL_NONE };
	EGLConfig config;
	EGLint num_config;
	eglChooseConfig (display, attributes, &config, 1, &num_config);
	context = eglCreateContext (display, config, EGL_NO_CONTEXT, NULL);
	
	// create the GBM and EGL surface
	gbm_surface = gbm_surface_create (gbm_device, mode_info.hdisplay, mode_info.vdisplay, 
									GBM_BO_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT|GBM_BO_USE_RENDERING);
	egl_surface = eglCreateWindowSurface (display, config, gbm_surface, NULL);
	eglMakeCurrent (display, egl_surface, egl_surface, context);
	
	/* Rendering using OpenGL... */
	int i;
	for (i = 0; i < 600; i++)
		draw (i / 600.0f);
	
	clean_up ();
	close (g_device_fd);
	
	return 0;
}
           

http://virtuousgeek.org/blog/index.php/jbarnes/2011/10/31/writing_stanalone_programs_with_egl_and_

Both on dri-devel and at the most recent Kernel Summit, the idea of a KMS based console program came up yet again. So in the interest of facilitating the lazyweb to write one, I thought I’d provide a review of what it takes to write a simple KMS program that ties in with GL, just in case anyone wants to port the VTE widget and give me my VTs on a cube. :) The ideal situation would be one where a distro could set CONFIG_VT=n and have userspace provide all console services, including support for the various types of input and output that exist in the wild today. The only thing that ought to be left to the kernel is spitting out panic information somehow. That means having a very simple text drawing layer remain, and a way for the kernel to flip to the latest kernel console buffer (today these functions are provided by fbcon and the DRM KMS layer, but if a userspace console service existed, we could likely clean them up quite a bit, and remove the need for the full VT and fb layers to be compiled in).

Initialization

One of the first things any DRM based program must do is open the DRI device:

int fd;
fd = open("/dev/dri/card0", O_RDWR);
           

You’ll need to save that fd for use with any later DRM call, including the ones we’ll need to get display resources. The path to the device may vary by distribution or configuration (e.g. with a multi-card setup), so take care to report sensible error strings if things go wrong.

The next step is to initialize the GBM library. GBM (for Generic Buffer Management) is a simple library to abstract buffers that will be used with Mesa. We’ll use the handle it creates to initialize EGL and to create render target buffers.

struct gbm_device *gbm;
gbm = gbm_create_device(fd);
           

Then we get EGL into shape:

EGLDisplay dpy;
dpy = eglGetDisplay(gbm);
           

We’ll need to hang on to both the GBM device handle and the EGL display handle as well, since most of the calls we make will need them. What follows is more EGL init.

EGLint major, minor;
const char *ver, *extensions;
eglInitialize(dpy, &major, &minor);
ver = eglQueryString(dpy, EGL_VERSION);
extensions = eglQueryString(dpy, EGL_EXTENSIONS);
           

Now that we have the extensions string, we need to check and make sure the EGL_KHR_surfaceless_opengl extension is available, since it’s what we’ll use to avoid the need for a full windowing system, which would normally provide us with pixmaps and windows to turn into EGL surfaces.

if (!strstr(extensions, "EGL_KHR_surfaceless_opengl")) {
    fprintf(stderr, "no surfaceless support, cannot initialize\n");
    exit(-1);
}
           

And now we’re finally ready to see what outputs are available for us to use. There are several good references for KMS output handling: the DDX drivers for radeon, nouveau, and intel, and testdisplay.c in the intel-gpu-tools package. (The below is taken from eglkms.c in the mesa-demos package.)

drmModeRes *resources;
    drmModeConnector *connector;
    drmModeEncoder *encoder;
    int i;
 
    /* Find the first available connector with modes */
 
    resources = drmModeGetResources(fd);
    if (!resources) {
        fprintf(stderr, "drmModeGetResources failed\n");
        return;
    }
 
    for (i = 0; i < resources->count_connectors; i++) {
        connector = drmModeGetConnector(fd, resources->connectors[i]);
        if (connector == NULL)
            continue;
 
        if (connector->connection == DRM_MODE_CONNECTED &&
            connector->count_modes > 0)
            break;
 
        drmModeFreeConnector(connector);
    }
 
    if (i == resources->count_connectors) {
        fprintf(stderr, "No currently active connector found.\n");
        return;
    }
 
    for (i = 0; i < resources->count_encoders; i++) {
        encoder = drmModeGetEncoder(fd, resources->encoders[i]);
 
        if (encoder == NULL)
  	 continue;
 
       if (encoder->encoder_id == connector->encoder_id)
 	 break;
 
       drmModeFreeEncoder(encoder);
    }
           

At this point we have connector, encoder, and mode structures we’ll use to put something on the display later.

Drawing & displaying

We’ve initialized EGL, but we need to bind an API and create a context. In this case, we’ll use OpenGL.

EGLContext ctx;

    eglBindAPI(EGL_OPENGL_API);
    ctx = eglCreateContext(dpy, NULL, EGL_NO_CONTEXT, NULL);

    eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx);
           

Now we have a fully functional KMS and EGL stack and it’s time for a little bit of glue magic. We need to create some buffers using GBM and bind them into EGL images that we can use as renderbuffers.

EGLImageKHR image;
    struct gbm_bo *bo;
    GLuint fb, color_rb, depth_rb;
    uint32_t handle, stride;

    /* Create a full screen buffer for use as a render target */
    bo = gbm_bo_create(gbm, mode.hdisplay, mode.vdisplay, GBM_BO_FORMAT_XRGB888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);

    glGenFramebuffers(1, &fb);
    glBindFramebuffer(GL_FRAMEBUFFER_EXT, fb);

    handle = gbm_bo_get_handle(bo).u32;
    stride = gbm_bo_get_pitch(bo);

    image = eglCreateImageKHR(dpy, NULL, EGL_NATIVE_PIXMAP_KHR, bo, NULL);

    /* Set up render buffer... */
    glGenRenderbuffers(1, &color_rb);
    glBindRenderbuffer(GL_RENDERBUFFER_EXT, color_rb);
    glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, image);
    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, color_rb);

    /* and depth buffer */
    glGenRenderbuffers(1, &depth_rb);
    glBindRenderbuffer(GL_RENDERBUFFER_EXT, depth_rb);
    glRenderbufferStorage(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, mode.hdisplay, mode.vdisplay);
    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth_rb);

    if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)) != GL_FRAMEBUFFER_COMPLETE) {
        fprintf(stderr, "something bad happened\n");
        exit(-1);
    }
           

At this point, you’re all ready to start rendering using standard GL calls. Note that since we don’t have a window system or a window bound, we won’t be using eglSwapBuffers() to display the final renderbuffer. But we still need to make sure the rendering is finished before we display it, so we can use glFinish() or some out of band synchronization mechanism (TBD) to ensure our rendering is complete before we display the new buffer.

Assuming we have something presentable, we can now try to display it:

uint32_t crtc;

    /* Create a KMS framebuffer handle to set a mode with */
    drmModeAddFB(fd, mode.hdisplay, mode.vdisplay, 24, 32, stride, handle, &fb_id);

    drmModeSetCrtc(fd, encoder->crtc_id, fb_id, 0, 0, connector->connector_id, 1, mode);
           

In this example, we’re assuming a 24 bit depth buffer with 32 bits per pixel. That can of course vary, but what’s supported will depend on the chipset (24/32 is fairly common however). The drmModeSetCrtc call will also take care to simply flip to the fb in question if the mode timings are already programmed and are the same as the mode timings passed into the call. This allows for a simple page flipping scheme that simply creates two fbs using AddFB (pointing at different buffers) and flips between them with SetCrtc calls in a loop. Alternately, one can simply draw to the buffer that’s being displayed, if the visibility of partial rendering isn’t a problem.

DRM master privs and authentication

I forgot to mention that any program like this must drop DRM master privileges if another app, like X, wants to start up and use the DRM layer. The calls for this are drmSetMaster(int fd) and drmDropMaster(int fd). If no other DRM master is present, the SetMaster call should succeed. And before switching away due to a dbus call or whatever, any console program using the DRM layer should call drmDropMaster to allow the next client to get access to the DRM. Alternately, the console program could provide a display management API and make X a slave of that interface. That would also mean providing DRM authentication for other apps wanting to do direct rendering (typically master privs are required for display management, and simple auth privs are required to do command submission). Wayland is a good example of a display manager that provides this kind of functionality.

And that’s all there is to it, and should be enough to get someone started on kmscon… :) If you have questions or comments, feel free to bring them up at [email protected], in the comments section here, or via email to me.

• Trackback (0)