blob: 0ca896c2917be06f5bfeae8741c2e871868de4fa [file] [log] [blame]
// Copyright 2016 The Android Open Source Project
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program 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.
#include "android/skin/qt/gl-widget.h"
#include "android/base/memory/LazyInstance.h"
#include "GLES2/gl2.h"
#include <QResizeEvent>
#include <QtGlobal>
#include <QWindow>
// Note that this header must come after ALL Qt headers.
// Including any Qt headers after it will cause compilation
// to fail spectacularly. This is because EGL indirectly
// includes X11 headers which define stuff that conflicts
// with Qt's own macros. It is a known issue.
#include "OpenGLESDispatch/EGLDispatch.h"
#include "OpenGLESDispatch/GLESv2Dispatch.h"
namespace {
// Helper class to hold a global GLESv2Dispatch that is initialized lazily
// in a thread-safe way. The instance is leaked on program exit.
struct MyGLESv2Dispatch : public GLESv2Dispatch {
// Return pointer to global GLESv2Dispatch instance, or nullptr if there
// was an error when trying to initialize/load the library.
static const GLESv2Dispatch* get();
MyGLESv2Dispatch() { mValid = gles2_dispatch_init(&mDispatch); }
private:
GLESv2Dispatch mDispatch;
bool mValid;
};
// Must be declared outside of MyGLESv2Dispatch scope due to the use of
// sizeof(T) within the template definition.
android::base::LazyInstance<MyGLESv2Dispatch> sGLESv2Dispatch =
LAZY_INSTANCE_INIT;
// static
const GLESv2Dispatch* MyGLESv2Dispatch::get() {
MyGLESv2Dispatch* instance = sGLESv2Dispatch.ptr();
if (instance->mValid) {
return &instance->mDispatch;
} else {
return nullptr;
}
}
// Helper class used to lazily initialize the global EGL dispatch table
// in a thread safe way. Note that the dispatch table is provided by
// libOpenGLESDispatch as the 's_egl' global variable.
struct MyEGLDispatch : public EGLDispatch {
// Return pointer to EGLDispatch table, or nullptr if there was
// an error when trying to initialize/load the library.
static const EGLDispatch* get();
MyEGLDispatch() { mValid = init_egl_dispatch(); }
private:
bool mValid;
};
android::base::LazyInstance<MyEGLDispatch> sEGLDispatch = LAZY_INSTANCE_INIT;
// static
const EGLDispatch* MyEGLDispatch::get() {
MyEGLDispatch* instance = sEGLDispatch.ptr();
if (instance->mValid) {
return &s_egl;
} else {
return nullptr;
}
}
} // namespace
struct EGLState {
EGLDisplay display;
EGLContext context;
EGLSurface surface;
};
// Helper function to get the nearest power-of-two.
// It's needed to create a GLCanvas with correct dimensions
// to allow generating mipmaps (GLES 2.0 doesn't support
// mipmaps for NPOT textures)
static int nearestPOT(int value) {
return pow(2, ceil(log(value)/log(2)));
}
GLWidget::GLWidget(QWidget* parent) :
QWidget(parent),
mEGL(MyEGLDispatch::get()),
mGLES2(MyGLESv2Dispatch::get()),
mEGLState(nullptr),
mValid(false),
mEnableAA(false) {
setAutoFillBackground(false);
setAttribute(Qt::WA_OpaquePaintEvent, true);
setAttribute(Qt::WA_PaintOnScreen, true);
setAttribute(Qt::WA_NoSystemBackground, true);
// QGLWidget always has a native window, and ours should behave
// in a similar fashion as well. Not having these attributes set
// causes issues with the window initialization.
setAttribute(Qt::WA_DontCreateNativeAncestors, true);
setAttribute(Qt::WA_NativeWindow, true);
}
void GLWidget::handleScreenChange(QScreen*) {
// Destroy the context, forcing its re-creation on
// next paint event.
destroyContext();
resizeGL(realPixelsWidth(), realPixelsHeight());
}
bool GLWidget::ensureInit() {
// If an error occured when loading the EGL/GLESv2 libraries, return false.
if (!mEGL || !mGLES2) {
return false;
}
// If already initialized, return mValid to indicate if an error occured.
if (mEGLState) {
return mValid;
}
mEGLState = new EGLState();
mValid = false;
mEGLState->display = mEGL->eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (mEGLState->display == EGL_NO_DISPLAY) {
qWarning("Failed to get EGL display: EGL error %d",
mEGL->eglGetError());
return false;
}
EGLint egl_maj, egl_min;
EGLConfig egl_config;
// Try to initialize EGL display.
// Initializing an already-initialized display is OK.
if (mEGL->eglInitialize(mEGLState->display, &egl_maj, &egl_min) ==
EGL_FALSE) {
qWarning("Failed to initialize EGL display: EGL error %d",
mEGL->eglGetError());
return false;
}
// Get an EGL config.
const EGLint config_attribs[] = {EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_DEPTH_SIZE,
24,
EGL_SAMPLES,
0, // No multisampling
EGL_NONE};
EGLint num_config;
EGLBoolean choose_result = mEGL->eglChooseConfig(
mEGLState->display, config_attribs, &egl_config, 1, &num_config);
if (choose_result == EGL_FALSE || num_config < 1) {
qWarning("Failed to choose EGL config: EGL error %d",
mEGL->eglGetError());
return false;
}
// Create a context.
EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
mEGLState->context = mEGL->eglCreateContext(
mEGLState->display, egl_config, EGL_NO_CONTEXT, context_attribs);
if (mEGLState->context == EGL_NO_CONTEXT) {
qWarning("Failed to create EGL context %d", mEGL->eglGetError());
}
// Finally, create a window surface associated with this widget.
mEGLState->surface = mEGL->eglCreateWindowSurface(
mEGLState->display, egl_config, (EGLNativeWindowType)(winId()),
nullptr);
if (mEGLState->surface == EGL_NO_SURFACE) {
qWarning("Failed to create an EGL surface %d", mEGL->eglGetError());
return false;
}
makeContextCurrent();
mCanvas.reset(new GLCanvas(
nearestPOT(realPixelsWidth()),
nearestPOT(realPixelsHeight()),
mGLES2));
mTextureDraw.reset(new TextureDraw(mGLES2));
mValid = initGL();
return mValid;
}
bool GLWidget::makeContextCurrent() {
if (mEGLState) {
return
mEGL->eglMakeCurrent(mEGLState->display, mEGLState->surface,
mEGLState->surface, mEGLState->context) == EGL_TRUE;
} else {
return false;
}
}
void GLWidget::renderFrame() {
if (!readyForRendering()) {
return;
}
makeContextCurrent();
// Render 3D scene to texture.
if (mEnableAA) {
mCanvas->bind();
repaintGL();
mCanvas->unbind();
} else {
mGLES2->glViewport(0, 0, realPixelsWidth(), realPixelsHeight());
repaintGL();
}
if (mEnableAA) {
mGLES2->glBindTexture(GL_TEXTURE_2D, mCanvas->texture());
mGLES2->glGenerateMipmap(GL_TEXTURE_2D);
mTextureDraw->draw(mCanvas->texture(),
realPixelsWidth(),
realPixelsHeight());
}
mEGL->eglSwapBuffers(mEGLState->display, mEGLState->surface);
}
void GLWidget::paintEvent(QPaintEvent*) {
if (ensureInit()) {
renderFrame();
}
}
void GLWidget::showEvent(QShowEvent*) {
// When the widget first becomes visible, we render a frame,
// which will force the necessary initialization.
// It is important to make sure that the widget is actually
// visible on screen (at least for OS X) before doing any
// initialization at all.
// However, show events may be delivered when the widget
// isn't visible yet, so we need an additional check.
if (isVisible() && !visibleRegion().isNull()) {
if (ensureInit()) {
renderFrame();
}
}
connect(window()->windowHandle(), SIGNAL(screenChanged(QScreen*)), this, SLOT(handleScreenChange(QScreen*)));
}
void GLWidget::resizeEvent(QResizeEvent* e) {
if (mEGLState) {
// We should only call resizeGL and repaint if all the setup
// has been done.
// We should NOT attempt to initialize EGL state during a resize
// event due to some subtleties on OS X. If we attempt to initialize
// EGL state at the time the resize event is generated, the default
// framebuffer will not be created.
makeContextCurrent();
// Re-create the framebuffer with new size.
mCanvas.reset(new GLCanvas(
nearestPOT(e->size().width() * devicePixelRatio()),
nearestPOT(e->size().height() * devicePixelRatio()),
mGLES2));
resizeGL(e->size().width() * devicePixelRatio(),
e->size().height() * devicePixelRatio());
renderFrame();
}
}
GLWidget::~GLWidget() {
destroyContext();
}
void GLWidget::destroyContext() {
if (mGLES2 && mEGL && mEGLState) {
// Destroy canvas and texturedraw state
// within this context.
makeContextCurrent();
mCanvas.reset(nullptr);
mTextureDraw.reset(nullptr);
// Make sure the context isn't active before destroying it.
mEGL->eglMakeCurrent(mEGLState->display, 0, 0, 0);
// Reset EGL state and set mEGLState to null, which will force
// re-initialization when attempting to render the next frame.
mEGL->eglDestroySurface(mEGLState->display, mEGLState->surface);
mEGL->eglDestroyContext(mEGLState->display, mEGLState->context);
delete mEGLState;
mEGLState = nullptr;
}
}