blob: d9ce3cf3d38e32e354f103c98dd91bb672f3de47 [file] [log] [blame]
/*
* Copyright 2014 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#define EGL_EGLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES
#include <gbm.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <drm_fourcc.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
const char * get_gl_error()
{
switch (glGetError()) {
case GL_NO_ERROR:
return "GL_NO_ERROR";
case GL_INVALID_ENUM:
return "GL_INVALID_ENUM";
case GL_INVALID_VALUE:
return "GL_INVALID_VALUE";
case GL_INVALID_OPERATION:
return "GL_INVALID_OPERATION";
case GL_INVALID_FRAMEBUFFER_OPERATION:
return "GL_INVALID_FRAMEBUFFER_OPERATION";
case GL_OUT_OF_MEMORY:
return "GL_OUT_OF_MEMORY";
default:
return "Unknown error";
}
}
const char * get_egl_error()
{
switch (eglGetError()) {
case EGL_SUCCESS:
return "EGL_SUCCESS";
case EGL_NOT_INITIALIZED:
return "EGL_NOT_INITIALIZED";
case EGL_BAD_ACCESS:
return "EGL_BAD_ACCESS";
case EGL_BAD_ALLOC:
return "EGL_BAD_ALLOC";
case EGL_BAD_ATTRIBUTE:
return "EGL_BAD_ATTRIBUTE";
case EGL_BAD_CONTEXT:
return "EGL_BAD_CONTEXT";
case EGL_BAD_CONFIG:
return "EGL_BAD_CONFIG";
case EGL_BAD_CURRENT_SURFACE:
return "EGL_BAD_CURRENT_SURFACE";
case EGL_BAD_DISPLAY:
return "EGL_BAD_DISPLAY";
case EGL_BAD_SURFACE:
return "EGL_BAD_SURFACE";
case EGL_BAD_MATCH:
return "EGL_BAD_MATCH";
case EGL_BAD_PARAMETER:
return "EGL_BAD_PARAMETER";
case EGL_BAD_NATIVE_PIXMAP:
return "EGL_BAD_NATIVE_PIXMAP";
case EGL_BAD_NATIVE_WINDOW:
return "EGL_BAD_NATIVE_WINDOW";
case EGL_CONTEXT_LOST:
return "EGL_CONTEXT_LOST";
default:
return "Unknown error";
}
}
#define BUFFERS 2
struct context {
int card_fd;
struct gbm_device * gbm_dev;
EGLDisplay egl_display;
EGLContext egl_ctx;
drmModeConnector * connector;
drmModeEncoder * encoder;
drmModeModeInfo * mode;
struct gbm_bo * gbm_buffer[BUFFERS];
EGLImageKHR egl_image[BUFFERS];
uint32_t drm_fb_id[BUFFERS];
unsigned gl_fb[BUFFERS];
unsigned gl_rb[BUFFERS];
};
bool setup_drm(struct context * ctx)
{
int fd = ctx->card_fd;
drmModeRes *resources = NULL;
drmModeConnector *connector = NULL;
drmModeEncoder *encoder = NULL;
int i, j;
resources = drmModeGetResources(fd);
if (!resources) {
fprintf(stderr, "drmModeGetResources failed\n");
return 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 false;
}
for (i = 0; i < resources->count_encoders; i++) {
encoder = drmModeGetEncoder(fd, resources->encoders[i]);
if (encoder == NULL)
continue;
for (j = 0; j < connector->count_encoders; j++) {
if (encoder->encoder_id == connector->encoders[j])
break;
}
if (j == connector->count_encoders) {
drmModeFreeEncoder(encoder);
continue;
}
break;
}
if (i == resources->count_encoders) {
fprintf(stderr, "no supported encoder found\n");
return false;
}
for (i = 0; i < resources->count_crtcs; i++) {
if (encoder->possible_crtcs & (1 << i)) {
encoder->crtc_id = resources->crtcs[i];
break;
}
}
if (i == resources->count_crtcs) {
fprintf(stderr, "no possible crtc found\n");
return false;
}
ctx->connector = connector;
ctx->encoder = encoder;
ctx->mode = &connector->modes[0];
return true;
}
float f(int i) {
int a = i % 40;
int b = (i / 40) % 6;
switch (b) {
case 0:
case 1:
return 0.0f;
case 3:
case 4:
return 1.0f;
case 2:
return (a / 40.0f);
case 5:
return 1.0f - (a / 40.0f);
default:
return 0.0f;
}
}
static void page_flip_handler(int fd, unsigned int frame, unsigned int sec,
unsigned int usec, void *data)
{
int *waiting_for_flip = data;
*waiting_for_flip = 0;
}
void draw(struct context * ctx)
{
int i;
const GLchar *vertexShaderStr =
"attribute vec4 vPosition;\n"
"attribute vec4 vColor;\n"
"varying vec4 vFillColor;\n"
"void main() {\n"
" gl_Position = vPosition;\n"
" vFillColor = vColor;\n"
"}\n";
const GLchar *fragmentShaderStr =
"precision mediump float;\n"
"varying vec4 vFillColor;\n"
"void main() {\n"
" gl_FragColor = vFillColor;\n"
"}\n";
GLint vertexShader, fragmentShader, program, status;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
if (!vertexShader) {
fprintf(stderr, "Failed to create vertex shader. Error=0x%x\n", glGetError());
return;
}
glShaderSource(vertexShader, 1, &vertexShaderStr, NULL);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status);
if (!status) {
fprintf(stderr, "Failed to compile vertex shader. Error=0x%x\n", glGetError());
return;
}
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
if (!fragmentShader) {
fprintf(stderr, "Failed to create fragment shader. Error=0x%x\n", glGetError());
return;
}
glShaderSource(fragmentShader, 1, &fragmentShaderStr, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status);
if (!status) {
fprintf(stderr, "Failed to compile fragment shader. Error=0x%x\n", glGetError());
return;
}
program = glCreateProgram();
if (!program) {
fprintf(stderr, "Failed to create program.\n");
return;
}
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glBindAttribLocation(program, 0, "vPosition");
glBindAttribLocation(program, 1, "vColor");
glLinkProgram(program);
glGetShaderiv(program, GL_LINK_STATUS, &status);
if (!status) {
fprintf(stderr, "Failed to link program.\n");
return;
}
int fb_idx = 1;
for (i = 0; i <= 500; i++) {
int waiting_for_flip = 1;
GLfloat verts[] = {
0.0f, -0.5f, 0.0f,
-0.5f, 0.5f, 0.0f,
0.5f, 0.5f, 0.0f
};
GLfloat colors[] = {
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f
};
glBindFramebuffer(GL_FRAMEBUFFER, ctx->gl_fb[fb_idx]);
glViewport(0, 0,(GLint) ctx->mode->hdisplay,
(GLint) ctx->mode->vdisplay);
glClearColor(f(i), f(i + 80), f(i + 160), 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(program);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, verts);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, colors);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glDrawArrays(GL_TRIANGLES, 0, 3);
usleep(1e6 / 120); /* 120 Hz */
glFinish();
drmModePageFlip(ctx->card_fd, ctx->encoder->crtc_id,
ctx->drm_fb_id[fb_idx],
DRM_MODE_PAGE_FLIP_EVENT,
&waiting_for_flip);
while (waiting_for_flip) {
drmEventContext evctx = {
.version = DRM_EVENT_CONTEXT_VERSION,
.page_flip_handler = page_flip_handler,
};
fd_set fds;
FD_ZERO(&fds);
FD_SET(0, &fds);
FD_SET(ctx->card_fd, &fds);
int ret = select(ctx->card_fd + 1, &fds, NULL, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "select err: %s\n", strerror(errno));
return;
} else if (ret == 0) {
fprintf(stderr, "select timeout\n");
return;
} else if (FD_ISSET(0, &fds)) {
fprintf(stderr, "user interrupted\n");
return;
}
drmHandleEvent(ctx->card_fd, &evctx);
}
fb_idx = fb_idx ^ 1;
}
glDeleteProgram(program);
}
int main(int argc, char ** argv)
{
int ret = 0;
struct context ctx;
EGLint egl_major, egl_minor;
const char * extensions;
uint32_t bo_handle;
uint32_t bo_stride;
int drm_prime_fd;
EGLint num_configs;
EGLConfig egl_config;
size_t i;
char* card_path = "/dev/dri/card1";
const EGLint config_attribs[] = {
EGL_RED_SIZE, 1,
EGL_GREEN_SIZE, 1,
EGL_BLUE_SIZE, 1,
EGL_DEPTH_SIZE, 1,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_NONE
};
const EGLint context_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
if (argc >= 2)
card_path = argv[1];
ctx.card_fd = open(card_path, O_RDWR);
if (ctx.card_fd < 0) {
fprintf(stderr, "failed to open %s\n", card_path);
ret = 1;
goto fail;
}
ctx.gbm_dev = gbm_create_device(ctx.card_fd);
if (!ctx.gbm_dev) {
fprintf(stderr, "failed to create gbm device\n");
ret = 1;
goto close_card;
}
ctx.egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (ctx.egl_display == EGL_NO_DISPLAY) {
fprintf(stderr, "failed to get egl display\n");
ret = 1;
goto destroy_device;
}
if (!eglInitialize(ctx.egl_display, &egl_major, &egl_minor)) {
fprintf(stderr, "failed to initialize egl: %s\n",
get_egl_error());
ret = 1;
goto terminate_display;
}
fprintf(stderr, "EGL %d.%d\n", egl_major, egl_minor);
fprintf(stderr, "EGL %s\n",
eglQueryString(ctx.egl_display, EGL_VERSION));
extensions = eglQueryString(ctx.egl_display, EGL_EXTENSIONS);
fprintf(stderr, "EGL Extensions: %s\n", extensions);
if (!setup_drm(&ctx)) {
fprintf(stderr, "failed to setup drm resources\n");
ret = 1;
goto terminate_display;
}
fprintf(stderr, "display size: %dx%d\n",
ctx.mode->hdisplay, ctx.mode->vdisplay);
if (!eglChooseConfig(ctx.egl_display, config_attribs, NULL, 0,
&num_configs)) {
fprintf(stderr, "eglChooseConfig() failed with error: %x\n", eglGetError());
goto free_drm;
}
if (!eglChooseConfig(ctx.egl_display, config_attribs, &egl_config, 1,
&num_configs)) {
fprintf(stderr, "eglChooseConfig() failed with error: %x\n", eglGetError());
goto free_drm;
}
if (!eglBindAPI(EGL_OPENGL_ES_API)) {
fprintf(stderr, "failed to bind OpenGL ES: %s\n",
get_egl_error());
ret = 1;
goto free_drm;
}
ctx.egl_ctx = eglCreateContext(ctx.egl_display,
NULL /* No framebuffer */,
EGL_NO_CONTEXT /* No shared context */,
context_attribs);
if (ctx.egl_ctx == EGL_NO_CONTEXT) {
fprintf(stderr, "failed to create OpenGL ES Context: %s\n",
get_egl_error());
ret = 1;
goto free_drm;
}
if (!eglMakeCurrent(ctx.egl_display,
EGL_NO_SURFACE /* No default draw surface */,
EGL_NO_SURFACE /* No default draw read */,
ctx.egl_ctx)) {
fprintf(stderr, "failed to make the OpenGL ES Context current: %s\n",
get_egl_error());
ret = 1;
goto destroy_context;
}
fprintf(stderr, "GL extensions: %s\n", glGetString(GL_EXTENSIONS));
glGenFramebuffers(BUFFERS, ctx.gl_fb);
glGenRenderbuffers(BUFFERS, ctx.gl_rb);
for (i = 0; i < BUFFERS; ++i) {
ctx.gbm_buffer[i] = gbm_bo_create(ctx.gbm_dev,
ctx.mode->hdisplay, ctx.mode->vdisplay, GBM_BO_FORMAT_XRGB8888,
GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
if (!ctx.gbm_buffer[i]) {
fprintf(stderr, "failed to create buffer object\n");
ret = 1;
goto free_buffers;
}
bo_handle = gbm_bo_get_handle(ctx.gbm_buffer[i]).u32;
bo_stride = gbm_bo_get_stride(ctx.gbm_buffer[i]);
if (drmPrimeHandleToFD(ctx.card_fd, bo_handle, DRM_CLOEXEC,
&drm_prime_fd)) {
fprintf(stderr, "failed to turn handle into fd\n");
ret = 1;
goto free_buffers;
}
const EGLint khr_image_attrs[] = {
EGL_WIDTH, ctx.mode->hdisplay,
EGL_HEIGHT, ctx.mode->vdisplay,
EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_XRGB8888,
EGL_DMA_BUF_PLANE0_FD_EXT, drm_prime_fd,
EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
EGL_DMA_BUF_PLANE0_PITCH_EXT, bo_stride,
EGL_NONE
};
ctx.egl_image[i] = eglCreateImageKHR(ctx.egl_display, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, NULL, khr_image_attrs);
if (ctx.egl_image[i] == EGL_NO_IMAGE_KHR) {
fprintf(stderr, "failed to create egl image: %s\n",
get_egl_error());
ret = 1;
goto free_buffers;
}
glBindRenderbuffer(GL_RENDERBUFFER, ctx.gl_rb[i]);
glBindFramebuffer(GL_FRAMEBUFFER, ctx.gl_fb[i]);
glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, ctx.egl_image[i]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, ctx.gl_rb[i]);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) !=
GL_FRAMEBUFFER_COMPLETE) {
fprintf(stderr, "failed to create framebuffer: %s\n", get_gl_error());
ret = 1;
goto free_buffers;
}
ret = drmModeAddFB(ctx.card_fd, ctx.mode->hdisplay, ctx.mode->vdisplay,
24, 32, bo_stride, bo_handle, &ctx.drm_fb_id[i]);
if (ret) {
fprintf(stderr, "failed to add fb\n");
ret = 1;
goto free_buffers;
}
}
if (drmModeSetCrtc(ctx.card_fd, ctx.encoder->crtc_id, ctx.drm_fb_id[0],
0, 0, &ctx.connector->connector_id, 1, ctx.mode)) {
fprintf(stderr, "failed to set CRTC\n");
ret = 1;
goto free_buffers;
}
draw(&ctx);
free_buffers:
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
for (i = 0; i < BUFFERS; ++i) {
if (ctx.drm_fb_id[i])
drmModeRmFB(ctx.card_fd, ctx.drm_fb_id[i]);
if (ctx.egl_image[i])
eglDestroyImageKHR(ctx.egl_display, ctx.egl_image[i]);
if (ctx.gl_fb[i])
glDeleteFramebuffers(1, &ctx.gl_fb[i]);
if (ctx.gl_rb[i])
glDeleteRenderbuffers(1, &ctx.gl_rb[i]);
if (ctx.gbm_buffer[i])
gbm_bo_destroy(ctx.gbm_buffer[i]);
}
destroy_context:
eglDestroyContext(ctx.egl_display, ctx.egl_ctx);
free_drm:
drmModeFreeConnector(ctx.connector);
drmModeFreeEncoder(ctx.encoder);
terminate_display:
eglTerminate(ctx.egl_display);
destroy_device:
gbm_device_destroy(ctx.gbm_dev);
close_card:
close(ctx.card_fd);
fail:
return ret;
}