blob: 55138495f91b3ddf80ac8a1443441249ae8170b8 [file] [log] [blame]
/*
* Copyright © 2010-2012 Linaro Limited
*
* This file is part of the glmark2 OpenGL (ES) 2.0 benchmark.
*
* glmark2 is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* glmark2 is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* glmark2. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Alexandros Frantzis (glmark2)
* Jesse Barker
* Simon Que
*/
#include "canvas-drm.h"
#include "log.h"
#include "options.h"
#include "util.h"
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <fstream>
#include <sstream>
#include <list>
/******************
* Public methods *
******************/
bool
CanvasDRM::reset()
{
if (!reset_context())
return false;
if (!make_current())
return false;
if (!supports_gl2()) {
Log::error("Glmark2 needs OpenGL(ES) version >= 2.0 to run"
" (but version string is: '%s')!\n",
glGetString(GL_VERSION));
return false;
}
glViewport(0, 0, width_, height_);
clear();
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
return true;
}
bool
CanvasDRM::init()
{
EGLint major, minor;
const char *ver, *extensions;
uint32_t handle, stride;
int i, retval;
const char device_name[] = "/dev/dri/card0";
num_buffers_ = Options::num_buffers;
current_front_buffer_index_ = -1;
signal(SIGINT, &CanvasDRM::quit_handler);
fd_ = open(device_name, O_RDWR);
if (fd_ < 0) {
// Probably permissions error
fprintf(stderr, "Couldn't open %s, skipping.\n", device_name);
return false;
}
gbm_ = gbm_create_device(fd_);
if (gbm_ == NULL) {
fprintf(stderr, "Couldn't create GBM device.\n");
goto close_fd;
}
egl_display_ = eglGetDisplay(gbm_);
if (egl_display_ == EGL_NO_DISPLAY) {
fprintf(stderr, "eglGetDisplay() failed.\n");
goto destroy_gbm_device;
}
if (!eglInitialize(egl_display_, &major, &minor)) {
fprintf(stderr, "eglInitialize() failed.\n");
goto egl_terminate;
}
ver = eglQueryString(egl_display_, EGL_VERSION);
printf("EGL_VERSION = %s\n", ver);
extensions = eglQueryString(egl_display_, EGL_EXTENSIONS);
printf("EGL_EXTENSIONS: %s\n", extensions);
if (!strstr(extensions, "EGL_KHR_surfaceless_opengl")) {
printf("No support for EGL_KHR_surfaceless_opengl.\n");
goto egl_terminate;
}
if (!setup_kms(fd_, &kms_))
goto egl_terminate;
eglBindAPI(EGL_OPENGL_API);
egl_context_ = eglCreateContext(egl_display_, NULL, EGL_NO_CONTEXT, NULL);
if (egl_context_ == NULL) {
fprintf(stderr, "Failed to create context.\n");
goto egl_terminate;
}
if (!eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE,
egl_context_)) {
fprintf(stderr, "Failed to make context current.\n");
goto destroy_context;
}
if (GLExtensions::MapBuffer == NULL && GLExtensions::UnmapBuffer == NULL)
init_gl_extensions();
glGenFramebuffers(1, &fb_);
glBindFramebuffer(GL_FRAMEBUFFER, fb_);
render_buffers_.resize(num_buffers_);
buffer_objs_.resize(num_buffers_);
kms_.fb_ids.resize(num_buffers_);
images_.resize(num_buffers_);
for (i = 0; i < num_buffers_; i++) {
glGenRenderbuffers(1, &render_buffers_[i]);
glBindRenderbuffer(GL_RENDERBUFFER, render_buffers_[i]);
buffer_objs_[i] =
gbm_bo_create(gbm_, kms_.mode.hdisplay, kms_.mode.vdisplay,
GBM_BO_FORMAT_XRGB8888,
GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
if (buffer_objs_[i] == NULL) {
fprintf(stderr, "Failed to create GBM buffer objects.\n");
goto unmake_current;
}
handle = gbm_bo_get_handle(buffer_objs_[i]).u32;
stride = gbm_bo_get_pitch(buffer_objs_[i]);
images_[i] = eglCreateImageKHR(egl_display_, NULL,
EGL_NATIVE_PIXMAP_KHR,
buffer_objs_[i], NULL);
if (images_[i] == EGL_NO_IMAGE_KHR) {
fprintf(stderr, "Failed to create EGL image.\n");
goto destroy_gbm_bo;
}
#ifdef GL_OES_EGL_image
glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, images_[i]);
#else
fprintf(stderr, "GL_OES_EGL_image was not found at compile time.\n");
#endif
glGenRenderbuffers(1, &depth_rb_);
glBindRenderbuffer(GL_RENDERBUFFER, depth_rb_);
glRenderbufferStorage(GL_RENDERBUFFER,
GL_DEPTH_COMPONENT,
kms_.mode.hdisplay, kms_.mode.vdisplay);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER,
depth_rb_);
retval = drmModeAddFB(fd_, kms_.mode.hdisplay, kms_.mode.vdisplay,
24, 32, stride, handle, &kms_.fb_ids[i]);
if (retval) {
fprintf(stderr, "Failed to create framebuffer.\n");
goto rm_rb;
}
available_buffers_.push(i);
}
current_back_buffer_index_ = available_buffers_.front();
available_buffers_.pop();
saved_crtc_ = drmModeGetCrtc(fd_, kms_.encoder->crtc_id);
if (saved_crtc_ == NULL)
goto rm_fb;
if (!reset())
{
fprintf(stderr, "failed to reset canvas!\n");
goto drm_mode_free_crtc;
}
glFramebufferRenderbuffer(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER,
render_buffers_[current_back_buffer_index_]);
if ((retval = glCheckFramebufferStatus(GL_FRAMEBUFFER)) !=
GL_FRAMEBUFFER_COMPLETE) {
fprintf(stderr, "framebuffer not complete: %x\n", retval);
goto drm_mode_free_crtc;
}
resize_no_viewport(width_, height_);
return true;
drm_mode_free_crtc:
drmModeFreeCrtc(saved_crtc_);
rm_rb:
glFramebufferRenderbuffer(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
for (i = 0; i < num_buffers_; i++)
glDeleteRenderbuffers(1, &render_buffers_[i]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, 0);
glDeleteRenderbuffers(1, &depth_rb_);
rm_fb:
for (i = 0; i < num_buffers_; i++) {
drmModeRmFB(fd_, kms_.fb_ids[i]);
eglDestroyImageKHR(egl_display_, images_[i]);
}
destroy_gbm_bo:
for (i = 0; i < num_buffers_; i++)
gbm_bo_destroy(buffer_objs_[i]);
unmake_current:
eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
destroy_context:
eglDestroyContext(egl_display_, egl_context_);
egl_terminate:
eglTerminate(egl_display_);
destroy_gbm_device:
gbm_device_destroy(gbm_);
close_fd:
close(fd_);
return false;
}
CanvasDRM::~CanvasDRM() {
int i, ret;
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: %d\n", ret);
}
drmModeFreeCrtc(saved_crtc_);
glFramebufferRenderbuffer(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
for (i = 0; i < num_buffers_; i++)
glDeleteRenderbuffers(1, &render_buffers_[i]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER,
GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, 0);
glDeleteRenderbuffers(1, &depth_rb_);
for (i = 0; i < num_buffers_; i++) {
drmModeRmFB(fd_, kms_.fb_ids[i]);
eglDestroyImageKHR(egl_display_, images_[i]);
}
for (i = 0; i < num_buffers_; i++)
gbm_bo_destroy(buffer_objs_[i]);
eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
eglDestroyContext(egl_display_, egl_context_);
eglTerminate(egl_display_);
gbm_device_destroy(gbm_);
close(fd_);
}
void
CanvasDRM::visible(bool /* visible */)
{
}
void CanvasDRM::clear()
{
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
#if USE_GL
glClearDepth(1.0f);
#elif USE_GLESv2
glClearDepthf(1.0f);
#endif
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void CanvasDRM::update()
{
if (Options::swap_buffers)
swap_buffers();
else
glFinish();
}
void CanvasDRM::print_info()
{
make_current();
std::stringstream ss;
ss << " OpenGL Information" << std::endl;
ss << " GL_VENDOR: " << glGetString(GL_VENDOR) << std::endl;
ss << " GL_RENDERER: " << glGetString(GL_RENDERER) << std::endl;
ss << " GL_VERSION: " << glGetString(GL_VERSION) << std::endl;
Log::info("%s", ss.str().c_str());
}
Canvas::Pixel CanvasDRM::read_pixel(int x, int y)
{
uint8_t pixel[4];
glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
return Canvas::Pixel(pixel[0], pixel[1], pixel[2], pixel[3]);
}
void CanvasDRM::write_to_file(std::string &filename)
{
char *pixels = new char[width_ * height_ * 4];
for (int i = 0; i < height_; i++) {
glReadPixels(0, i, width_, 1, GL_RGBA, GL_UNSIGNED_BYTE,
&pixels[(height_ - i - 1) * width_ * 4]);
}
std::ofstream output (filename.c_str(), std::ios::out | std::ios::binary);
output.write(pixels, 4 * width_ * height_);
delete [] pixels;
}
bool
CanvasDRM::should_quit()
{
return should_quit_;
}
void
CanvasDRM::resize(int width, int height)
{
resize_no_viewport(width, height);
glViewport(0, 0, width_, height_);
}
int
CanvasDRM::get_frame_buffer() {
return fb_;
}
/*********************
* Protected methods *
*********************/
bool CanvasDRM::supports_gl2()
{
std::string gl_version_str(
reinterpret_cast<const char*>(glGetString(GL_VERSION)));
int gl_major = 0;
size_t point_pos(gl_version_str.find('.'));
if (point_pos != std::string::npos) {
point_pos--;
size_t start_pos(gl_version_str.rfind(' ', point_pos));
if (start_pos == std::string::npos)
start_pos = 0;
else
start_pos++;
gl_major = Util::fromString<int>(
gl_version_str.substr(start_pos, point_pos - start_pos + 1)
);
}
return gl_major >= 2;
}
bool CanvasDRM::make_current()
{
if (!ensure_egl_surface())
return false;
if (!ensure_egl_context())
return false;
if (egl_context_ == eglGetCurrentContext())
return true;
if (!eglMakeCurrent(egl_display_, egl_surface_, egl_surface_,
egl_context_)) {
Log::error("Error: eglMakeCurrent failed with error %d\n",
eglGetError());
return false;
}
if (!eglSwapInterval(egl_display_, 0))
Log::info("** Failed to set swap interval. Results may be bounded above"
" by refresh rate.\n");
init_gl_extensions();
return true;
}
bool CanvasDRM::reset_context()
{
if (!ensure_egl_display())
return false;
if (!egl_context_)
return true;
if (eglDestroyContext(egl_display_, egl_context_) == EGL_FALSE) {
Log::debug("eglDestroyContext() failed with error: 0x%x\n",
eglGetError());
}
egl_context_ = 0;
return true;
}
extern unsigned int *nitrous_timestamps;
void CanvasDRM::swap_buffers()
{
int ret;
rendered_buffers_.push(current_back_buffer_index_);
pollfd poll_data;
poll_data.fd = fd_;
poll_data.events = POLLIN;
// Check the display controller to see if it has data to be read back.
ret = poll(&poll_data, 1, 0);
// If there is display controller data available, or there are no more
// free buffers, handle the display controller data.
if (ret > 0 || available_buffers_.empty()) {
// Make sure there is data available.
if (ret <= 0)
while (poll(&poll_data, 1, -1) <= 0);
// Handle the display controller page flip event(s).
drmEventContext event_context;
memset(&event_context, 0, sizeof event_context);
event_context.version = DRM_EVENT_CONTEXT_VERSION;
event_context.page_flip_handler = CanvasDRM::page_flip_handler;
drmHandleEvent(fd_, &event_context);
}
// Get the next buffer for rendering.
current_back_buffer_index_ = available_buffers_.front();
available_buffers_.pop();
glFramebufferRenderbuffer(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER,
render_buffers_[current_back_buffer_index_]);
if ((ret = glCheckFramebufferStatus(GL_FRAMEBUFFER)) !=
GL_FRAMEBUFFER_COMPLETE) {
fprintf(stderr, "framebuffer not complete: %x\n", ret);
return;
}
// Attempt to flip to the first rendered buffer in the queue.
if (current_front_buffer_index_ < 0) {
ret = drmModePageFlip(fd_, kms_.encoder->crtc_id,
kms_.fb_ids[rendered_buffers_.front()],
DRM_MODE_PAGE_FLIP_EVENT, this);
if (!ret) {
// If flip was successful, take it out of the queue.
current_front_buffer_index_ = rendered_buffers_.front();
rendered_buffers_.pop();
}
}
}
bool
CanvasDRM::ensure_egl_display()
{
if (egl_display_)
return true;
egl_display_ = eglGetDisplay((EGLNativeDisplayType) egl_display_);
if (!egl_display_) {
Log::error("eglGetDisplay() failed with error: %d\n",
eglGetError());
return false;
}
if (!eglInitialize(egl_display_, NULL, NULL)) {
Log::error("eglInitialize() failed with error: %d\n",
eglGetError());
return false;
egl_display_ = 0;
}
return true;
}
bool
CanvasDRM::ensure_egl_config()
{
static const EGLint attribs[] = {
EGL_RED_SIZE, 1,
EGL_GREEN_SIZE, 1,
EGL_BLUE_SIZE, 1,
EGL_ALPHA_SIZE, 1,
EGL_DEPTH_SIZE, 1,
#if USE_GLESv2
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
#elif USE_GL
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
#endif
EGL_NONE
};
EGLint num_configs;
EGLint vid;
if (egl_config_)
return true;
if (!ensure_egl_display())
return false;
if (!eglChooseConfig(egl_display_, attribs, &egl_config_, 1,
&num_configs)) {
Log::error("eglChooseConfig() failed with error: %d\n", eglGetError());
return false;
}
if (!eglGetConfigAttrib(egl_display_, egl_config_,
EGL_NATIVE_VISUAL_ID, &vid)) {
Log::error("eglGetConfigAttrib() failed with error: %d\n",
eglGetError());
return false;
}
if (Options::show_debug) {
int buf, red, green, blue, alpha, depth, id, native_id;
eglGetConfigAttrib(egl_display_, egl_config_, EGL_CONFIG_ID, &id);
eglGetConfigAttrib(egl_display_, egl_config_, EGL_NATIVE_VISUAL_ID,
&native_id);
eglGetConfigAttrib(egl_display_, egl_config_, EGL_BUFFER_SIZE, &buf);
eglGetConfigAttrib(egl_display_, egl_config_, EGL_RED_SIZE, &red);
eglGetConfigAttrib(egl_display_, egl_config_, EGL_GREEN_SIZE, &green);
eglGetConfigAttrib(egl_display_, egl_config_, EGL_BLUE_SIZE, &blue);
eglGetConfigAttrib(egl_display_, egl_config_, EGL_ALPHA_SIZE, &alpha);
eglGetConfigAttrib(egl_display_, egl_config_, EGL_DEPTH_SIZE, &depth);
Log::debug("EGL chosen config ID: 0x%x Native Visual ID: 0x%x\n"
" Buffer: %d bits\n"
" Red: %d bits\n"
" Green: %d bits\n"
" Blue: %d bits\n"
" Alpha: %d bits\n"
" Depth: %d bits\n",
id, native_id,
buf, red, green, blue, alpha, depth);
}
return true;
}
bool
CanvasDRM::ensure_egl_context()
{
return true;
}
bool
CanvasDRM::ensure_egl_surface()
{
return true;
}
void
CanvasDRM::init_gl_extensions()
{
#if USE_GLESv2
/*
* Parse the extensions we care about from the extension string.
* Don't even bother to get function pointers until we know the
* extension is present.
*/
std::string extString;
const char* exts =
reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
if (exts)
extString = exts;
if (extString.find("GL_OES_mapbuffer") != std::string::npos) {
GLExtensions::MapBuffer = reinterpret_cast<PFNGLMAPBUFFEROESPROC>(
eglGetProcAddress("glMapBufferOES"));
GLExtensions::UnmapBuffer = reinterpret_cast<PFNGLUNMAPBUFFEROESPROC>(
eglGetProcAddress("glUnmapBufferOES"));
}
#elif USE_GL
GLExtensions::MapBuffer = glMapBuffer;
GLExtensions::UnmapBuffer = glUnmapBuffer;
#endif
}
/*******************
* Private methods *
*******************/
void
CanvasDRM::resize_no_viewport(int width, int height)
{
width_ = width;
height_ = height;
projection_ =
LibMatrix::Mat4::perspective(60.0, width_ / static_cast<float>(height_),
1.0, 1024.0);
}
EGLBoolean CanvasDRM::setup_kms(int fd_, struct CanvasDRM::kms *kms_)
{
drmModeRes *resources = NULL;
drmModeConnector *connector = NULL;
drmModeEncoder *encoder = NULL;
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;
}
void CanvasDRM::page_flip_handler(int /* fd */, unsigned int /* frame */,
unsigned int /* sec */, unsigned int /* usec */, void* data)
{
CanvasDRM* canvas = reinterpret_cast<CanvasDRM*> (data);
canvas->available_buffers_.push(canvas->current_front_buffer_index_);
canvas->current_front_buffer_index_ = -1;
}
bool CanvasDRM::should_quit_ = false;
void CanvasDRM::quit_handler(int /* signum */)
{
should_quit_ = true;
}