blob: 44557effe231c22cdeb950213c1fa16aba1346ae [file] [log] [blame]
/*
* Copyright © 2011 Kristian Høgsberg
* Copyright © 2011 Benjamin Franzke
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that copyright
* notice and this permission notice appear in supporting documentation, and
* that the name of the copyright holders not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no representations
* about the suitability of this software for any purpose. It is provided "as
* is" without express or implied warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THIS SOFTWARE.
*/
#define _GNU_SOURCE /* Needed for O_CLOEXEC */
#include <stdio.h>
#include <stdlib.h>
#define EGL_EGLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES
#include <sys/types.h>
#include <sys/stat.h>
#include <gbm.h>
#include <GL/gl.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <drm.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
struct kms {
drmModeConnector *connector;
drmModeEncoder *encoder;
drmModeModeInfo mode;
};
static EGLBoolean
setup_kms(int fd, struct kms *kms)
{
drmModeRes *resources;
drmModeConnector *connector;
drmModeEncoder *encoder;
int i;
resources = drmModeGetResources(fd);
if (!resources) {
fprintf(stderr, "drmModeGetResources failed\n");
return EGL_FALSE;
}
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 EGL_FALSE;
}
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);
}
kms->connector = connector;
kms->encoder = encoder;
kms->mode = connector->modes[0];
return EGL_TRUE;
}
static void
render_to_texture(int width, int height)
{
GLfloat rsize = 100;
GLfloat x = (width - rsize) / 2.0 ;
GLfloat y = (height - rsize) / 2.0;
glViewport(0, 0, (GLint) width, (GLint) height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, 0, height, 1.0, -1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClearColor(0.0f, 1.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0f, 0.0f, 0.0f);
glRectf(x, y, x + rsize, y + rsize);
glFinish();
}
static void
render_from_texture(int width, int height)
{
GLfloat x = width / 2.0;
GLfloat y = height / 2.0;
GLfloat Vertices[] =
{
0.0f, y, 0.0f,
0.0f, 0.0f, 0.0f,
x, 0.0f, 0.0f,
x, y, 0.0f
};
GLfloat TexCoords[] =
{
0.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
glViewport(0, 0, (GLint) width, (GLint) height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, 0, height, 1.0, -1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClear(GL_COLOR_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, Vertices);
glTexCoordPointer(2, GL_FLOAT, 0, TexCoords);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glFinish();
}
static const char device_name[] = "/dev/dri/card0";
static void
page_flip_handler(int fd, unsigned int frame,
unsigned int sec, unsigned int usec, void *data)
{
;
}
void quit_handler(int signum)
{
printf("Quitting!\n");
}
int main(int argc, char *argv[])
{
EGLDisplay dpy;
EGLContext ctx;
EGLImageKHR image, out_image;
EGLint major, minor;
const char *extensions;
GLuint fb, texture, out_texture;
uint32_t handle, stride, fb_id, name;
struct kms kms;
int ret, fd;
struct gbm_device *gbm;
struct gbm_bo *bo;
drmModeCrtcPtr saved_crtc;
time_t start, end;
int is_producer = (argc == 1);
signal (SIGINT, quit_handler);
fd = open(device_name, O_RDWR | O_CLOEXEC);
if (fd < 0) {
/* Probably permissions error */
fprintf(stderr, "couldn't open %s, skipping\n", device_name);
return -1;
}
gbm = gbm_create_device(fd);
if (gbm == NULL) {
fprintf(stderr, "couldn't create gbm device\n");
ret = -1;
goto close_fd;
}
dpy = eglGetDisplay(gbm);
if (dpy == EGL_NO_DISPLAY) {
fprintf(stderr, "eglGetDisplay() failed\n");
ret = -1;
goto destroy_gbm_device;
}
if (!eglInitialize(dpy, &major, &minor)) {
printf("eglInitialize() failed\n");
ret = -1;
goto egl_terminate;
}
extensions = eglQueryString(dpy, EGL_EXTENSIONS);
if (!strstr(extensions, "EGL_KHR_surfaceless_opengl")) {
printf("No support for EGL_KHR_surfaceless_opengl\n");
ret = -1;
goto egl_terminate;
}
if (!setup_kms(fd, &kms)) {
ret = -1;
goto egl_terminate;
}
eglBindAPI(EGL_OPENGL_API);
ctx = eglCreateContext(dpy, NULL, EGL_NO_CONTEXT, NULL);
if (ctx == NULL) {
fprintf(stderr, "failed to create context\n");
ret = -1;
goto egl_terminate;
}
if (!eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx)) {
fprintf(stderr, "failed to make context current\n");
ret = -1;
goto destroy_context;
}
glGenFramebuffers(1, &fb);
glBindFramebuffer(GL_FRAMEBUFFER, fb);
glGenTextures(1, &out_texture);
glBindTexture(GL_TEXTURE_2D, out_texture);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
bo = gbm_bo_create(gbm, kms.mode.hdisplay, kms.mode.vdisplay,
GBM_BO_FORMAT_XRGB8888,
GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
if (bo == NULL) {
fprintf(stderr, "failed to create gbm bo\n");
ret = -1;
goto unmake_current;
}
out_image = eglCreateImageKHR(dpy, NULL, EGL_NATIVE_PIXMAP_KHR, bo, NULL);
if (out_image == EGL_NO_IMAGE_KHR) {
fprintf(stderr, "failed to create egl image\n");
ret = -1;
goto destroy_gbm_bo;
}
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, out_image);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
out_texture, 0);
if ((ret = glCheckFramebufferStatus(GL_FRAMEBUFFER)) !=
GL_FRAMEBUFFER_COMPLETE) {
fprintf(stderr, "framebuffer not complete: %x\n", ret);
ret = 1;
goto destroy_gbm_bo;
}
if (is_producer) {
char name_str[sizeof(long) + 1] = { 0 };
char stride_str[sizeof(long) + 1] = { 0 };
eglExportDRMImageMESA(dpy, out_image, &name, NULL, &stride);
snprintf(name_str, sizeof(name_str), "%d", (int)name);
snprintf(stride_str, sizeof(stride_str), "%d", (int)stride);
render_to_texture(kms.mode.hdisplay, kms.mode.vdisplay);
drmDropMaster(fd);
if (!fork())
execl(argv[0], argv[0], name_str, stride_str, NULL);
else
wait(NULL);
goto destroy_gbm_bo;
}
sscanf(argv[1], "%d", &name);
sscanf(argv[2], "%d", &stride);
EGLint attribs[] = {
EGL_WIDTH, kms.mode.hdisplay,
EGL_HEIGHT, kms.mode.vdisplay,
EGL_DRM_BUFFER_STRIDE_MESA, stride / 4,
EGL_DRM_BUFFER_FORMAT_MESA, EGL_DRM_BUFFER_FORMAT_ARGB32_MESA,
EGL_NONE
};
image = eglCreateImageKHR(dpy,
EGL_NO_CONTEXT,
EGL_DRM_BUFFER_MESA,
(void *)(intptr_t)(name),
attribs);
if (image == EGL_NO_IMAGE_KHR) {
fprintf(stderr, "Failed to import image\n");
return;
}
glEnable(GL_TEXTURE_2D);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
eglExportDRMImageMESA(dpy, out_image, NULL, &handle, &stride);
render_from_texture(kms.mode.hdisplay, kms.mode.vdisplay);
ret = drmModeAddFB(fd,
kms.mode.hdisplay, kms.mode.vdisplay,
24, 32, stride, handle, &fb_id);
if (ret) {
fprintf(stderr, "failed to create fb\n");
goto destroy_gbm_bo;
}
saved_crtc = drmModeGetCrtc(fd, kms.encoder->crtc_id);
if (saved_crtc == NULL)
goto rm_fb;
drmEventContext evctx;
fd_set rfds;
ret = drmModePageFlip(fd, kms.encoder->crtc_id,
fb_id, DRM_MODE_PAGE_FLIP_EVENT, 0);
if (ret) {
fprintf(stderr, "failed to page flip: %m\n");
goto free_saved_crtc;
}
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
while (select(fd + 1, &rfds, NULL, NULL, NULL) == -1)
NULL;
memset(&evctx, 0, sizeof evctx);
evctx.version = DRM_EVENT_CONTEXT_VERSION;
evctx.page_flip_handler = page_flip_handler;
drmHandleEvent(fd, &evctx);
sleep(60);
ret = drmModeSetCrtc(fd, saved_crtc->crtc_id, saved_crtc->buffer_id,
saved_crtc->x, saved_crtc->y,
&kms.connector->connector_id, 1, &saved_crtc->mode);
if (ret) {
fprintf(stderr, "failed to restore crtc: %m\n");
}
free_saved_crtc:
drmModeFreeCrtc(saved_crtc);
rm_fb:
drmModeRmFB(fd, fb_id);
eglDestroyImageKHR(dpy, image);
destroy_gbm_bo:
gbm_bo_destroy(bo);
unmake_current:
eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
destroy_context:
eglDestroyContext(dpy, ctx);
egl_terminate:
eglTerminate(dpy);
destroy_gbm_device:
gbm_device_destroy(gbm);
close_fd:
close(fd);
return ret;
}