| // |
| // Copyright © 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: |
| // Simon Que |
| // Jesse Barker |
| // |
| #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_); |
| glEnable(GL_DEPTH_TEST); |
| glDepthFunc(GL_LEQUAL); |
| glEnable(GL_CULL_FACE); |
| glCullFace(GL_BACK); |
| |
| clear(); |
| egl_.swap(); |
| |
| if (!drm_.reset()) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void |
| DRMState::fb_destroy_callback(gbm_bo* bo, void* data) |
| { |
| DRMFBState* fb = reinterpret_cast<DRMFBState*>(data); |
| if (fb && fb->fb_id) { |
| drmModeRmFB(fb->fd, fb->fb_id); |
| } |
| delete fb; |
| gbm_device* dev = gbm_bo_get_device(bo); |
| Log::debug("Got GBM device handle %p from buffer object\n", dev); |
| } |
| |
| DRMFBState* |
| DRMState::fb_get_from_bo(gbm_bo* bo) |
| { |
| DRMFBState* fb = reinterpret_cast<DRMFBState*>(gbm_bo_get_user_data(bo)); |
| if (fb) { |
| return fb; |
| } |
| |
| unsigned int width = gbm_bo_get_width(bo); |
| unsigned int height = gbm_bo_get_height(bo); |
| unsigned int stride = gbm_bo_get_stride(bo); |
| unsigned int handle = gbm_bo_get_handle(bo).u32; |
| unsigned int fb_id(0); |
| int status = drmModeAddFB(fd_, width, height, 24, 32, stride, handle, &fb_id); |
| if (status < 0) { |
| Log::error("Failed to create FB: %d\n", status); |
| return 0; |
| } |
| |
| fb = new DRMFBState(); |
| fb->fd = fd_; |
| fb->bo = bo; |
| fb->fb_id = fb_id; |
| |
| gbm_bo_set_user_data(bo, fb, fb_destroy_callback); |
| return fb; |
| } |
| |
| bool |
| DRMState::init_gbm() |
| { |
| dev_ = gbm_create_device(fd_); |
| if (!dev_) { |
| Log::error("Failed to create GBM device\n"); |
| return false; |
| } |
| |
| surface_ = gbm_surface_create(dev_, mode_->hdisplay, mode_->vdisplay, |
| GBM_FORMAT_XRGB8888, |
| GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); |
| if (!surface_) { |
| Log::error("Failed to create GBM surface\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| DRMState::init() |
| { |
| // TODO: Replace this with something that explicitly probes for the loaded |
| // driver (udev?). |
| static const char* drm_modules[] = { |
| "i915", |
| "nouveau", |
| "radeon", |
| "vmgfx", |
| "omapdrm", |
| "exynos" |
| }; |
| |
| unsigned int num_modules(sizeof(drm_modules)/sizeof(drm_modules[0])); |
| for (unsigned int m = 0; m < num_modules; m++) { |
| fd_ = drmOpen(drm_modules[m], 0); |
| if (fd_ < 0) { |
| Log::debug("Failed to open DRM module '%s'\n", drm_modules[m]); |
| continue; |
| } |
| Log::debug("Opened DRM module '%s'\n", drm_modules[m]); |
| break; |
| } |
| |
| if (fd_ < 0) { |
| Log::error("Failed to find a suitable DRM device\n"); |
| return false; |
| } |
| |
| resources_ = drmModeGetResources(fd_); |
| if (!resources_) { |
| Log::error("drmModeGetResources failed\n"); |
| return false; |
| } |
| |
| // Find a connected connector |
| for (int c = 0; c < resources_->count_connectors; c++) { |
| connector_ = drmModeGetConnector(fd_, resources_->connectors[c]); |
| if (DRM_MODE_CONNECTED == connector_->connection) { |
| break; |
| } |
| drmModeFreeConnector(connector_); |
| connector_ = 0; |
| } |
| |
| if (!connector_) { |
| Log::error("Failed to find a suitable connector\n"); |
| return false; |
| } |
| |
| // Find the best resolution (we will always operate full-screen). |
| unsigned int bestArea(0); |
| for (int m = 0; m < connector_->count_modes; m++) { |
| drmModeModeInfo* curMode = &connector_->modes[m]; |
| unsigned int curArea = curMode->hdisplay * curMode->vdisplay; |
| if (curArea > bestArea) { |
| mode_ = curMode; |
| bestArea = curArea; |
| } |
| } |
| |
| if (!mode_) { |
| Log::error("Failed to find a suitable mode\n"); |
| return false; |
| } |
| |
| // Find a suitable encoder |
| for (int e = 0; e < resources_->count_encoders; e++) { |
| encoder_ = drmModeGetEncoder(fd_, resources_->encoders[e]); |
| if (encoder_ && encoder_->encoder_id == connector_->encoder_id) { |
| break; |
| } |
| drmModeFreeEncoder(encoder_); |
| encoder_ = 0; |
| } |
| |
| if (!encoder_) { |
| Log::error("Failed to find a suitable encoder\n"); |
| return false; |
| } |
| |
| if (!init_gbm()) { |
| return false; |
| } |
| |
| crtc_ = drmModeGetCrtc(fd_, encoder_->crtc_id); |
| if (!crtc_) { |
| Log::error("Failed to get current CRTC\n"); |
| return false; |
| } |
| |
| signal(SIGINT, &DRMState::quit_handler); |
| |
| return true; |
| } |
| |
| bool |
| DRMState::reset() |
| { |
| if (bo_) { |
| gbm_surface_release_buffer(surface_, bo_); |
| } |
| |
| bo_ = gbm_surface_lock_front_buffer(surface_); |
| fb_ = fb_get_from_bo(bo_); |
| |
| int status = drmModeSetCrtc(fd_, encoder_->crtc_id, fb_->fb_id, 0, 0, |
| &connector_->connector_id, 1, mode_); |
| if (status < 0) { |
| Log::error("Failed to set CRTC: %d\n", status); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| |
| bool DRMState::should_quit_ = false; |
| |
| void |
| DRMState::quit_handler(int signo) |
| { |
| Log::debug("Got SIGINT (%d).\n", signo); |
| should_quit_ = true; |
| } |
| void |
| DRMState::page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void* data) |
| { |
| unsigned int* waiting = reinterpret_cast<unsigned int*>(data); |
| *waiting = 0; |
| // Deal with unused parameters |
| static_cast<void>(fd); |
| static_cast<void>(frame); |
| static_cast<void>(sec); |
| static_cast<void>(usec); |
| } |
| |
| void |
| DRMState::do_flip() |
| { |
| gbm_bo* next = gbm_surface_lock_front_buffer(surface_); |
| fb_ = fb_get_from_bo(next); |
| unsigned int waiting(1); |
| int status = drmModePageFlip(fd_, encoder_->crtc_id, fb_->fb_id, |
| DRM_MODE_PAGE_FLIP_EVENT, &waiting); |
| if (status < 0) { |
| Log::error("Failed to enqueue page flip: %d\n", status); |
| return; |
| } |
| |
| fd_set fds; |
| FD_ZERO(&fds); |
| FD_SET(fd_, &fds); |
| drmEventContext evCtx; |
| evCtx.version = DRM_EVENT_CONTEXT_VERSION; |
| evCtx.page_flip_handler = page_flip_handler; |
| |
| while (waiting) { |
| status = select(fd_ + 1, &fds, 0, 0, 0); |
| if (status < 0) { |
| // Most of the time, select() will return an error because the |
| // user pressed Ctrl-C. So, only print out a message in debug |
| // mode, and just check for the likely condition and release |
| // the current buffer object before getting out. |
| Log::debug("Error in select\n"); |
| if (should_quit()) { |
| gbm_surface_release_buffer(surface_, bo_); |
| bo_ = next; |
| } |
| return; |
| } |
| drmHandleEvent(fd_, &evCtx); |
| } |
| |
| gbm_surface_release_buffer(surface_, bo_); |
| bo_ = next; |
| } |
| |
| void |
| DRMState::cleanup() |
| { |
| // Restore CRTC state if necessary |
| if (crtc_) { |
| int status = drmModeSetCrtc(fd_, crtc_->crtc_id, crtc_->buffer_id, |
| crtc_->x, crtc_->y, &connector_->connector_id, |
| 1, &crtc_->mode); |
| if (status < 0) { |
| Log::error("Failed to restore original CRTC: %d\n", status); |
| } |
| drmModeFreeCrtc(crtc_); |
| crtc_ = 0; |
| } |
| if (surface_) { |
| gbm_surface_destroy(surface_); |
| surface_ = 0; |
| } |
| if (dev_) { |
| gbm_device_destroy(dev_); |
| dev_ = 0; |
| } |
| if (connector_) { |
| drmModeFreeConnector(connector_); |
| connector_ = 0; |
| } |
| if (encoder_) { |
| drmModeFreeEncoder(encoder_); |
| encoder_ = 0; |
| } |
| if (resources_) { |
| drmModeFreeResources(resources_); |
| resources_ = 0; |
| } |
| if (fd_ > 0) { |
| drmClose(fd_); |
| } |
| fd_ = 0; |
| mode_ = 0; |
| } |
| |
| bool |
| CanvasDRM::init() |
| { |
| Log::info("NOTE: The DRM canvas is still experimental and its behavior\n"); |
| Log::info(" is subject to change as GBM and modesetting behavior\n"); |
| Log::info(" evolves\n"); |
| |
| if (!drm_.init()) { |
| Log::error("Failed to initialize the DRM canvas\n"); |
| drm_.cleanup(); |
| return false; |
| } |
| |
| width_ = drm_.mode_width(); |
| height_ = drm_.mode_height(); |
| resize_no_viewport(width_, height_); |
| |
| if (!egl_.init_display(drm_.device(), visual_config_)) { |
| return false; |
| } |
| if (!egl_.init_surface(drm_.surface())) { |
| return false; |
| } |
| if (!egl_.valid()) { |
| return false; |
| } |
| |
| return reset(); |
| } |
| |
| |
| CanvasDRM::~CanvasDRM() |
| { |
| drm_.cleanup(); |
| } |
| |
| 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() |
| { |
| Options::FrameEnd m = Options::frame_end; |
| |
| if (m == Options::FrameEndDefault) { |
| if (offscreen_) |
| m = Options::FrameEndFinish; |
| else |
| m = Options::FrameEndSwap; |
| } |
| |
| switch(m) { |
| case Options::FrameEndSwap: |
| swap_buffers(); |
| break; |
| case Options::FrameEndFinish: |
| glFinish(); |
| break; |
| case Options::FrameEndReadPixels: |
| read_pixel(width_ / 2, height_ / 2); |
| break; |
| case Options::FrameEndNone: |
| default: |
| break; |
| } |
| } |
| |
| 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 drm_.should_quit(); |
| } |
| |
| void |
| CanvasDRM::resize(int width, int height) |
| { |
| resize_no_viewport(width, height); |
| glViewport(0, 0, width_, height_); |
| } |
| |
| /********************* |
| * 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 (!egl_.valid()) { |
| Log::error("CanvasDRM: Invalid EGL state\n"); |
| return false; |
| } |
| |
| init_gl_extensions(); |
| |
| return true; |
| } |
| |
| bool |
| CanvasDRM::reset_context() |
| { |
| return egl_.reset(); |
| } |
| |
| void |
| CanvasDRM::swap_buffers() |
| { |
| egl_.swap(); |
| drm_.do_flip(); |
| } |
| |
| 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_ = drm_.mode_width(); |
| height_ = drm_.mode_height(); |
| |
| if (!width_) |
| width_ = width; |
| if (!height_) |
| height_ = height; |
| |
| projection_ = |
| LibMatrix::Mat4::perspective(60.0, width_ / static_cast<float>(height_), |
| 1.0, 1024.0); |
| } |