/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #ifdef Q_OS_LINUX #include #include #endif #include #include "qeglconvenience_p.h" #ifndef EGL_OPENGL_ES3_BIT_KHR #define EGL_OPENGL_ES3_BIT_KHR 0x0040 #endif QT_BEGIN_NAMESPACE QVector q_createConfigAttributesFromFormat(const QSurfaceFormat &format) { int redSize = format.redBufferSize(); int greenSize = format.greenBufferSize(); int blueSize = format.blueBufferSize(); int alphaSize = format.alphaBufferSize(); int depthSize = format.depthBufferSize(); int stencilSize = format.stencilBufferSize(); int sampleCount = format.samples(); QVector configAttributes; // Map default, unspecified values (-1) to 0. This is important due to sorting rule #3 // in section 3.4.1 of the spec and allows picking a potentially faster 16-bit config // over 32-bit ones when there is no explicit request for the color channel sizes: // // The red/green/blue sizes have a sort priority of 3, so they are sorted by // first. (unless a caveat like SLOW or NON_CONFORMANT is present) The sort order is // Special and described as "by larger _total_ number of color bits.". So EGL will put // 32-bit configs in the list before the 16-bit configs. However, the spec also goes // on to say "If the requested number of bits in attrib_list for a particular // component is 0, then the number of bits for that component is not considered". This // part of the spec also seems to imply that setting the red/green/blue bits to zero // means none of the components are considered and EGL disregards the entire sorting // rule. It then looks to the next highest priority rule, which is // EGL_BUFFER_SIZE. Despite the selection criteria being "AtLeast" for // EGL_BUFFER_SIZE, it's sort order is "smaller" meaning 16-bit configs are put in the // list before 32-bit configs. // // This also means that explicitly specifying a size like 565 will still result in // having larger (888) configs first in the returned list. We need to handle this // ourselves later by manually filtering the list, instead of just blindly taking the // first config from it. configAttributes.append(EGL_RED_SIZE); configAttributes.append(redSize > 0 ? redSize : 0); configAttributes.append(EGL_GREEN_SIZE); configAttributes.append(greenSize > 0 ? greenSize : 0); configAttributes.append(EGL_BLUE_SIZE); configAttributes.append(blueSize > 0 ? blueSize : 0); configAttributes.append(EGL_ALPHA_SIZE); configAttributes.append(alphaSize > 0 ? alphaSize : 0); configAttributes.append(EGL_SAMPLES); configAttributes.append(sampleCount > 0 ? sampleCount : 0); configAttributes.append(EGL_SAMPLE_BUFFERS); configAttributes.append(sampleCount > 0); if (format.renderableType() != QSurfaceFormat::OpenVG) { configAttributes.append(EGL_DEPTH_SIZE); configAttributes.append(depthSize > 0 ? depthSize : 0); configAttributes.append(EGL_STENCIL_SIZE); configAttributes.append(stencilSize > 0 ? stencilSize : 0); } else { // OpenVG needs alpha mask for clipping configAttributes.append(EGL_ALPHA_MASK_SIZE); configAttributes.append(8); } return configAttributes; } bool q_reduceConfigAttributes(QVector *configAttributes) { int i = -1; // Reduce the complexity of a configuration request to ask for less // because the previous request did not result in success. Returns // true if the complexity was reduced, or false if no further // reductions in complexity are possible. i = configAttributes->indexOf(EGL_SWAP_BEHAVIOR); if (i >= 0) { configAttributes->remove(i,2); } #ifdef EGL_VG_ALPHA_FORMAT_PRE_BIT // For OpenVG, we sometimes try to create a surface using a pre-multiplied format. If we can't // find a config which supports pre-multiplied formats, remove the flag on the surface type: i = configAttributes->indexOf(EGL_SURFACE_TYPE); if (i >= 0) { EGLint surfaceType = configAttributes->at(i +1); if (surfaceType & EGL_VG_ALPHA_FORMAT_PRE_BIT) { surfaceType ^= EGL_VG_ALPHA_FORMAT_PRE_BIT; configAttributes->replace(i+1,surfaceType); return true; } } #endif // EGL chooses configs with the highest color depth over // those with smaller (but faster) lower color depths. One // way around this is to set EGL_BUFFER_SIZE to 16, which // trumps the others. Of course, there may not be a 16-bit // config available, so it's the first restraint we remove. i = configAttributes->indexOf(EGL_BUFFER_SIZE); if (i >= 0) { if (configAttributes->at(i+1) == 16) { configAttributes->remove(i,2); return true; } } i = configAttributes->indexOf(EGL_SAMPLES); if (i >= 0) { EGLint value = configAttributes->value(i+1, 0); if (value > 1) configAttributes->replace(i+1, qMin(EGLint(16), value / 2)); else configAttributes->remove(i, 2); return true; } i = configAttributes->indexOf(EGL_SAMPLE_BUFFERS); if (i >= 0) { configAttributes->remove(i,2); return true; } i = configAttributes->indexOf(EGL_DEPTH_SIZE); if (i >= 0) { if (configAttributes->at(i + 1) >= 32) configAttributes->replace(i + 1, 24); else if (configAttributes->at(i + 1) > 1) configAttributes->replace(i + 1, 1); else configAttributes->remove(i, 2); return true; } i = configAttributes->indexOf(EGL_ALPHA_SIZE); if (i >= 0) { configAttributes->remove(i,2); #if defined(EGL_BIND_TO_TEXTURE_RGBA) && defined(EGL_BIND_TO_TEXTURE_RGB) i = configAttributes->indexOf(EGL_BIND_TO_TEXTURE_RGBA); if (i >= 0) { configAttributes->replace(i,EGL_BIND_TO_TEXTURE_RGB); configAttributes->replace(i+1,true); } #endif return true; } i = configAttributes->indexOf(EGL_STENCIL_SIZE); if (i >= 0) { if (configAttributes->at(i + 1) > 1) configAttributes->replace(i + 1, 1); else configAttributes->remove(i, 2); return true; } #ifdef EGL_BIND_TO_TEXTURE_RGB i = configAttributes->indexOf(EGL_BIND_TO_TEXTURE_RGB); if (i >= 0) { configAttributes->remove(i,2); return true; } #endif return false; } QEglConfigChooser::QEglConfigChooser(EGLDisplay display) : m_display(display) , m_surfaceType(EGL_WINDOW_BIT) , m_ignore(false) , m_confAttrRed(0) , m_confAttrGreen(0) , m_confAttrBlue(0) , m_confAttrAlpha(0) { } QEglConfigChooser::~QEglConfigChooser() { } EGLConfig QEglConfigChooser::chooseConfig() { QVector configureAttributes = q_createConfigAttributesFromFormat(m_format); configureAttributes.append(EGL_SURFACE_TYPE); configureAttributes.append(surfaceType()); configureAttributes.append(EGL_RENDERABLE_TYPE); bool needsES2Plus = false; switch (m_format.renderableType()) { case QSurfaceFormat::OpenVG: configureAttributes.append(EGL_OPENVG_BIT); break; #ifdef EGL_VERSION_1_4 case QSurfaceFormat::DefaultRenderableType: #ifndef QT_NO_OPENGL if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) configureAttributes.append(EGL_OPENGL_BIT); else #endif // QT_NO_OPENGL needsES2Plus = true; break; case QSurfaceFormat::OpenGL: configureAttributes.append(EGL_OPENGL_BIT); break; #endif case QSurfaceFormat::OpenGLES: if (m_format.majorVersion() == 1) { configureAttributes.append(EGL_OPENGL_ES_BIT); break; } // fall through default: needsES2Plus = true; break; } if (needsES2Plus) { if (m_format.majorVersion() >= 3 && q_hasEglExtension(display(), "EGL_KHR_create_context")) configureAttributes.append(EGL_OPENGL_ES3_BIT_KHR); else configureAttributes.append(EGL_OPENGL_ES2_BIT); } configureAttributes.append(EGL_NONE); EGLConfig cfg = 0; do { // Get the number of matching configurations for this set of properties. EGLint matching = 0; if (!eglChooseConfig(display(), configureAttributes.constData(), 0, 0, &matching) || !matching) continue; // Fetch all of the matching configurations and find the // first that matches the pixel format we wanted. int i = configureAttributes.indexOf(EGL_RED_SIZE); m_confAttrRed = configureAttributes.at(i+1); i = configureAttributes.indexOf(EGL_GREEN_SIZE); m_confAttrGreen = configureAttributes.at(i+1); i = configureAttributes.indexOf(EGL_BLUE_SIZE); m_confAttrBlue = configureAttributes.at(i+1); i = configureAttributes.indexOf(EGL_ALPHA_SIZE); m_confAttrAlpha = i == -1 ? 0 : configureAttributes.at(i+1); QVector configs(matching); eglChooseConfig(display(), configureAttributes.constData(), configs.data(), configs.size(), &matching); if (!cfg && matching > 0) cfg = configs.first(); // Filter the list. Due to the EGL sorting rules configs with higher depth are // placed first when the minimum color channel sizes have been specified (i.e. the // QSurfaceFormat contains color sizes > 0). To prevent returning a 888 config // when the QSurfaceFormat explicitly asked for 565, go through the returned // configs and look for one that exactly matches the requested sizes. When no // sizes have been given, take the first, which will be a config with the smaller // (e.g. 16-bit) depth. for (int i = 0; i < configs.size(); ++i) { if (filterConfig(configs[i])) return configs.at(i); } } while (q_reduceConfigAttributes(&configureAttributes)); if (!cfg) qWarning("Cannot find EGLConfig, returning null config"); return cfg; } bool QEglConfigChooser::filterConfig(EGLConfig config) const { // If we are fine with the highest depth (e.g. RGB888 configs) even when something // smaller (565) was explicitly requested, do nothing. if (m_ignore) return true; EGLint red = 0; EGLint green = 0; EGLint blue = 0; EGLint alpha = 0; // Compare only if a size was given. Otherwise just accept. if (m_confAttrRed) eglGetConfigAttrib(display(), config, EGL_RED_SIZE, &red); if (m_confAttrGreen) eglGetConfigAttrib(display(), config, EGL_GREEN_SIZE, &green); if (m_confAttrBlue) eglGetConfigAttrib(display(), config, EGL_BLUE_SIZE, &blue); if (m_confAttrAlpha) eglGetConfigAttrib(display(), config, EGL_ALPHA_SIZE, &alpha); return red == m_confAttrRed && green == m_confAttrGreen && blue == m_confAttrBlue && alpha == m_confAttrAlpha; } EGLConfig q_configFromGLFormat(EGLDisplay display, const QSurfaceFormat &format, bool highestPixelFormat, int surfaceType) { QEglConfigChooser chooser(display); chooser.setSurfaceFormat(format); chooser.setSurfaceType(surfaceType); chooser.setIgnoreColorChannels(highestPixelFormat); return chooser.chooseConfig(); } QSurfaceFormat q_glFormatFromConfig(EGLDisplay display, const EGLConfig config, const QSurfaceFormat &referenceFormat) { QSurfaceFormat format; EGLint redSize = 0; EGLint greenSize = 0; EGLint blueSize = 0; EGLint alphaSize = 0; EGLint depthSize = 0; EGLint stencilSize = 0; EGLint sampleCount = 0; EGLint renderableType = 0; eglGetConfigAttrib(display, config, EGL_RED_SIZE, &redSize); eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &greenSize); eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &blueSize); eglGetConfigAttrib(display, config, EGL_ALPHA_SIZE, &alphaSize); eglGetConfigAttrib(display, config, EGL_DEPTH_SIZE, &depthSize); eglGetConfigAttrib(display, config, EGL_STENCIL_SIZE, &stencilSize); eglGetConfigAttrib(display, config, EGL_SAMPLES, &sampleCount); eglGetConfigAttrib(display, config, EGL_RENDERABLE_TYPE, &renderableType); if (referenceFormat.renderableType() == QSurfaceFormat::OpenVG && (renderableType & EGL_OPENVG_BIT)) format.setRenderableType(QSurfaceFormat::OpenVG); #ifdef EGL_VERSION_1_4 else if (referenceFormat.renderableType() == QSurfaceFormat::OpenGL && (renderableType & EGL_OPENGL_BIT)) format.setRenderableType(QSurfaceFormat::OpenGL); else if (referenceFormat.renderableType() == QSurfaceFormat::DefaultRenderableType #ifndef QT_NO_OPENGL && QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL #endif && (renderableType & EGL_OPENGL_BIT)) format.setRenderableType(QSurfaceFormat::OpenGL); #endif else format.setRenderableType(QSurfaceFormat::OpenGLES); format.setRedBufferSize(redSize); format.setGreenBufferSize(greenSize); format.setBlueBufferSize(blueSize); format.setAlphaBufferSize(alphaSize); format.setDepthBufferSize(depthSize); format.setStencilBufferSize(stencilSize); format.setSamples(sampleCount); format.setStereo(false); // EGL doesn't support stereo buffers format.setSwapInterval(referenceFormat.swapInterval()); // Clear the EGL error state because some of the above may // have errored out because the attribute is not applicable // to the surface type. Such errors don't matter. eglGetError(); return format; } bool q_hasEglExtension(EGLDisplay display, const char* extensionName) { QList extensions = QByteArray(reinterpret_cast (eglQueryString(display, EGL_EXTENSIONS))).split(' '); return extensions.contains(extensionName); } struct AttrInfo { EGLint attr; const char *name; }; static struct AttrInfo attrs[] = { {EGL_BUFFER_SIZE, "EGL_BUFFER_SIZE"}, {EGL_ALPHA_SIZE, "EGL_ALPHA_SIZE"}, {EGL_BLUE_SIZE, "EGL_BLUE_SIZE"}, {EGL_GREEN_SIZE, "EGL_GREEN_SIZE"}, {EGL_RED_SIZE, "EGL_RED_SIZE"}, {EGL_DEPTH_SIZE, "EGL_DEPTH_SIZE"}, {EGL_STENCIL_SIZE, "EGL_STENCIL_SIZE"}, {EGL_CONFIG_CAVEAT, "EGL_CONFIG_CAVEAT"}, {EGL_CONFIG_ID, "EGL_CONFIG_ID"}, {EGL_LEVEL, "EGL_LEVEL"}, {EGL_MAX_PBUFFER_HEIGHT, "EGL_MAX_PBUFFER_HEIGHT"}, {EGL_MAX_PBUFFER_PIXELS, "EGL_MAX_PBUFFER_PIXELS"}, {EGL_MAX_PBUFFER_WIDTH, "EGL_MAX_PBUFFER_WIDTH"}, {EGL_NATIVE_RENDERABLE, "EGL_NATIVE_RENDERABLE"}, {EGL_NATIVE_VISUAL_ID, "EGL_NATIVE_VISUAL_ID"}, {EGL_NATIVE_VISUAL_TYPE, "EGL_NATIVE_VISUAL_TYPE"}, {EGL_SAMPLES, "EGL_SAMPLES"}, {EGL_SAMPLE_BUFFERS, "EGL_SAMPLE_BUFFERS"}, {EGL_SURFACE_TYPE, "EGL_SURFACE_TYPE"}, {EGL_TRANSPARENT_TYPE, "EGL_TRANSPARENT_TYPE"}, {EGL_TRANSPARENT_BLUE_VALUE, "EGL_TRANSPARENT_BLUE_VALUE"}, {EGL_TRANSPARENT_GREEN_VALUE, "EGL_TRANSPARENT_GREEN_VALUE"}, {EGL_TRANSPARENT_RED_VALUE, "EGL_TRANSPARENT_RED_VALUE"}, {EGL_BIND_TO_TEXTURE_RGB, "EGL_BIND_TO_TEXTURE_RGB"}, {EGL_BIND_TO_TEXTURE_RGBA, "EGL_BIND_TO_TEXTURE_RGBA"}, {EGL_MIN_SWAP_INTERVAL, "EGL_MIN_SWAP_INTERVAL"}, {EGL_MAX_SWAP_INTERVAL, "EGL_MAX_SWAP_INTERVAL"}, {-1, 0}}; void q_printEglConfig(EGLDisplay display, EGLConfig config) { EGLint index; for (index = 0; attrs[index].attr != -1; ++index) { EGLint value; if (eglGetConfigAttrib(display, config, attrs[index].attr, &value)) { qDebug("\t%s: %d", attrs[index].name, (int)value); } } } #ifdef Q_OS_UNIX QSizeF q_physicalScreenSizeFromFb(int framebufferDevice, const QSize &screenSize) { #ifndef Q_OS_LINUX Q_UNUSED(framebufferDevice) #endif const int defaultPhysicalDpi = 100; static QSizeF size; if (size.isEmpty()) { // Note: in millimeters int width = qEnvironmentVariableIntValue("QT_QPA_EGLFS_PHYSICAL_WIDTH"); int height = qEnvironmentVariableIntValue("QT_QPA_EGLFS_PHYSICAL_HEIGHT"); if (width && height) { size.setWidth(width); size.setHeight(height); return size; } int w = -1; int h = -1; QSize screenResolution; #ifdef Q_OS_LINUX struct fb_var_screeninfo vinfo; if (framebufferDevice != -1) { if (ioctl(framebufferDevice, FBIOGET_VSCREENINFO, &vinfo) == -1) { qWarning("eglconvenience: Could not query screen info"); } else { w = vinfo.width; h = vinfo.height; screenResolution = QSize(vinfo.xres, vinfo.yres); } } else #endif { // Use the provided screen size, when available, since some platforms may have their own // specific way to query it. Otherwise try querying it from the framebuffer. screenResolution = screenSize.isEmpty() ? q_screenSizeFromFb(framebufferDevice) : screenSize; } size.setWidth(w <= 0 ? screenResolution.width() * Q_MM_PER_INCH / defaultPhysicalDpi : qreal(w)); size.setHeight(h <= 0 ? screenResolution.height() * Q_MM_PER_INCH / defaultPhysicalDpi : qreal(h)); if (w <= 0 || h <= 0) qWarning("Unable to query physical screen size, defaulting to %d dpi.\n" "To override, set QT_QPA_EGLFS_PHYSICAL_WIDTH " "and QT_QPA_EGLFS_PHYSICAL_HEIGHT (in millimeters).", defaultPhysicalDpi); } return size; } QSize q_screenSizeFromFb(int framebufferDevice) { #ifndef Q_OS_LINUX Q_UNUSED(framebufferDevice) #endif const int defaultWidth = 800; const int defaultHeight = 600; static QSize size; if (size.isEmpty()) { int width = qEnvironmentVariableIntValue("QT_QPA_EGLFS_WIDTH"); int height = qEnvironmentVariableIntValue("QT_QPA_EGLFS_HEIGHT"); if (width && height) { size.setWidth(width); size.setHeight(height); return size; } #ifdef Q_OS_LINUX struct fb_var_screeninfo vinfo; int xres = -1; int yres = -1; if (framebufferDevice != -1) { if (ioctl(framebufferDevice, FBIOGET_VSCREENINFO, &vinfo) == -1) { qWarning("eglconvenience: Could not read screen info"); } else { xres = vinfo.xres; yres = vinfo.yres; } } size.setWidth(xres <= 0 ? defaultWidth : xres); size.setHeight(yres <= 0 ? defaultHeight : yres); #else size.setWidth(defaultWidth); size.setHeight(defaultHeight); #endif } return size; } int q_screenDepthFromFb(int framebufferDevice) { #ifndef Q_OS_LINUX Q_UNUSED(framebufferDevice) #endif const int defaultDepth = 32; static int depth = qEnvironmentVariableIntValue("QT_QPA_EGLFS_DEPTH"); if (depth == 0) { #ifdef Q_OS_LINUX struct fb_var_screeninfo vinfo; if (framebufferDevice != -1) { if (ioctl(framebufferDevice, FBIOGET_VSCREENINFO, &vinfo) == -1) qWarning("eglconvenience: Could not query screen info"); else depth = vinfo.bits_per_pixel; } if (depth <= 0) depth = defaultDepth; #else depth = defaultDepth; #endif } return depth; } qreal q_refreshRateFromFb(int framebufferDevice) { #ifndef Q_OS_LINUX Q_UNUSED(framebufferDevice) #endif static qreal rate = 0; #ifdef Q_OS_LINUX if (rate == 0) { if (framebufferDevice != -1) { struct fb_var_screeninfo vinfo; if (ioctl(framebufferDevice, FBIOGET_VSCREENINFO, &vinfo) != -1) { const quint64 quot = quint64(vinfo.left_margin + vinfo.right_margin + vinfo.xres + vinfo.hsync_len) * quint64(vinfo.upper_margin + vinfo.lower_margin + vinfo.yres + vinfo.vsync_len) * vinfo.pixclock; if (quot) rate = 1000000000000LLU / quot; } else { qWarning("eglconvenience: Could not query screen info"); } } } #endif if (rate == 0) rate = 60; return rate; } #endif // Q_OS_UNIX QT_END_NAMESPACE