| /* |
| * 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; |
| } |