2011-08-22 08:49:28 +00:00
|
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
2016-01-15 07:08:27 +00:00
|
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
2011-08-22 08:49:28 +00:00
|
|
|
|
**
|
2019-12-11 13:50:40 +00:00
|
|
|
|
** This file is part of the QtOpenGL module of the Qt Toolkit.
|
2011-08-22 08:49:28 +00:00
|
|
|
|
**
|
2016-01-15 07:08:27 +00:00
|
|
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
2012-09-19 12:28:29 +00:00
|
|
|
|
** 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
|
2015-01-28 08:44:43 +00:00
|
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
2016-01-15 07:08:27 +00:00
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
2012-09-19 12:28:29 +00:00
|
|
|
|
**
|
2011-08-22 08:49:28 +00:00
|
|
|
|
** GNU Lesser General Public License Usage
|
2012-09-19 12:28:29 +00:00
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
2016-01-15 07:08:27 +00:00
|
|
|
|
** 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.
|
2012-09-19 12:28:29 +00:00
|
|
|
|
**
|
2016-01-15 07:08:27 +00:00
|
|
|
|
** 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.
|
2011-08-22 08:49:28 +00:00
|
|
|
|
**
|
|
|
|
|
** $QT_END_LICENSE$
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
When the active program changes, we need to update it's uniforms.
|
|
|
|
|
We could track state for each program and only update stale uniforms
|
|
|
|
|
- Could lead to lots of overhead if there's a lot of programs
|
|
|
|
|
We could update all the uniforms when the program changes
|
|
|
|
|
- Could end up updating lots of uniforms which don't need updating
|
|
|
|
|
|
|
|
|
|
Updating uniforms should be cheap, so the overhead of updating up-to-date
|
|
|
|
|
uniforms should be minimal. It's also less complex.
|
|
|
|
|
|
|
|
|
|
Things which _may_ cause a different program to be used:
|
|
|
|
|
- Change in brush/pen style
|
|
|
|
|
- Change in painter opacity
|
|
|
|
|
- Change in composition mode
|
|
|
|
|
|
|
|
|
|
Whenever we set a mode on the shader manager - it needs to tell us if it had
|
|
|
|
|
to switch to a different program.
|
|
|
|
|
|
|
|
|
|
The shader manager should only switch when we tell it to. E.g. if we set a new
|
|
|
|
|
brush style and then switch to transparent painter, we only want it to compile
|
|
|
|
|
and use the correct program when we really need it.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// #define QT_OPENGL_CACHE_AS_VBOS
|
|
|
|
|
|
2019-12-11 13:50:40 +00:00
|
|
|
|
#include <private/qopenglgradientcache_p.h>
|
|
|
|
|
#include <private/qopengltexturecache_p.h>
|
2011-08-30 08:36:25 +00:00
|
|
|
|
#include "qopenglpaintengine_p.h"
|
2014-10-14 10:07:41 +00:00
|
|
|
|
#include "qopenglpaintdevice_p.h"
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
#include <string.h> //for memcpy
|
|
|
|
|
#include <qmath.h>
|
|
|
|
|
|
|
|
|
|
#include <private/qopengl_p.h>
|
|
|
|
|
#include <private/qopenglcontext_p.h>
|
|
|
|
|
#include <private/qopenglextensions_p.h>
|
|
|
|
|
#include <private/qpaintengineex_p.h>
|
|
|
|
|
#include <QPaintEngine>
|
|
|
|
|
#include <private/qpainter_p.h>
|
|
|
|
|
#include <private/qfontengine_p.h>
|
|
|
|
|
#include <private/qdatabuffer_p.h>
|
|
|
|
|
#include <private/qstatictext_p.h>
|
|
|
|
|
#include <private/qtriangulator_p.h>
|
|
|
|
|
|
2019-12-11 13:50:40 +00:00
|
|
|
|
#include <private/qopenglengineshadermanager_p.h>
|
|
|
|
|
#include <private/qopengl2pexvertexarray_p.h>
|
|
|
|
|
#include <private/qopengltextureglyphcache_p.h>
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
|
2014-06-13 16:48:49 +00:00
|
|
|
|
#include <qtopengl_tracepoints_p.h>
|
|
|
|
|
|
2018-02-19 11:33:57 +00:00
|
|
|
|
#ifndef GL_KHR_blend_equation_advanced
|
|
|
|
|
#define GL_KHR_blend_equation_advanced 1
|
|
|
|
|
#define GL_MULTIPLY_KHR 0x9294
|
|
|
|
|
#define GL_SCREEN_KHR 0x9295
|
|
|
|
|
#define GL_OVERLAY_KHR 0x9296
|
|
|
|
|
#define GL_DARKEN_KHR 0x9297
|
|
|
|
|
#define GL_LIGHTEN_KHR 0x9298
|
|
|
|
|
#define GL_COLORDODGE_KHR 0x9299
|
|
|
|
|
#define GL_COLORBURN_KHR 0x929A
|
|
|
|
|
#define GL_HARDLIGHT_KHR 0x929B
|
|
|
|
|
#define GL_SOFTLIGHT_KHR 0x929C
|
|
|
|
|
#define GL_DIFFERENCE_KHR 0x929E
|
|
|
|
|
#define GL_EXCLUSION_KHR 0x92A0
|
|
|
|
|
#endif /* GL_KHR_blend_equation_advanced */
|
|
|
|
|
|
|
|
|
|
#ifndef GL_KHR_blend_equation_advanced_coherent
|
|
|
|
|
#define GL_KHR_blend_equation_advanced_coherent 1
|
|
|
|
|
#define GL_BLEND_ADVANCED_COHERENT_KHR 0x9285
|
|
|
|
|
#endif /* GL_KHR_blend_equation_advanced_coherent */
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2018-02-19 11:33:57 +00:00
|
|
|
|
QT_BEGIN_NAMESPACE
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
|
2019-12-11 13:50:40 +00:00
|
|
|
|
Q_OPENGL_EXPORT QImage qt_imageForBrush(int brushStyle, bool invert);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
////////////////////////////////// Private Methods //////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
QOpenGL2PaintEngineExPrivate::~QOpenGL2PaintEngineExPrivate()
|
|
|
|
|
{
|
|
|
|
|
delete shaderManager;
|
|
|
|
|
|
2016-07-27 13:45:31 +00:00
|
|
|
|
vertexBuffer.destroy();
|
|
|
|
|
texCoordBuffer.destroy();
|
|
|
|
|
opacityBuffer.destroy();
|
|
|
|
|
indexBuffer.destroy();
|
|
|
|
|
vao.destroy();
|
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
if (elementIndicesVBOId != 0) {
|
|
|
|
|
funcs.glDeleteBuffers(1, &elementIndicesVBOId);
|
|
|
|
|
elementIndicesVBOId = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline QColor qt_premultiplyColor(QColor c, GLfloat opacity)
|
|
|
|
|
{
|
|
|
|
|
qreal alpha = c.alphaF() * opacity;
|
|
|
|
|
c.setAlphaF(alpha);
|
|
|
|
|
c.setRedF(c.redF() * alpha);
|
|
|
|
|
c.setGreenF(c.greenF() * alpha);
|
|
|
|
|
c.setBlueF(c.blueF() * alpha);
|
|
|
|
|
return c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::setBrush(const QBrush& brush)
|
|
|
|
|
{
|
|
|
|
|
if (qbrush_fast_equals(currentBrush, brush))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const Qt::BrushStyle newStyle = qbrush_style(brush);
|
|
|
|
|
Q_ASSERT(newStyle != Qt::NoBrush);
|
|
|
|
|
|
|
|
|
|
currentBrush = brush;
|
2014-07-03 11:36:01 +00:00
|
|
|
|
if (!currentBrushImage.isNull())
|
|
|
|
|
currentBrushImage = QImage();
|
2011-08-22 08:49:28 +00:00
|
|
|
|
brushUniformsDirty = true; // All brushes have at least one uniform
|
|
|
|
|
|
|
|
|
|
if (newStyle > Qt::SolidPattern)
|
|
|
|
|
brushTextureDirty = true;
|
|
|
|
|
|
|
|
|
|
if (currentBrush.style() == Qt::TexturePattern
|
|
|
|
|
&& qHasPixmapTexture(brush) && brush.texture().isQBitmap())
|
|
|
|
|
{
|
|
|
|
|
shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::TextureSrcWithPattern);
|
|
|
|
|
} else {
|
|
|
|
|
shaderManager->setSrcPixelType(newStyle);
|
|
|
|
|
}
|
|
|
|
|
shaderManager->optimiseForBrushTransform(currentBrush.transform().type());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::useSimpleShader()
|
|
|
|
|
{
|
|
|
|
|
shaderManager->useSimpleProgram();
|
|
|
|
|
|
|
|
|
|
if (matrixDirty)
|
|
|
|
|
updateMatrix();
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-08 14:31:37 +00:00
|
|
|
|
/*
|
|
|
|
|
Single entry-point for activating, binding, and setting properties.
|
|
|
|
|
|
|
|
|
|
Allows keeping track of (caching) the latest texture unit and bound
|
|
|
|
|
texture in a central place, so that we can skip re-binding unless
|
|
|
|
|
needed.
|
|
|
|
|
|
|
|
|
|
\note Any code or Qt API that internally activates or binds will
|
|
|
|
|
not affect the cache used by this function, which means they will
|
|
|
|
|
lead to inconsisent state. QPainter::beginNativePainting() takes
|
|
|
|
|
care of resetting the cache, so for user–code this is fine, but
|
|
|
|
|
internally in the paint engine care must be taken to not call
|
|
|
|
|
functions that may activate or bind under our feet.
|
|
|
|
|
*/
|
2014-12-02 19:16:23 +00:00
|
|
|
|
template<typename T>
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::updateTexture(GLenum textureUnit, const T &texture, GLenum wrapMode, GLenum filterMode, TextureUpdateMode updateMode)
|
|
|
|
|
{
|
|
|
|
|
static const GLenum target = GL_TEXTURE_2D;
|
|
|
|
|
|
2014-12-08 14:31:37 +00:00
|
|
|
|
activateTextureUnit(textureUnit);
|
2014-12-02 19:16:23 +00:00
|
|
|
|
|
|
|
|
|
GLuint textureId = bindTexture(texture);
|
|
|
|
|
|
|
|
|
|
if (updateMode == UpdateIfNeeded && textureId == lastTextureUsed)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
lastTextureUsed = textureId;
|
|
|
|
|
|
|
|
|
|
funcs.glTexParameteri(target, GL_TEXTURE_WRAP_S, wrapMode);
|
|
|
|
|
funcs.glTexParameteri(target, GL_TEXTURE_WRAP_T, wrapMode);
|
|
|
|
|
|
|
|
|
|
funcs.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filterMode);
|
|
|
|
|
funcs.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filterMode);
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-08 14:31:37 +00:00
|
|
|
|
void QOpenGL2PaintEngineExPrivate::activateTextureUnit(GLenum textureUnit)
|
|
|
|
|
{
|
|
|
|
|
if (textureUnit != lastTextureUnitUsed) {
|
|
|
|
|
funcs.glActiveTexture(GL_TEXTURE0 + textureUnit);
|
|
|
|
|
lastTextureUnitUsed = textureUnit;
|
|
|
|
|
|
|
|
|
|
// We simplify things by keeping a single cached value of the last
|
|
|
|
|
// texture that was bound, instead of one per texture unit. This
|
|
|
|
|
// means that switching texture units could potentially mean we
|
|
|
|
|
// need a re-bind and corresponding parameter updates.
|
|
|
|
|
lastTextureUsed = GLuint(-1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-02 19:16:23 +00:00
|
|
|
|
template<>
|
|
|
|
|
GLuint QOpenGL2PaintEngineExPrivate::bindTexture(const GLuint &textureId)
|
|
|
|
|
{
|
|
|
|
|
if (textureId != lastTextureUsed)
|
|
|
|
|
funcs.glBindTexture(GL_TEXTURE_2D, textureId);
|
|
|
|
|
|
|
|
|
|
return textureId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<>
|
|
|
|
|
GLuint QOpenGL2PaintEngineExPrivate::bindTexture(const QImage &image)
|
|
|
|
|
{
|
|
|
|
|
return QOpenGLTextureCache::cacheForContext(ctx)->bindTexture(ctx, image);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<>
|
|
|
|
|
GLuint QOpenGL2PaintEngineExPrivate::bindTexture(const QPixmap &pixmap)
|
|
|
|
|
{
|
|
|
|
|
return QOpenGLTextureCache::cacheForContext(ctx)->bindTexture(ctx, pixmap);
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-08 15:41:33 +00:00
|
|
|
|
template<>
|
|
|
|
|
GLuint QOpenGL2PaintEngineExPrivate::bindTexture(const QGradient &gradient)
|
|
|
|
|
{
|
|
|
|
|
// We apply global opacity in the fragment shaders, so we always pass 1.0
|
|
|
|
|
// for opacity to the cache.
|
|
|
|
|
GLuint textureId = QOpenGL2GradientCache::cacheForContext(ctx)->getBuffer(gradient, 1.0);
|
|
|
|
|
|
|
|
|
|
// QOpenGL2GradientCache::getBuffer() may bind and generate a new texture if it
|
|
|
|
|
// hasn't been cached yet, but will otherwise return an unbound texture id. To
|
|
|
|
|
// be sure that the texture is bound, we unfortunately have to bind again,
|
|
|
|
|
// which results in the initial generation of the texture doing two binds.
|
|
|
|
|
return bindTexture(textureId);
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-02 19:16:23 +00:00
|
|
|
|
struct ImageWithBindOptions
|
|
|
|
|
{
|
|
|
|
|
const QImage ℑ
|
2018-07-18 12:49:15 +00:00
|
|
|
|
QOpenGLTextureUploader::BindOptions options;
|
2014-12-02 19:16:23 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template<>
|
|
|
|
|
GLuint QOpenGL2PaintEngineExPrivate::bindTexture(const ImageWithBindOptions &imageWithOptions)
|
|
|
|
|
{
|
|
|
|
|
return QOpenGLTextureCache::cacheForContext(ctx)->bindTexture(ctx, imageWithOptions.image, imageWithOptions.options);
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-18 14:42:11 +00:00
|
|
|
|
inline static bool isPowerOfTwo(int x)
|
|
|
|
|
{
|
|
|
|
|
// Assumption: x >= 1
|
|
|
|
|
return x == (x & -x);
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
void QOpenGL2PaintEngineExPrivate::updateBrushTexture()
|
|
|
|
|
{
|
|
|
|
|
Q_Q(QOpenGL2PaintEngineEx);
|
|
|
|
|
// qDebug("QOpenGL2PaintEngineExPrivate::updateBrushTexture()");
|
|
|
|
|
Qt::BrushStyle style = currentBrush.style();
|
|
|
|
|
|
2014-12-02 19:16:23 +00:00
|
|
|
|
bool smoothPixmapTransform = q->state()->renderHints & QPainter::SmoothPixmapTransform;
|
|
|
|
|
GLenum filterMode = smoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
|
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
if ( (style >= Qt::Dense1Pattern) && (style <= Qt::DiagCrossPattern) ) {
|
|
|
|
|
// Get the image data for the pattern
|
2014-12-02 19:16:23 +00:00
|
|
|
|
QImage textureImage = qt_imageForBrush(style, false);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-12-02 19:16:23 +00:00
|
|
|
|
updateTexture(QT_BRUSH_TEXTURE_UNIT, textureImage, GL_REPEAT, filterMode, ForceUpdate);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
else if (style >= Qt::LinearGradientPattern && style <= Qt::ConicalGradientPattern) {
|
|
|
|
|
// Gradiant brush: All the gradiants use the same texture
|
|
|
|
|
|
2014-12-08 15:41:33 +00:00
|
|
|
|
const QGradient *gradient = currentBrush.gradient();
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-12-02 17:31:38 +00:00
|
|
|
|
GLenum wrapMode = GL_CLAMP_TO_EDGE;
|
2014-12-08 15:41:33 +00:00
|
|
|
|
if (gradient->spread() == QGradient::RepeatSpread || gradient->type() == QGradient::ConicalGradient)
|
2014-12-02 17:31:38 +00:00
|
|
|
|
wrapMode = GL_REPEAT;
|
2014-12-08 15:41:33 +00:00
|
|
|
|
else if (gradient->spread() == QGradient::ReflectSpread)
|
2014-12-02 17:31:38 +00:00
|
|
|
|
wrapMode = GL_MIRRORED_REPEAT;
|
|
|
|
|
|
2014-12-08 15:41:33 +00:00
|
|
|
|
updateTexture(QT_BRUSH_TEXTURE_UNIT, *gradient, wrapMode, filterMode, ForceUpdate);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
else if (style == Qt::TexturePattern) {
|
2014-07-03 11:36:01 +00:00
|
|
|
|
currentBrushImage = currentBrush.textureImage();
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
int max_texture_size = ctx->d_func()->maxTextureSize();
|
2018-07-18 14:42:11 +00:00
|
|
|
|
QSize newSize = currentBrushImage.size();
|
|
|
|
|
newSize = newSize.boundedTo(QSize(max_texture_size, max_texture_size));
|
|
|
|
|
if (!QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(QOpenGLFunctions::NPOTTextureRepeat)) {
|
|
|
|
|
if (!isPowerOfTwo(newSize.width()) || !isPowerOfTwo(newSize.height())) {
|
|
|
|
|
newSize.setHeight(qNextPowerOfTwo(newSize.height() - 1));
|
|
|
|
|
newSize.setWidth(qNextPowerOfTwo(newSize.width() - 1));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (currentBrushImage.size() != newSize)
|
|
|
|
|
currentBrushImage = currentBrushImage.scaled(newSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2013-05-02 10:27:19 +00:00
|
|
|
|
GLuint wrapMode = GL_REPEAT;
|
Dynamic GL switch on Windows
The patch introduces a new build configuration on Windows which
can be requested by passing -opengl dynamic to configure.
Platforms other than Windows (including WinRT) are not affected.
The existing Angle and desktop configurations are not affected.
These continue to function as before and Angle remains the default.
In the future, when all modules have added support for the dynamic
path, as described below, the default configuration could be changed
to be the dynamic one. This would allow providing a single set of
binaries in the official builds instead of the current two.
When requesting dynamic GL, Angle is built but QT_OPENGL_ES[_2] are
never defined. Instead, the code path that has traditionally been
desktop GL only becomes the dynamic path that has to do runtime
checks. Qt modules and applications are not linked to opengl32.dll or
libegl/glesv2.dll in this case. Instead, QtGui exports all necessary
egl/egl/gl functions which will, under the hood, forward all requests
to a dynamically loaded EGL/WGL/GL implementation.
Porting guide (better said, changes needed to prepare your code to
work with dynamic GL builds when the fallback to Angle is utilized):
1. In !QT_OPENGL_ES[_2] code branches use QOpenGLFunctions::isES() to
differentiate between desktop and ES where needed. Keep in mind that
it is the desktop GL header (plus qopenglext.h) that is included,
not the GLES one.
QtGui's proxy will handle some differences, for example calling
glClearDepth will route to glClearDepthf when needed. The built-in
eglGetProcAddress is able to retrieve pointers for standard GLES2
functions too so code resolving OpenGL 2 functions will function
in any case.
2. QT_CONFIG will contain "opengl" and "dynamicgl" in dynamic builds,
but never "angle" or "opengles2".
3. The preprocessor define QT_OPENGL_DYNAMIC is also available in
dynamic builds. The usage of this is strongly discouraged and should
not be needed anywhere except for QtGui and the platform plugin.
4. Code in need of the library handle can use
QOpenGLFunctions::platformGLHandle().
The decision on which library to load is currently based on a simple
test that creates a dummy window/context and tries to resolve an
OpenGL 2 function. If this fails, it goes for Angle. This seems to work
well on Win7 PCs for example that do not have proper graphics drivers
providing OpenGL installed but are D3D9 capable using the default drivers.
Setting QT_OPENGL to desktop or angle skips the test and forces
usage of the given GL. There are also two new application attributes
that could be used for the same purpose.
If Angle is requested but the libraries are not present, desktop is
tried. If desktop is requested, or if angle is requested but nothing
works, the EGL/WGL functions will still be callable but will return 0.
This conveniently means that eglInitialize() and such will report a failure.
Debug messages can be enabled by setting QT_OPENGLPROXY_DEBUG. This will
tell which implementation is chosen.
The textures example application is ported to OpenGL 2, the GL 1
code path is removed.
[ChangeLog][QtGui] Qt builds on Windows can now be configured for
dynamic loading of the OpenGL implementation. This can be requested
by passing -opengl dynamic to configure. In this mode no modules will
link to opengl32.dll or Angle's libegl/libglesv2. Instead, QtGui will
dynamically choose between desktop and Angle during the first GL/EGL/WGL
call. This allows deploying applications with a single set of Qt libraries
with the ability of transparently falling back to Angle in case the
opengl32.dll is not suitable, due to missing graphics drivers for example.
Task-number: QTBUG-36483
Change-Id: I716fdebbf60b355b7d9ef57d1e069eef366b4ab9
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
Reviewed-by: Jørgen Lind <jorgen.lind@digia.com>
2014-01-27 13:45:11 +00:00
|
|
|
|
|
2014-12-02 19:16:23 +00:00
|
|
|
|
updateTexture(QT_BRUSH_TEXTURE_UNIT, currentBrushImage, wrapMode, filterMode, ForceUpdate);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
brushTextureDirty = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::updateBrushUniforms()
|
|
|
|
|
{
|
|
|
|
|
// qDebug("QOpenGL2PaintEngineExPrivate::updateBrushUniforms()");
|
|
|
|
|
Qt::BrushStyle style = currentBrush.style();
|
|
|
|
|
|
|
|
|
|
if (style == Qt::NoBrush)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
QTransform brushQTransform = currentBrush.transform();
|
|
|
|
|
|
|
|
|
|
if (style == Qt::SolidPattern) {
|
|
|
|
|
QColor col = qt_premultiplyColor(currentBrush.color(), (GLfloat)q->state()->opacity);
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::FragmentColor), col);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// All other brushes have a transform and thus need the translation point:
|
|
|
|
|
QPointF translationPoint;
|
|
|
|
|
|
|
|
|
|
if (style <= Qt::DiagCrossPattern) {
|
|
|
|
|
QColor col = qt_premultiplyColor(currentBrush.color(), (GLfloat)q->state()->opacity);
|
|
|
|
|
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::PatternColor), col);
|
|
|
|
|
|
|
|
|
|
QVector2D halfViewportSize(width*0.5, height*0.5);
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::HalfViewportSize), halfViewportSize);
|
|
|
|
|
}
|
|
|
|
|
else if (style == Qt::LinearGradientPattern) {
|
|
|
|
|
const QLinearGradient *g = static_cast<const QLinearGradient *>(currentBrush.gradient());
|
|
|
|
|
|
|
|
|
|
QPointF realStart = g->start();
|
|
|
|
|
QPointF realFinal = g->finalStop();
|
|
|
|
|
translationPoint = realStart;
|
|
|
|
|
|
|
|
|
|
QPointF l = realFinal - realStart;
|
|
|
|
|
|
|
|
|
|
QVector3D linearData(
|
|
|
|
|
l.x(),
|
|
|
|
|
l.y(),
|
|
|
|
|
1.0f / (l.x() * l.x() + l.y() * l.y())
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::LinearData), linearData);
|
|
|
|
|
|
|
|
|
|
QVector2D halfViewportSize(width*0.5, height*0.5);
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::HalfViewportSize), halfViewportSize);
|
|
|
|
|
}
|
|
|
|
|
else if (style == Qt::ConicalGradientPattern) {
|
|
|
|
|
const QConicalGradient *g = static_cast<const QConicalGradient *>(currentBrush.gradient());
|
|
|
|
|
translationPoint = g->center();
|
|
|
|
|
|
2015-02-26 12:53:00 +00:00
|
|
|
|
GLfloat angle = -qDegreesToRadians(g->angle());
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::Angle), angle);
|
|
|
|
|
|
|
|
|
|
QVector2D halfViewportSize(width*0.5, height*0.5);
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::HalfViewportSize), halfViewportSize);
|
|
|
|
|
}
|
|
|
|
|
else if (style == Qt::RadialGradientPattern) {
|
|
|
|
|
const QRadialGradient *g = static_cast<const QRadialGradient *>(currentBrush.gradient());
|
|
|
|
|
QPointF realCenter = g->center();
|
|
|
|
|
QPointF realFocal = g->focalPoint();
|
|
|
|
|
qreal realRadius = g->centerRadius() - g->focalRadius();
|
|
|
|
|
translationPoint = realFocal;
|
|
|
|
|
|
|
|
|
|
QPointF fmp = realCenter - realFocal;
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::Fmp), fmp);
|
|
|
|
|
|
|
|
|
|
GLfloat fmp2_m_radius2 = -fmp.x() * fmp.x() - fmp.y() * fmp.y() + realRadius*realRadius;
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::Fmp2MRadius2), fmp2_m_radius2);
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::Inverse2Fmp2MRadius2),
|
|
|
|
|
GLfloat(1.0 / (2.0*fmp2_m_radius2)));
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::SqrFr),
|
|
|
|
|
GLfloat(g->focalRadius() * g->focalRadius()));
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::BRadius),
|
|
|
|
|
GLfloat(2 * (g->centerRadius() - g->focalRadius()) * g->focalRadius()),
|
|
|
|
|
g->focalRadius(),
|
|
|
|
|
g->centerRadius() - g->focalRadius());
|
|
|
|
|
|
|
|
|
|
QVector2D halfViewportSize(width*0.5, height*0.5);
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::HalfViewportSize), halfViewportSize);
|
|
|
|
|
}
|
|
|
|
|
else if (style == Qt::TexturePattern) {
|
|
|
|
|
const QPixmap& texPixmap = currentBrush.texture();
|
|
|
|
|
|
|
|
|
|
if (qHasPixmapTexture(currentBrush) && currentBrush.texture().isQBitmap()) {
|
|
|
|
|
QColor col = qt_premultiplyColor(currentBrush.color(), (GLfloat)q->state()->opacity);
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::PatternColor), col);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QSizeF invertedTextureSize(1.0 / texPixmap.width(), 1.0 / texPixmap.height());
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::InvertedTextureSize), invertedTextureSize);
|
|
|
|
|
|
|
|
|
|
QVector2D halfViewportSize(width*0.5, height*0.5);
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::HalfViewportSize), halfViewportSize);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
qWarning("QOpenGL2PaintEngineEx: Unimplemented fill style");
|
|
|
|
|
|
|
|
|
|
const QPointF &brushOrigin = q->state()->brushOrigin;
|
|
|
|
|
QTransform matrix = q->state()->matrix;
|
|
|
|
|
matrix.translate(brushOrigin.x(), brushOrigin.y());
|
|
|
|
|
|
|
|
|
|
QTransform translate(1, 0, 0, 1, -translationPoint.x(), -translationPoint.y());
|
|
|
|
|
qreal m22 = -1;
|
|
|
|
|
qreal dy = height;
|
2011-09-07 08:31:03 +00:00
|
|
|
|
if (device->paintFlipped()) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
m22 = 1;
|
|
|
|
|
dy = 0;
|
|
|
|
|
}
|
|
|
|
|
QTransform gl_to_qt(1, 0, 0, m22, 0, dy);
|
2016-01-18 10:57:05 +00:00
|
|
|
|
QTransform inv_matrix = gl_to_qt * (brushQTransform * matrix).inverted() * translate;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::BrushTransform), inv_matrix);
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::BrushTexture), QT_BRUSH_TEXTURE_UNIT);
|
|
|
|
|
}
|
|
|
|
|
brushUniformsDirty = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This assumes the shader manager has already setup the correct shader program
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::updateMatrix()
|
|
|
|
|
{
|
|
|
|
|
// qDebug("QOpenGL2PaintEngineExPrivate::updateMatrix()");
|
|
|
|
|
|
|
|
|
|
const QTransform& transform = q->state()->matrix;
|
|
|
|
|
|
|
|
|
|
// The projection matrix converts from Qt's coordinate system to GL's coordinate system
|
|
|
|
|
// * GL's viewport is 2x2, Qt's is width x height
|
|
|
|
|
// * GL has +y -> -y going from bottom -> top, Qt is the other way round
|
|
|
|
|
// * GL has [0,0] in the center, Qt has it in the top-left
|
|
|
|
|
//
|
|
|
|
|
// This results in the Projection matrix below, which is multiplied by the painter's
|
|
|
|
|
// transformation matrix, as shown below:
|
|
|
|
|
//
|
|
|
|
|
// Projection Matrix Painter Transform
|
|
|
|
|
// ------------------------------------------------ ------------------------
|
|
|
|
|
// | 2.0 / width | 0.0 | -1.0 | | m11 | m21 | dx |
|
|
|
|
|
// | 0.0 | -2.0 / height | 1.0 | * | m12 | m22 | dy |
|
|
|
|
|
// | 0.0 | 0.0 | 1.0 | | m13 | m23 | m33 |
|
|
|
|
|
// ------------------------------------------------ ------------------------
|
|
|
|
|
//
|
|
|
|
|
// NOTE: The resultant matrix is also transposed, as GL expects column-major matracies
|
|
|
|
|
|
|
|
|
|
const GLfloat wfactor = 2.0f / width;
|
|
|
|
|
GLfloat hfactor = -2.0f / height;
|
|
|
|
|
|
|
|
|
|
GLfloat dx = transform.dx();
|
|
|
|
|
GLfloat dy = transform.dy();
|
|
|
|
|
|
2011-09-07 08:31:03 +00:00
|
|
|
|
if (device->paintFlipped()) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
hfactor *= -1;
|
|
|
|
|
dy -= height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Non-integer translates can have strange effects for some rendering operations such as
|
|
|
|
|
// anti-aliased text rendering. In such cases, we snap the translate to the pixel grid.
|
|
|
|
|
if (snapToPixelGrid && transform.type() == QTransform::TxTranslate) {
|
|
|
|
|
// 0.50 needs to rounded down to 0.0 for consistency with raster engine:
|
2015-01-14 10:17:47 +00:00
|
|
|
|
dx = std::ceil(dx - 0.5f);
|
|
|
|
|
dy = std::ceil(dy - 0.5f);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
pmvMatrix[0][0] = (wfactor * transform.m11()) - transform.m13();
|
|
|
|
|
pmvMatrix[1][0] = (wfactor * transform.m21()) - transform.m23();
|
|
|
|
|
pmvMatrix[2][0] = (wfactor * dx) - transform.m33();
|
|
|
|
|
pmvMatrix[0][1] = (hfactor * transform.m12()) + transform.m13();
|
|
|
|
|
pmvMatrix[1][1] = (hfactor * transform.m22()) + transform.m23();
|
|
|
|
|
pmvMatrix[2][1] = (hfactor * dy) + transform.m33();
|
|
|
|
|
pmvMatrix[0][2] = transform.m13();
|
|
|
|
|
pmvMatrix[1][2] = transform.m23();
|
|
|
|
|
pmvMatrix[2][2] = transform.m33();
|
|
|
|
|
|
|
|
|
|
// 1/10000 == 0.0001, so we have good enough res to cover curves
|
|
|
|
|
// that span the entire widget...
|
|
|
|
|
inverseScale = qMax(1 / qMax( qMax(qAbs(transform.m11()), qAbs(transform.m22())),
|
|
|
|
|
qMax(qAbs(transform.m12()), qAbs(transform.m21())) ),
|
|
|
|
|
qreal(0.0001));
|
|
|
|
|
|
|
|
|
|
matrixDirty = false;
|
|
|
|
|
matrixUniformDirty = true;
|
|
|
|
|
|
|
|
|
|
// Set the PMV matrix attribute. As we use an attributes rather than uniforms, we only
|
|
|
|
|
// need to do this once for every matrix change and persists across all shader programs.
|
|
|
|
|
funcs.glVertexAttrib3fv(QT_PMV_MATRIX_1_ATTR, pmvMatrix[0]);
|
|
|
|
|
funcs.glVertexAttrib3fv(QT_PMV_MATRIX_2_ATTR, pmvMatrix[1]);
|
|
|
|
|
funcs.glVertexAttrib3fv(QT_PMV_MATRIX_3_ATTR, pmvMatrix[2]);
|
|
|
|
|
|
|
|
|
|
dasher.setInvScale(inverseScale);
|
|
|
|
|
stroker.setInvScale(inverseScale);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::updateCompositionMode()
|
|
|
|
|
{
|
|
|
|
|
// NOTE: The entire paint engine works on pre-multiplied data - which is why some of these
|
|
|
|
|
// composition modes look odd.
|
|
|
|
|
// qDebug() << "QOpenGL2PaintEngineExPrivate::updateCompositionMode() - Setting GL composition mode for " << q->state()->composition_mode;
|
2018-02-19 11:33:57 +00:00
|
|
|
|
if (ctx->functions()->hasOpenGLFeature(QOpenGLFunctions::BlendEquationAdvanced)) {
|
|
|
|
|
if (q->state()->composition_mode <= QPainter::CompositionMode_Plus) {
|
|
|
|
|
funcs.glDisable(GL_BLEND_ADVANCED_COHERENT_KHR);
|
|
|
|
|
funcs.glBlendEquation(GL_FUNC_ADD);
|
|
|
|
|
} else {
|
|
|
|
|
funcs.glEnable(GL_BLEND_ADVANCED_COHERENT_KHR);
|
|
|
|
|
}
|
|
|
|
|
shaderManager->setCompositionMode(q->state()->composition_mode);
|
|
|
|
|
} else {
|
|
|
|
|
if (q->state()->composition_mode > QPainter::CompositionMode_Plus) {
|
|
|
|
|
qWarning("Unsupported composition mode");
|
|
|
|
|
compositionModeDirty = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-08-22 08:49:28 +00:00
|
|
|
|
switch(q->state()->composition_mode) {
|
|
|
|
|
case QPainter::CompositionMode_SourceOver:
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_DestinationOver:
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_Clear:
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBlendFunc(GL_ZERO, GL_ZERO);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_Source:
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBlendFunc(GL_ONE, GL_ZERO);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_Destination:
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBlendFunc(GL_ZERO, GL_ONE);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_SourceIn:
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBlendFunc(GL_DST_ALPHA, GL_ZERO);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_DestinationIn:
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_SourceOut:
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ZERO);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_DestinationOut:
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_SourceAtop:
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_DestinationAtop:
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_Xor:
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_Plus:
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBlendFunc(GL_ONE, GL_ONE);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
break;
|
2018-02-19 11:33:57 +00:00
|
|
|
|
case QPainter::CompositionMode_Multiply:
|
|
|
|
|
funcs.glBlendEquation(GL_MULTIPLY_KHR);
|
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_Screen:
|
|
|
|
|
funcs.glBlendEquation(GL_SCREEN_KHR);
|
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_Overlay:
|
|
|
|
|
funcs.glBlendEquation(GL_OVERLAY_KHR);
|
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_Darken:
|
|
|
|
|
funcs.glBlendEquation(GL_DARKEN_KHR);
|
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_Lighten:
|
|
|
|
|
funcs.glBlendEquation(GL_LIGHTEN_KHR);
|
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_ColorDodge:
|
|
|
|
|
funcs.glBlendEquation(GL_COLORDODGE_KHR);
|
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_ColorBurn:
|
|
|
|
|
funcs.glBlendEquation(GL_COLORBURN_KHR);
|
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_HardLight:
|
|
|
|
|
funcs.glBlendEquation(GL_HARDLIGHT_KHR);
|
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_SoftLight:
|
|
|
|
|
funcs.glBlendEquation(GL_SOFTLIGHT_KHR);
|
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_Difference:
|
|
|
|
|
funcs.glBlendEquation(GL_DIFFERENCE_KHR);
|
|
|
|
|
break;
|
|
|
|
|
case QPainter::CompositionMode_Exclusion:
|
|
|
|
|
funcs.glBlendEquation(GL_EXCLUSION_KHR);
|
|
|
|
|
break;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
default:
|
|
|
|
|
qWarning("Unsupported composition mode");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compositionModeDirty = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void setCoords(GLfloat *coords, const QOpenGLRect &rect)
|
|
|
|
|
{
|
|
|
|
|
coords[0] = rect.left;
|
|
|
|
|
coords[1] = rect.top;
|
|
|
|
|
coords[2] = rect.right;
|
|
|
|
|
coords[3] = rect.top;
|
|
|
|
|
coords[4] = rect.right;
|
|
|
|
|
coords[5] = rect.bottom;
|
|
|
|
|
coords[6] = rect.left;
|
|
|
|
|
coords[7] = rect.bottom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::drawTexture(const QOpenGLRect& dest, const QOpenGLRect& src, const QSize &textureSize, bool opaque, bool pattern)
|
|
|
|
|
{
|
2014-06-13 16:48:49 +00:00
|
|
|
|
Q_TRACE_SCOPE(QOpenGL2PaintEngineExPrivate_drawTexture, dest, src, textureSize, opaque, pattern);
|
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
// Setup for texture drawing
|
|
|
|
|
currentBrush = noBrush;
|
|
|
|
|
|
|
|
|
|
if (snapToPixelGrid) {
|
|
|
|
|
snapToPixelGrid = false;
|
|
|
|
|
matrixDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (prepareForDraw(opaque))
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::ImageTexture), QT_IMAGE_TEXTURE_UNIT);
|
|
|
|
|
|
|
|
|
|
if (pattern) {
|
|
|
|
|
QColor col = qt_premultiplyColor(q->state()->pen.color(), (GLfloat)q->state()->opacity);
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::PatternColor), col);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GLfloat dx = 1.0 / textureSize.width();
|
|
|
|
|
GLfloat dy = 1.0 / textureSize.height();
|
|
|
|
|
|
|
|
|
|
QOpenGLRect srcTextureRect(src.left*dx, src.top*dy, src.right*dx, src.bottom*dy);
|
|
|
|
|
|
|
|
|
|
setCoords(staticVertexCoordinateArray, dest);
|
|
|
|
|
setCoords(staticTextureCoordinateArray, srcTextureRect);
|
|
|
|
|
|
2016-07-27 13:45:31 +00:00
|
|
|
|
setVertexAttribArrayEnabled(QT_VERTEX_COORDS_ATTR, true);
|
|
|
|
|
setVertexAttribArrayEnabled(QT_TEXTURE_COORDS_ATTR, true);
|
|
|
|
|
|
|
|
|
|
uploadData(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray, 8);
|
|
|
|
|
uploadData(QT_TEXTURE_COORDS_ATTR, staticTextureCoordinateArray, 8);
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::beginNativePainting()
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
ensureActive();
|
|
|
|
|
d->transferMode(BrushDrawingMode);
|
|
|
|
|
|
|
|
|
|
d->nativePaintingActive = true;
|
|
|
|
|
|
|
|
|
|
d->funcs.glUseProgram(0);
|
|
|
|
|
|
|
|
|
|
// Disable all the vertex attribute arrays:
|
|
|
|
|
for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i)
|
|
|
|
|
d->funcs.glDisableVertexAttribArray(i);
|
|
|
|
|
|
2020-04-14 11:48:28 +00:00
|
|
|
|
#if !QT_CONFIG(opengles2) && !defined(QT_OPENGL_DYNAMIC)
|
2014-03-04 14:06:36 +00:00
|
|
|
|
Q_ASSERT(QOpenGLContext::currentContext());
|
|
|
|
|
const QOpenGLContext *ctx = d->ctx;
|
|
|
|
|
const QSurfaceFormat &fmt = d->device->context()->format();
|
|
|
|
|
if (fmt.majorVersion() < 3 || (fmt.majorVersion() == 3 && fmt.minorVersion() < 1)
|
|
|
|
|
|| (fmt.majorVersion() == 3 && fmt.minorVersion() == 1 && ctx->hasExtension(QByteArrayLiteral("GL_ARB_compatibility")))
|
|
|
|
|
|| fmt.profile() == QSurfaceFormat::CompatibilityProfile)
|
|
|
|
|
{
|
|
|
|
|
// be nice to people who mix OpenGL 1.x code with QPainter commands
|
|
|
|
|
// by setting modelview and projection matrices to mirror the GL 1
|
|
|
|
|
// paint engine
|
|
|
|
|
const QTransform& mtx = state()->matrix;
|
|
|
|
|
|
|
|
|
|
float mv_matrix[4][4] =
|
|
|
|
|
{
|
|
|
|
|
{ float(mtx.m11()), float(mtx.m12()), 0, float(mtx.m13()) },
|
|
|
|
|
{ float(mtx.m21()), float(mtx.m22()), 0, float(mtx.m23()) },
|
|
|
|
|
{ 0, 0, 1, 0 },
|
|
|
|
|
{ float(mtx.dx()), float(mtx.dy()), 0, float(mtx.m33()) }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const QSize sz = d->device->size();
|
|
|
|
|
|
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
|
|
|
glLoadIdentity();
|
|
|
|
|
glOrtho(0, sz.width(), sz.height(), 0, -999999, 999999);
|
|
|
|
|
|
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
|
|
|
glLoadMatrixf(&mv_matrix[0][0]);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
2020-04-14 11:48:28 +00:00
|
|
|
|
#endif // !QT_CONFIG(opengles2)
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-12-08 14:31:37 +00:00
|
|
|
|
d->resetGLState();
|
|
|
|
|
|
|
|
|
|
// We don't know what texture units and textures the native painting
|
|
|
|
|
// will activate and bind, so we can't assume anything when we return
|
|
|
|
|
// from the native painting.
|
|
|
|
|
d->lastTextureUnitUsed = QT_UNKNOWN_TEXTURE_UNIT;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
d->lastTextureUsed = GLuint(-1);
|
2014-12-08 14:31:37 +00:00
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
d->dirtyStencilRegion = QRect(0, 0, d->width, d->height);
|
|
|
|
|
|
|
|
|
|
d->shaderManager->setDirty();
|
|
|
|
|
|
|
|
|
|
d->needsSync = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::resetGLState()
|
|
|
|
|
{
|
2014-12-08 14:31:37 +00:00
|
|
|
|
activateTextureUnit(QT_DEFAULT_TEXTURE_UNIT);
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDisable(GL_BLEND);
|
|
|
|
|
funcs.glDisable(GL_STENCIL_TEST);
|
|
|
|
|
funcs.glDisable(GL_DEPTH_TEST);
|
|
|
|
|
funcs.glDisable(GL_SCISSOR_TEST);
|
|
|
|
|
funcs.glDepthMask(true);
|
|
|
|
|
funcs.glDepthFunc(GL_LESS);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
funcs.glClearDepthf(1);
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilMask(0xff);
|
|
|
|
|
funcs.glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
|
|
|
|
funcs.glStencilFunc(GL_ALWAYS, 0, 0xff);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
setVertexAttribArrayEnabled(QT_TEXTURE_COORDS_ATTR, false);
|
|
|
|
|
setVertexAttribArrayEnabled(QT_VERTEX_COORDS_ATTR, false);
|
|
|
|
|
setVertexAttribArrayEnabled(QT_OPACITY_ATTR, false);
|
2014-04-24 15:11:23 +00:00
|
|
|
|
if (!QOpenGLContext::currentContext()->isOpenGLES()) {
|
Dynamic GL switch on Windows
The patch introduces a new build configuration on Windows which
can be requested by passing -opengl dynamic to configure.
Platforms other than Windows (including WinRT) are not affected.
The existing Angle and desktop configurations are not affected.
These continue to function as before and Angle remains the default.
In the future, when all modules have added support for the dynamic
path, as described below, the default configuration could be changed
to be the dynamic one. This would allow providing a single set of
binaries in the official builds instead of the current two.
When requesting dynamic GL, Angle is built but QT_OPENGL_ES[_2] are
never defined. Instead, the code path that has traditionally been
desktop GL only becomes the dynamic path that has to do runtime
checks. Qt modules and applications are not linked to opengl32.dll or
libegl/glesv2.dll in this case. Instead, QtGui exports all necessary
egl/egl/gl functions which will, under the hood, forward all requests
to a dynamically loaded EGL/WGL/GL implementation.
Porting guide (better said, changes needed to prepare your code to
work with dynamic GL builds when the fallback to Angle is utilized):
1. In !QT_OPENGL_ES[_2] code branches use QOpenGLFunctions::isES() to
differentiate between desktop and ES where needed. Keep in mind that
it is the desktop GL header (plus qopenglext.h) that is included,
not the GLES one.
QtGui's proxy will handle some differences, for example calling
glClearDepth will route to glClearDepthf when needed. The built-in
eglGetProcAddress is able to retrieve pointers for standard GLES2
functions too so code resolving OpenGL 2 functions will function
in any case.
2. QT_CONFIG will contain "opengl" and "dynamicgl" in dynamic builds,
but never "angle" or "opengles2".
3. The preprocessor define QT_OPENGL_DYNAMIC is also available in
dynamic builds. The usage of this is strongly discouraged and should
not be needed anywhere except for QtGui and the platform plugin.
4. Code in need of the library handle can use
QOpenGLFunctions::platformGLHandle().
The decision on which library to load is currently based on a simple
test that creates a dummy window/context and tries to resolve an
OpenGL 2 function. If this fails, it goes for Angle. This seems to work
well on Win7 PCs for example that do not have proper graphics drivers
providing OpenGL installed but are D3D9 capable using the default drivers.
Setting QT_OPENGL to desktop or angle skips the test and forces
usage of the given GL. There are also two new application attributes
that could be used for the same purpose.
If Angle is requested but the libraries are not present, desktop is
tried. If desktop is requested, or if angle is requested but nothing
works, the EGL/WGL functions will still be callable but will return 0.
This conveniently means that eglInitialize() and such will report a failure.
Debug messages can be enabled by setting QT_OPENGLPROXY_DEBUG. This will
tell which implementation is chosen.
The textures example application is ported to OpenGL 2, the GL 1
code path is removed.
[ChangeLog][QtGui] Qt builds on Windows can now be configured for
dynamic loading of the OpenGL implementation. This can be requested
by passing -opengl dynamic to configure. In this mode no modules will
link to opengl32.dll or Angle's libegl/libglesv2. Instead, QtGui will
dynamically choose between desktop and Angle during the first GL/EGL/WGL
call. This allows deploying applications with a single set of Qt libraries
with the ability of transparently falling back to Angle in case the
opengl32.dll is not suitable, due to missing graphics drivers for example.
Task-number: QTBUG-36483
Change-Id: I716fdebbf60b355b7d9ef57d1e069eef366b4ab9
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
Reviewed-by: Jørgen Lind <jorgen.lind@digia.com>
2014-01-27 13:45:11 +00:00
|
|
|
|
// gl_Color, corresponding to vertex attribute 3, may have been changed
|
|
|
|
|
float color[] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
|
|
|
|
funcs.glVertexAttrib4fv(3, color);
|
|
|
|
|
}
|
2016-07-27 13:45:31 +00:00
|
|
|
|
if (vao.isCreated()) {
|
|
|
|
|
vao.release();
|
|
|
|
|
funcs.glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
|
funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
|
|
|
}
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::endNativePainting()
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
d->needsSync = true;
|
|
|
|
|
d->nativePaintingActive = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::invalidateState()
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
d->needsSync = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QOpenGL2PaintEngineEx::isNativePaintingActive() const {
|
|
|
|
|
Q_D(const QOpenGL2PaintEngineEx);
|
|
|
|
|
return d->nativePaintingActive;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::transferMode(EngineMode newMode)
|
|
|
|
|
{
|
|
|
|
|
if (newMode == mode)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (newMode == TextDrawingMode) {
|
|
|
|
|
shaderManager->setHasComplexGeometry(true);
|
|
|
|
|
} else {
|
|
|
|
|
shaderManager->setHasComplexGeometry(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (newMode == ImageDrawingMode) {
|
2016-07-27 13:45:31 +00:00
|
|
|
|
uploadData(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray, 8);
|
|
|
|
|
uploadData(QT_TEXTURE_COORDS_ATTR, staticTextureCoordinateArray, 8);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-02-11 17:16:49 +00:00
|
|
|
|
if (newMode == ImageArrayDrawingMode || newMode == ImageOpacityArrayDrawingMode) {
|
2016-07-27 13:45:31 +00:00
|
|
|
|
uploadData(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinateArray.data(), vertexCoordinateArray.vertexCount() * 2);
|
|
|
|
|
uploadData(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinateArray.data(), textureCoordinateArray.vertexCount() * 2);
|
2013-02-11 17:16:49 +00:00
|
|
|
|
|
|
|
|
|
if (newMode == ImageOpacityArrayDrawingMode)
|
2016-07-27 13:45:31 +00:00
|
|
|
|
uploadData(QT_OPACITY_ATTR, (GLfloat*)opacityArray.data(), opacityArray.size());
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This needs to change when we implement high-quality anti-aliasing...
|
|
|
|
|
if (newMode != TextDrawingMode)
|
|
|
|
|
shaderManager->setMaskType(QOpenGLEngineShaderManager::NoMask);
|
|
|
|
|
|
|
|
|
|
mode = newMode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct QOpenGL2PEVectorPathCache
|
|
|
|
|
{
|
|
|
|
|
#ifdef QT_OPENGL_CACHE_AS_VBOS
|
|
|
|
|
GLuint vbo;
|
|
|
|
|
GLuint ibo;
|
|
|
|
|
#else
|
|
|
|
|
float *vertices;
|
|
|
|
|
void *indices;
|
|
|
|
|
#endif
|
|
|
|
|
int vertexCount;
|
|
|
|
|
int indexCount;
|
|
|
|
|
GLenum primitiveType;
|
|
|
|
|
qreal iscale;
|
2011-09-07 06:40:28 +00:00
|
|
|
|
QVertexIndexVector::Type indexType;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::cleanupVectorPath(QPaintEngineEx *engine, void *data)
|
|
|
|
|
{
|
|
|
|
|
QOpenGL2PEVectorPathCache *c = (QOpenGL2PEVectorPathCache *) data;
|
|
|
|
|
#ifdef QT_OPENGL_CACHE_AS_VBOS
|
|
|
|
|
Q_ASSERT(engine->type() == QPaintEngine::OpenGL2);
|
|
|
|
|
static_cast<QOpenGL2PaintEngineEx *>(engine)->d_func()->unusedVBOSToClean << c->vbo;
|
|
|
|
|
if (c->ibo)
|
|
|
|
|
d->unusedIBOSToClean << c->ibo;
|
|
|
|
|
#else
|
|
|
|
|
Q_UNUSED(engine);
|
2011-12-20 20:39:12 +00:00
|
|
|
|
free(c->vertices);
|
|
|
|
|
free(c->indices);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#endif
|
|
|
|
|
delete c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Assumes everything is configured for the brush you want to use
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::fill(const QVectorPath& path)
|
|
|
|
|
{
|
|
|
|
|
transferMode(BrushDrawingMode);
|
|
|
|
|
|
|
|
|
|
if (snapToPixelGrid) {
|
|
|
|
|
snapToPixelGrid = false;
|
|
|
|
|
matrixDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Might need to call updateMatrix to re-calculate inverseScale
|
|
|
|
|
if (matrixDirty)
|
|
|
|
|
updateMatrix();
|
|
|
|
|
|
2017-01-11 10:42:14 +00:00
|
|
|
|
const bool supportsElementIndexUint = funcs.hasOpenGLExtension(QOpenGLExtensions::ElementIndexUint);
|
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
const QPointF* const points = reinterpret_cast<const QPointF*>(path.points());
|
|
|
|
|
|
|
|
|
|
// Check to see if there's any hints
|
|
|
|
|
if (path.shape() == QVectorPath::RectangleHint) {
|
|
|
|
|
QOpenGLRect rect(points[0].x(), points[0].y(), points[2].x(), points[2].y());
|
|
|
|
|
prepareForDraw(currentBrush.isOpaque());
|
|
|
|
|
composite(rect);
|
|
|
|
|
} else if (path.isConvex()) {
|
|
|
|
|
|
|
|
|
|
if (path.isCacheable()) {
|
|
|
|
|
QVectorPath::CacheEntry *data = path.lookupCacheData(q);
|
|
|
|
|
QOpenGL2PEVectorPathCache *cache;
|
|
|
|
|
|
|
|
|
|
bool updateCache = false;
|
|
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
|
cache = (QOpenGL2PEVectorPathCache *) data->data;
|
2018-06-22 13:56:15 +00:00
|
|
|
|
// Check if scale factor is exceeded and regenerate if so...
|
|
|
|
|
qreal scaleFactor = cache->iscale / inverseScale;
|
|
|
|
|
if (scaleFactor < 0.5 || scaleFactor > 2.0) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#ifdef QT_OPENGL_CACHE_AS_VBOS
|
2018-06-22 13:56:15 +00:00
|
|
|
|
glDeleteBuffers(1, &cache->vbo);
|
|
|
|
|
cache->vbo = 0;
|
|
|
|
|
Q_ASSERT(cache->ibo == 0);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#else
|
2018-06-22 13:56:15 +00:00
|
|
|
|
free(cache->vertices);
|
2019-11-22 13:46:58 +00:00
|
|
|
|
Q_ASSERT(cache->indices == nullptr);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#endif
|
2018-06-22 13:56:15 +00:00
|
|
|
|
updateCache = true;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
cache = new QOpenGL2PEVectorPathCache;
|
|
|
|
|
data = const_cast<QVectorPath &>(path).addCacheData(q, cache, cleanupVectorPath);
|
|
|
|
|
updateCache = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Flatten the path at the current scale factor and fill it into the cache struct.
|
|
|
|
|
if (updateCache) {
|
|
|
|
|
vertexCoordinateArray.clear();
|
|
|
|
|
vertexCoordinateArray.addPath(path, inverseScale, false);
|
|
|
|
|
int vertexCount = vertexCoordinateArray.vertexCount();
|
|
|
|
|
int floatSizeInBytes = vertexCount * 2 * sizeof(float);
|
|
|
|
|
cache->vertexCount = vertexCount;
|
|
|
|
|
cache->indexCount = 0;
|
|
|
|
|
cache->primitiveType = GL_TRIANGLE_FAN;
|
|
|
|
|
cache->iscale = inverseScale;
|
|
|
|
|
#ifdef QT_OPENGL_CACHE_AS_VBOS
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glGenBuffers(1, &cache->vbo);
|
|
|
|
|
funcs.glBindBuffer(GL_ARRAY_BUFFER, cache->vbo);
|
|
|
|
|
funcs.glBufferData(GL_ARRAY_BUFFER, floatSizeInBytes, vertexCoordinateArray.data(), GL_STATIC_DRAW);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
cache->ibo = 0;
|
|
|
|
|
#else
|
2011-12-20 20:39:12 +00:00
|
|
|
|
cache->vertices = (float *) malloc(floatSizeInBytes);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
memcpy(cache->vertices, vertexCoordinateArray.data(), floatSizeInBytes);
|
2019-11-22 13:46:58 +00:00
|
|
|
|
cache->indices = nullptr;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prepareForDraw(currentBrush.isOpaque());
|
|
|
|
|
#ifdef QT_OPENGL_CACHE_AS_VBOS
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBindBuffer(GL_ARRAY_BUFFER, cache->vbo);
|
2016-07-27 13:45:31 +00:00
|
|
|
|
uploadData(QT_VERTEX_COORD_ATTR, 0, cache->vertexCount);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, 0);
|
|
|
|
|
#else
|
2016-07-27 13:45:31 +00:00
|
|
|
|
uploadData(QT_VERTEX_COORDS_ATTR, cache->vertices, cache->vertexCount * 2);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#endif
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDrawArrays(cache->primitiveType, 0, cache->vertexCount);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// printf(" - Marking path as cachable...\n");
|
|
|
|
|
// Tag it for later so that if the same path is drawn twice, it is assumed to be static and thus cachable
|
|
|
|
|
path.makeCacheable();
|
|
|
|
|
vertexCoordinateArray.clear();
|
|
|
|
|
vertexCoordinateArray.addPath(path, inverseScale, false);
|
|
|
|
|
prepareForDraw(currentBrush.isOpaque());
|
|
|
|
|
drawVertexArrays(vertexCoordinateArray, GL_TRIANGLE_FAN);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
bool useCache = path.isCacheable();
|
|
|
|
|
if (useCache) {
|
|
|
|
|
QRectF bbox = path.controlPointRect();
|
|
|
|
|
// If the path doesn't fit within these limits, it is possible that the triangulation will fail.
|
|
|
|
|
useCache &= (bbox.left() > -0x8000 * inverseScale)
|
|
|
|
|
&& (bbox.right() < 0x8000 * inverseScale)
|
|
|
|
|
&& (bbox.top() > -0x8000 * inverseScale)
|
|
|
|
|
&& (bbox.bottom() < 0x8000 * inverseScale);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (useCache) {
|
|
|
|
|
QVectorPath::CacheEntry *data = path.lookupCacheData(q);
|
|
|
|
|
QOpenGL2PEVectorPathCache *cache;
|
|
|
|
|
|
|
|
|
|
bool updateCache = false;
|
|
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
|
cache = (QOpenGL2PEVectorPathCache *) data->data;
|
2018-06-22 13:56:15 +00:00
|
|
|
|
// Check if scale factor is exceeded and regenerate if so...
|
|
|
|
|
qreal scaleFactor = cache->iscale / inverseScale;
|
|
|
|
|
if (scaleFactor < 0.5 || scaleFactor > 2.0) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#ifdef QT_OPENGL_CACHE_AS_VBOS
|
2018-06-22 13:56:15 +00:00
|
|
|
|
glDeleteBuffers(1, &cache->vbo);
|
|
|
|
|
glDeleteBuffers(1, &cache->ibo);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#else
|
2018-06-22 13:56:15 +00:00
|
|
|
|
free(cache->vertices);
|
|
|
|
|
free(cache->indices);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#endif
|
2018-06-22 13:56:15 +00:00
|
|
|
|
updateCache = true;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
cache = new QOpenGL2PEVectorPathCache;
|
|
|
|
|
data = const_cast<QVectorPath &>(path).addCacheData(q, cache, cleanupVectorPath);
|
|
|
|
|
updateCache = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Flatten the path at the current scale factor and fill it into the cache struct.
|
|
|
|
|
if (updateCache) {
|
2017-01-11 10:42:14 +00:00
|
|
|
|
QTriangleSet polys = qTriangulate(path, QTransform().scale(1 / inverseScale, 1 / inverseScale), 1, supportsElementIndexUint);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
cache->vertexCount = polys.vertices.size() / 2;
|
|
|
|
|
cache->indexCount = polys.indices.size();
|
|
|
|
|
cache->primitiveType = GL_TRIANGLES;
|
|
|
|
|
cache->iscale = inverseScale;
|
2011-09-07 06:40:28 +00:00
|
|
|
|
cache->indexType = polys.indices.type();
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#ifdef QT_OPENGL_CACHE_AS_VBOS
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glGenBuffers(1, &cache->vbo);
|
|
|
|
|
funcs.glGenBuffers(1, &cache->ibo);
|
|
|
|
|
funcs.glBindBuffer(GL_ARRAY_BUFFER, cache->vbo);
|
|
|
|
|
funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache->ibo);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2011-09-07 06:40:28 +00:00
|
|
|
|
if (polys.indices.type() == QVertexIndexVector::UnsignedInt)
|
2011-08-22 08:49:28 +00:00
|
|
|
|
funcs.glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quint32) * polys.indices.size(), polys.indices.data(), GL_STATIC_DRAW);
|
|
|
|
|
else
|
|
|
|
|
funcs.glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quint16) * polys.indices.size(), polys.indices.data(), GL_STATIC_DRAW);
|
|
|
|
|
|
|
|
|
|
QVarLengthArray<float> vertices(polys.vertices.size());
|
|
|
|
|
for (int i = 0; i < polys.vertices.size(); ++i)
|
|
|
|
|
vertices[i] = float(inverseScale * polys.vertices.at(i));
|
|
|
|
|
funcs.glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vertices.size(), vertices.data(), GL_STATIC_DRAW);
|
|
|
|
|
#else
|
2011-12-20 20:39:12 +00:00
|
|
|
|
cache->vertices = (float *) malloc(sizeof(float) * polys.vertices.size());
|
2011-09-07 06:40:28 +00:00
|
|
|
|
if (polys.indices.type() == QVertexIndexVector::UnsignedInt) {
|
2011-12-20 20:39:12 +00:00
|
|
|
|
cache->indices = (quint32 *) malloc(sizeof(quint32) * polys.indices.size());
|
2011-08-22 08:49:28 +00:00
|
|
|
|
memcpy(cache->indices, polys.indices.data(), sizeof(quint32) * polys.indices.size());
|
|
|
|
|
} else {
|
2011-12-20 20:39:12 +00:00
|
|
|
|
cache->indices = (quint16 *) malloc(sizeof(quint16) * polys.indices.size());
|
2011-08-22 08:49:28 +00:00
|
|
|
|
memcpy(cache->indices, polys.indices.data(), sizeof(quint16) * polys.indices.size());
|
|
|
|
|
}
|
|
|
|
|
for (int i = 0; i < polys.vertices.size(); ++i)
|
|
|
|
|
cache->vertices[i] = float(inverseScale * polys.vertices.at(i));
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prepareForDraw(currentBrush.isOpaque());
|
|
|
|
|
#ifdef QT_OPENGL_CACHE_AS_VBOS
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBindBuffer(GL_ARRAY_BUFFER, cache->vbo);
|
|
|
|
|
funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache->ibo);
|
2016-07-27 13:45:31 +00:00
|
|
|
|
uploadData(QT_VERTEX_COORDS_ATTR, 0, cache->vertexCount);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, 0);
|
2011-09-07 06:40:28 +00:00
|
|
|
|
if (cache->indexType == QVertexIndexVector::UnsignedInt)
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDrawElements(cache->primitiveType, cache->indexCount, GL_UNSIGNED_INT, 0);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
else
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDrawElements(cache->primitiveType, cache->indexCount, GL_UNSIGNED_SHORT, 0);
|
|
|
|
|
funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
|
|
|
funcs.glBindBuffer(GL_ARRAY_BUFFER, 0);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#else
|
2016-07-27 13:45:31 +00:00
|
|
|
|
uploadData(QT_VERTEX_COORDS_ATTR, cache->vertices, cache->vertexCount * 2);
|
|
|
|
|
const GLenum indexValueType = cache->indexType == QVertexIndexVector::UnsignedInt ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
|
|
|
|
|
const bool useIndexVbo = uploadIndexData(cache->indices, indexValueType, cache->indexCount);
|
|
|
|
|
funcs.glDrawElements(cache->primitiveType, cache->indexCount, indexValueType, useIndexVbo ? nullptr : cache->indices);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// printf(" - Marking path as cachable...\n");
|
|
|
|
|
// Tag it for later so that if the same path is drawn twice, it is assumed to be static and thus cachable
|
|
|
|
|
path.makeCacheable();
|
|
|
|
|
|
2013-01-07 17:21:55 +00:00
|
|
|
|
if (device->context()->format().stencilBufferSize() <= 0) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
// If there is no stencil buffer, triangulate the path instead.
|
|
|
|
|
|
|
|
|
|
QRectF bbox = path.controlPointRect();
|
|
|
|
|
// If the path doesn't fit within these limits, it is possible that the triangulation will fail.
|
|
|
|
|
bool withinLimits = (bbox.left() > -0x8000 * inverseScale)
|
|
|
|
|
&& (bbox.right() < 0x8000 * inverseScale)
|
|
|
|
|
&& (bbox.top() > -0x8000 * inverseScale)
|
|
|
|
|
&& (bbox.bottom() < 0x8000 * inverseScale);
|
|
|
|
|
if (withinLimits) {
|
2017-01-11 10:42:14 +00:00
|
|
|
|
QTriangleSet polys = qTriangulate(path, QTransform().scale(1 / inverseScale, 1 / inverseScale), 1, supportsElementIndexUint);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
QVarLengthArray<float> vertices(polys.vertices.size());
|
|
|
|
|
for (int i = 0; i < polys.vertices.size(); ++i)
|
|
|
|
|
vertices[i] = float(inverseScale * polys.vertices.at(i));
|
|
|
|
|
|
|
|
|
|
prepareForDraw(currentBrush.isOpaque());
|
2016-07-27 13:45:31 +00:00
|
|
|
|
uploadData(QT_VERTEX_COORDS_ATTR, vertices.constData(), vertices.size());
|
|
|
|
|
const GLenum indexValueType = funcs.hasOpenGLExtension(QOpenGLExtensions::ElementIndexUint) ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
|
|
|
|
|
const bool useIndexVbo = uploadIndexData(polys.indices.data(), indexValueType, polys.indices.size());
|
|
|
|
|
funcs.glDrawElements(GL_TRIANGLES, polys.indices.size(), indexValueType, useIndexVbo ? nullptr : polys.indices.data());
|
2011-08-22 08:49:28 +00:00
|
|
|
|
} else {
|
|
|
|
|
// We can't handle big, concave painter paths with OpenGL without stencil buffer.
|
|
|
|
|
qWarning("Painter path exceeds +/-32767 pixels.");
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The path is too complicated & needs the stencil technique
|
|
|
|
|
vertexCoordinateArray.clear();
|
|
|
|
|
vertexCoordinateArray.addPath(path, inverseScale, false);
|
|
|
|
|
|
|
|
|
|
fillStencilWithVertexArray(vertexCoordinateArray, path.hasWindingFill());
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilMask(0xff);
|
|
|
|
|
funcs.glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
if (q->state()->clipTestEnabled) {
|
|
|
|
|
// Pass when high bit is set, replace stencil value with current clip
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_NOTEQUAL, q->state()->currentClip, GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
} else if (path.hasWindingFill()) {
|
|
|
|
|
// Pass when any bit is set, replace stencil value with 0
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_NOTEQUAL, 0, 0xff);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
} else {
|
|
|
|
|
// Pass when high bit is set, replace stencil value with 0
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_NOTEQUAL, 0, GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
prepareForDraw(currentBrush.isOpaque());
|
|
|
|
|
|
|
|
|
|
// Stencil the brush onto the dest buffer
|
|
|
|
|
composite(vertexCoordinateArray.boundingRect());
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilMask(0);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
updateClipScissorTest();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::fillStencilWithVertexArray(const float *data,
|
|
|
|
|
int count,
|
|
|
|
|
int *stops,
|
|
|
|
|
int stopCount,
|
|
|
|
|
const QOpenGLRect &bounds,
|
|
|
|
|
StencilFillMode mode)
|
|
|
|
|
{
|
|
|
|
|
Q_ASSERT(count || stops);
|
|
|
|
|
|
|
|
|
|
// qDebug("QOpenGL2PaintEngineExPrivate::fillStencilWithVertexArray()");
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilMask(0xff); // Enable stencil writes
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
if (dirtyStencilRegion.intersects(currentScissorBounds)) {
|
2016-02-27 13:34:46 +00:00
|
|
|
|
const QRegion clearRegion = dirtyStencilRegion.intersected(currentScissorBounds);
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glClearStencil(0); // Clear to zero
|
2016-02-27 13:34:46 +00:00
|
|
|
|
for (const QRect &rect : clearRegion) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#ifndef QT_GL_NO_SCISSOR_TEST
|
2016-02-27 13:34:46 +00:00
|
|
|
|
setScissor(rect);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#endif
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glClear(GL_STENCIL_BUFFER_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dirtyStencilRegion -= currentScissorBounds;
|
|
|
|
|
|
|
|
|
|
#ifndef QT_GL_NO_SCISSOR_TEST
|
|
|
|
|
updateClipScissorTest();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // Disable color writes
|
2011-08-22 08:49:28 +00:00
|
|
|
|
useSimpleShader();
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glEnable(GL_STENCIL_TEST); // For some reason, this has to happen _after_ the simple shader is use()'d
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
if (mode == WindingFillMode) {
|
|
|
|
|
Q_ASSERT(stops && !count);
|
|
|
|
|
if (q->state()->clipTestEnabled) {
|
|
|
|
|
// Flatten clip values higher than current clip, and set high bit to match current clip
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_LEQUAL, GL_STENCIL_HIGH_BIT | q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
|
|
|
|
|
funcs.glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
composite(bounds);
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_EQUAL, GL_STENCIL_HIGH_BIT, GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
} else if (!stencilClean) {
|
|
|
|
|
// Clear stencil buffer within bounding rect
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_ALWAYS, 0, 0xff);
|
|
|
|
|
funcs.glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
composite(bounds);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Inc. for front-facing triangle
|
|
|
|
|
funcs.glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_INCR_WRAP, GL_INCR_WRAP);
|
|
|
|
|
// Dec. for back-facing "holes"
|
|
|
|
|
funcs.glStencilOpSeparate(GL_BACK, GL_KEEP, GL_DECR_WRAP, GL_DECR_WRAP);
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilMask(~GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
drawVertexArrays(data, stops, stopCount, GL_TRIANGLE_FAN);
|
|
|
|
|
|
|
|
|
|
if (q->state()->clipTestEnabled) {
|
|
|
|
|
// Clear high bit of stencil outside of path
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_EQUAL, q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
|
|
|
|
|
funcs.glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
|
|
|
|
|
funcs.glStencilMask(GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
composite(bounds);
|
|
|
|
|
}
|
|
|
|
|
} else if (mode == OddEvenFillMode) {
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilMask(GL_STENCIL_HIGH_BIT);
|
|
|
|
|
funcs.glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); // Simply invert the stencil bit
|
2011-08-22 08:49:28 +00:00
|
|
|
|
drawVertexArrays(data, stops, stopCount, GL_TRIANGLE_FAN);
|
|
|
|
|
|
|
|
|
|
} else { // TriStripStrokeFillMode
|
|
|
|
|
Q_ASSERT(count && !stops); // tristrips generated directly, so no vertexArray or stops
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilMask(GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#if 0
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); // Simply invert the stencil bit
|
2011-08-22 08:49:28 +00:00
|
|
|
|
setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, data);
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#else
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
if (q->state()->clipTestEnabled) {
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_LEQUAL, q->state()->currentClip | GL_STENCIL_HIGH_BIT,
|
|
|
|
|
~GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
} else {
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_ALWAYS, GL_STENCIL_HIGH_BIT, 0xff);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
2016-07-27 13:45:31 +00:00
|
|
|
|
|
|
|
|
|
uploadData(QT_VERTEX_COORDS_ATTR, data, count * 2);
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enable color writes & disable stencil writes
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
If the maximum value in the stencil buffer is GL_STENCIL_HIGH_BIT - 1,
|
|
|
|
|
restore the stencil buffer to a pristine state. The current clip region
|
|
|
|
|
is set to 1, and the rest to 0.
|
|
|
|
|
*/
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::resetClipIfNeeded()
|
|
|
|
|
{
|
|
|
|
|
if (maxClip != (GL_STENCIL_HIGH_BIT - 1))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Q_Q(QOpenGL2PaintEngineEx);
|
|
|
|
|
|
|
|
|
|
useSimpleShader();
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glEnable(GL_STENCIL_TEST);
|
|
|
|
|
funcs.glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
QRectF bounds = q->state()->matrix.inverted().mapRect(QRectF(0, 0, width, height));
|
|
|
|
|
QOpenGLRect rect(bounds.left(), bounds.top(), bounds.right(), bounds.bottom());
|
|
|
|
|
|
|
|
|
|
// Set high bit on clip region
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_LEQUAL, q->state()->currentClip, 0xff);
|
|
|
|
|
funcs.glStencilOp(GL_KEEP, GL_INVERT, GL_INVERT);
|
|
|
|
|
funcs.glStencilMask(GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
composite(rect);
|
|
|
|
|
|
|
|
|
|
// Reset clipping to 1 and everything else to zero
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_NOTEQUAL, 0x01, GL_STENCIL_HIGH_BIT);
|
|
|
|
|
funcs.glStencilOp(GL_ZERO, GL_REPLACE, GL_REPLACE);
|
|
|
|
|
funcs.glStencilMask(0xff);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
composite(rect);
|
|
|
|
|
|
|
|
|
|
q->state()->currentClip = 1;
|
|
|
|
|
q->state()->canRestoreClip = false;
|
|
|
|
|
|
|
|
|
|
maxClip = 1;
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilMask(0x0);
|
|
|
|
|
funcs.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-01-15 14:32:41 +00:00
|
|
|
|
bool QOpenGL2PaintEngineExPrivate::prepareForCachedGlyphDraw(const QFontEngineGlyphCache &cache)
|
|
|
|
|
{
|
|
|
|
|
Q_Q(QOpenGL2PaintEngineEx);
|
|
|
|
|
|
2013-01-22 16:05:09 +00:00
|
|
|
|
Q_ASSERT(cache.transform().type() <= QTransform::TxScale);
|
|
|
|
|
|
2013-01-15 14:32:41 +00:00
|
|
|
|
QTransform &transform = q->state()->matrix;
|
|
|
|
|
transform.scale(1.0 / cache.transform().m11(), 1.0 / cache.transform().m22());
|
|
|
|
|
bool ret = prepareForDraw(false);
|
|
|
|
|
transform.scale(cache.transform().m11(), cache.transform().m22());
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
bool QOpenGL2PaintEngineExPrivate::prepareForDraw(bool srcPixelsAreOpaque)
|
|
|
|
|
{
|
2013-02-11 17:16:49 +00:00
|
|
|
|
if (brushTextureDirty && (mode == TextDrawingMode || mode == BrushDrawingMode))
|
2011-08-22 08:49:28 +00:00
|
|
|
|
updateBrushTexture();
|
|
|
|
|
|
|
|
|
|
if (compositionModeDirty)
|
|
|
|
|
updateCompositionMode();
|
|
|
|
|
|
|
|
|
|
if (matrixDirty)
|
|
|
|
|
updateMatrix();
|
|
|
|
|
|
|
|
|
|
const bool stateHasOpacity = q->state()->opacity < 0.99f;
|
|
|
|
|
if (q->state()->composition_mode == QPainter::CompositionMode_Source
|
|
|
|
|
|| (q->state()->composition_mode == QPainter::CompositionMode_SourceOver
|
|
|
|
|
&& srcPixelsAreOpaque && !stateHasOpacity))
|
|
|
|
|
{
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDisable(GL_BLEND);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
} else {
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glEnable(GL_BLEND);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QOpenGLEngineShaderManager::OpacityMode opacityMode;
|
2013-02-11 17:16:49 +00:00
|
|
|
|
if (mode == ImageOpacityArrayDrawingMode) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
opacityMode = QOpenGLEngineShaderManager::AttributeOpacity;
|
|
|
|
|
} else {
|
|
|
|
|
opacityMode = stateHasOpacity ? QOpenGLEngineShaderManager::UniformOpacity
|
|
|
|
|
: QOpenGLEngineShaderManager::NoOpacity;
|
2013-02-11 17:16:49 +00:00
|
|
|
|
if (stateHasOpacity && (mode != ImageDrawingMode && mode != ImageArrayDrawingMode)) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
// Using a brush
|
|
|
|
|
bool brushIsPattern = (currentBrush.style() >= Qt::Dense1Pattern) &&
|
|
|
|
|
(currentBrush.style() <= Qt::DiagCrossPattern);
|
|
|
|
|
|
|
|
|
|
if ((currentBrush.style() == Qt::SolidPattern) || brushIsPattern)
|
|
|
|
|
opacityMode = QOpenGLEngineShaderManager::NoOpacity; // Global opacity handled by srcPixel shader
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
shaderManager->setOpacityMode(opacityMode);
|
|
|
|
|
|
|
|
|
|
bool changed = shaderManager->useCorrectShaderProg();
|
|
|
|
|
// If the shader program needs changing, we change it and mark all uniforms as dirty
|
|
|
|
|
if (changed) {
|
|
|
|
|
// The shader program has changed so mark all uniforms as dirty:
|
|
|
|
|
brushUniformsDirty = true;
|
|
|
|
|
opacityUniformDirty = true;
|
|
|
|
|
matrixUniformDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-11 17:16:49 +00:00
|
|
|
|
if (brushUniformsDirty && (mode == TextDrawingMode || mode == BrushDrawingMode))
|
2011-08-22 08:49:28 +00:00
|
|
|
|
updateBrushUniforms();
|
|
|
|
|
|
|
|
|
|
if (opacityMode == QOpenGLEngineShaderManager::UniformOpacity && opacityUniformDirty) {
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::GlobalOpacity), (GLfloat)q->state()->opacity);
|
|
|
|
|
opacityUniformDirty = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (matrixUniformDirty && shaderManager->hasComplexGeometry()) {
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::Matrix),
|
|
|
|
|
pmvMatrix);
|
|
|
|
|
matrixUniformDirty = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::composite(const QOpenGLRect& boundingRect)
|
|
|
|
|
{
|
|
|
|
|
setCoords(staticVertexCoordinateArray, boundingRect);
|
2016-07-27 13:45:31 +00:00
|
|
|
|
|
|
|
|
|
uploadData(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray, 8);
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draws the vertex array as a set of <vertexArrayStops.size()> triangle fans.
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::drawVertexArrays(const float *data, int *stops, int stopCount,
|
|
|
|
|
GLenum primitive)
|
|
|
|
|
{
|
|
|
|
|
// Now setup the pointer to the vertex array:
|
2016-07-27 13:45:31 +00:00
|
|
|
|
uploadData(QT_VERTEX_COORDS_ATTR, data, stops[stopCount-1] * 2);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
int previousStop = 0;
|
|
|
|
|
for (int i=0; i<stopCount; ++i) {
|
|
|
|
|
int stop = stops[i];
|
2016-07-27 13:45:31 +00:00
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDrawArrays(primitive, previousStop, stop - previousStop);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
previousStop = stop;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////// Public Methods //////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
QOpenGL2PaintEngineEx::QOpenGL2PaintEngineEx()
|
|
|
|
|
: QPaintEngineEx(*(new QOpenGL2PaintEngineExPrivate(this)))
|
|
|
|
|
{
|
2019-12-19 20:33:10 +00:00
|
|
|
|
gccaps &= ~QPaintEngine::RasterOpModes;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QOpenGL2PaintEngineEx::~QOpenGL2PaintEngineEx()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::fill(const QVectorPath &path, const QBrush &brush)
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
|
|
|
|
|
if (qbrush_style(brush) == Qt::NoBrush)
|
|
|
|
|
return;
|
|
|
|
|
ensureActive();
|
|
|
|
|
d->setBrush(brush);
|
|
|
|
|
d->fill(path);
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-11 13:50:40 +00:00
|
|
|
|
Q_GUI_EXPORT extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); // qtransform.cpp
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::stroke(const QVectorPath &path, const QPen &pen)
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
|
|
|
|
|
const QBrush &penBrush = qpen_brush(pen);
|
|
|
|
|
if (qpen_style(pen) == Qt::NoPen || qbrush_style(penBrush) == Qt::NoBrush)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
QOpenGL2PaintEngineState *s = state();
|
2019-11-22 13:46:58 +00:00
|
|
|
|
if (qt_pen_is_cosmetic(pen, state()->renderHints) && !qt_scaleForTransform(s->transform(), nullptr)) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
// QTriangulatingStroker class is not meant to support cosmetically sheared strokes.
|
|
|
|
|
QPaintEngineEx::stroke(path, pen);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensureActive();
|
|
|
|
|
d->setBrush(penBrush);
|
|
|
|
|
d->stroke(path, pen);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::stroke(const QVectorPath &path, const QPen &pen)
|
|
|
|
|
{
|
|
|
|
|
const QOpenGL2PaintEngineState *s = q->state();
|
|
|
|
|
if (snapToPixelGrid) {
|
|
|
|
|
snapToPixelGrid = false;
|
|
|
|
|
matrixDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Qt::PenStyle penStyle = qpen_style(pen);
|
|
|
|
|
const QBrush &penBrush = qpen_brush(pen);
|
|
|
|
|
const bool opaque = penBrush.isOpaque() && s->opacity > 0.99;
|
|
|
|
|
|
|
|
|
|
transferMode(BrushDrawingMode);
|
|
|
|
|
|
|
|
|
|
// updateMatrix() is responsible for setting the inverse scale on
|
|
|
|
|
// the strokers, so we need to call it here and not wait for
|
|
|
|
|
// prepareForDraw() down below.
|
|
|
|
|
updateMatrix();
|
|
|
|
|
|
|
|
|
|
QRectF clip = q->state()->matrix.inverted().mapRect(q->state()->clipEnabled
|
|
|
|
|
? q->state()->rectangleClip
|
|
|
|
|
: QRectF(0, 0, width, height));
|
|
|
|
|
|
|
|
|
|
if (penStyle == Qt::SolidLine) {
|
2012-10-15 15:06:34 +00:00
|
|
|
|
stroker.process(path, pen, clip, s->renderHints);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
} else { // Some sort of dash
|
2012-10-15 15:06:34 +00:00
|
|
|
|
dasher.process(path, pen, clip, s->renderHints);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
QVectorPath dashStroke(dasher.points(),
|
|
|
|
|
dasher.elementCount(),
|
2017-05-02 10:58:15 +00:00
|
|
|
|
dasher.elementTypes());
|
2012-10-15 15:06:34 +00:00
|
|
|
|
stroker.process(dashStroke, pen, clip, s->renderHints);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!stroker.vertexCount())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (opaque) {
|
|
|
|
|
prepareForDraw(opaque);
|
|
|
|
|
|
2016-07-27 13:45:31 +00:00
|
|
|
|
uploadData(QT_VERTEX_COORDS_ATTR, stroker.vertices(), stroker.vertexCount());
|
|
|
|
|
funcs.glDrawArrays(GL_TRIANGLE_STRIP, 0, stroker.vertexCount() / 2);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
} else {
|
|
|
|
|
qreal width = qpen_widthf(pen) / 2;
|
|
|
|
|
if (width == 0)
|
|
|
|
|
width = 0.5;
|
|
|
|
|
qreal extra = pen.joinStyle() == Qt::MiterJoin
|
|
|
|
|
? qMax(pen.miterLimit() * width, width)
|
|
|
|
|
: width;
|
|
|
|
|
|
2012-10-15 15:06:34 +00:00
|
|
|
|
if (qt_pen_is_cosmetic(pen, q->state()->renderHints))
|
2011-08-22 08:49:28 +00:00
|
|
|
|
extra = extra * inverseScale;
|
|
|
|
|
|
|
|
|
|
QRectF bounds = path.controlPointRect().adjusted(-extra, -extra, extra, extra);
|
|
|
|
|
|
|
|
|
|
fillStencilWithVertexArray(stroker.vertices(), stroker.vertexCount() / 2,
|
2019-11-22 13:46:58 +00:00
|
|
|
|
nullptr, 0, bounds, QOpenGL2PaintEngineExPrivate::TriStripStrokeFillMode);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
// Pass when any bit is set, replace stencil value with 0
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_NOTEQUAL, 0, GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
prepareForDraw(false);
|
|
|
|
|
|
|
|
|
|
// Stencil the brush onto the dest buffer
|
|
|
|
|
composite(bounds);
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilMask(0);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
updateClipScissorTest();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::penChanged() { }
|
|
|
|
|
void QOpenGL2PaintEngineEx::brushChanged() { }
|
|
|
|
|
void QOpenGL2PaintEngineEx::brushOriginChanged() { }
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::opacityChanged()
|
|
|
|
|
{
|
|
|
|
|
// qDebug("QOpenGL2PaintEngineEx::opacityChanged()");
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
state()->opacityChanged = true;
|
|
|
|
|
|
|
|
|
|
Q_ASSERT(d->shaderManager);
|
|
|
|
|
d->brushUniformsDirty = true;
|
|
|
|
|
d->opacityUniformDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::compositionModeChanged()
|
|
|
|
|
{
|
|
|
|
|
// qDebug("QOpenGL2PaintEngineEx::compositionModeChanged()");
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
state()->compositionModeChanged = true;
|
|
|
|
|
d->compositionModeDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::renderHintsChanged()
|
|
|
|
|
{
|
|
|
|
|
state()->renderHintsChanged = true;
|
|
|
|
|
|
2020-04-14 11:48:28 +00:00
|
|
|
|
#if !QT_CONFIG(opengles2)
|
2014-04-24 15:11:23 +00:00
|
|
|
|
if (!QOpenGLContext::currentContext()->isOpenGLES()) {
|
2014-03-04 14:06:36 +00:00
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
2020-05-05 19:23:44 +00:00
|
|
|
|
if (state()->renderHints & QPainter::Antialiasing)
|
2014-03-04 14:06:36 +00:00
|
|
|
|
d->funcs.glEnable(GL_MULTISAMPLE);
|
Dynamic GL switch on Windows
The patch introduces a new build configuration on Windows which
can be requested by passing -opengl dynamic to configure.
Platforms other than Windows (including WinRT) are not affected.
The existing Angle and desktop configurations are not affected.
These continue to function as before and Angle remains the default.
In the future, when all modules have added support for the dynamic
path, as described below, the default configuration could be changed
to be the dynamic one. This would allow providing a single set of
binaries in the official builds instead of the current two.
When requesting dynamic GL, Angle is built but QT_OPENGL_ES[_2] are
never defined. Instead, the code path that has traditionally been
desktop GL only becomes the dynamic path that has to do runtime
checks. Qt modules and applications are not linked to opengl32.dll or
libegl/glesv2.dll in this case. Instead, QtGui exports all necessary
egl/egl/gl functions which will, under the hood, forward all requests
to a dynamically loaded EGL/WGL/GL implementation.
Porting guide (better said, changes needed to prepare your code to
work with dynamic GL builds when the fallback to Angle is utilized):
1. In !QT_OPENGL_ES[_2] code branches use QOpenGLFunctions::isES() to
differentiate between desktop and ES where needed. Keep in mind that
it is the desktop GL header (plus qopenglext.h) that is included,
not the GLES one.
QtGui's proxy will handle some differences, for example calling
glClearDepth will route to glClearDepthf when needed. The built-in
eglGetProcAddress is able to retrieve pointers for standard GLES2
functions too so code resolving OpenGL 2 functions will function
in any case.
2. QT_CONFIG will contain "opengl" and "dynamicgl" in dynamic builds,
but never "angle" or "opengles2".
3. The preprocessor define QT_OPENGL_DYNAMIC is also available in
dynamic builds. The usage of this is strongly discouraged and should
not be needed anywhere except for QtGui and the platform plugin.
4. Code in need of the library handle can use
QOpenGLFunctions::platformGLHandle().
The decision on which library to load is currently based on a simple
test that creates a dummy window/context and tries to resolve an
OpenGL 2 function. If this fails, it goes for Angle. This seems to work
well on Win7 PCs for example that do not have proper graphics drivers
providing OpenGL installed but are D3D9 capable using the default drivers.
Setting QT_OPENGL to desktop or angle skips the test and forces
usage of the given GL. There are also two new application attributes
that could be used for the same purpose.
If Angle is requested but the libraries are not present, desktop is
tried. If desktop is requested, or if angle is requested but nothing
works, the EGL/WGL functions will still be callable but will return 0.
This conveniently means that eglInitialize() and such will report a failure.
Debug messages can be enabled by setting QT_OPENGLPROXY_DEBUG. This will
tell which implementation is chosen.
The textures example application is ported to OpenGL 2, the GL 1
code path is removed.
[ChangeLog][QtGui] Qt builds on Windows can now be configured for
dynamic loading of the OpenGL implementation. This can be requested
by passing -opengl dynamic to configure. In this mode no modules will
link to opengl32.dll or Angle's libegl/libglesv2. Instead, QtGui will
dynamically choose between desktop and Angle during the first GL/EGL/WGL
call. This allows deploying applications with a single set of Qt libraries
with the ability of transparently falling back to Angle in case the
opengl32.dll is not suitable, due to missing graphics drivers for example.
Task-number: QTBUG-36483
Change-Id: I716fdebbf60b355b7d9ef57d1e069eef366b4ab9
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
Reviewed-by: Jørgen Lind <jorgen.lind@digia.com>
2014-01-27 13:45:11 +00:00
|
|
|
|
else
|
2014-03-04 14:06:36 +00:00
|
|
|
|
d->funcs.glDisable(GL_MULTISAMPLE);
|
Dynamic GL switch on Windows
The patch introduces a new build configuration on Windows which
can be requested by passing -opengl dynamic to configure.
Platforms other than Windows (including WinRT) are not affected.
The existing Angle and desktop configurations are not affected.
These continue to function as before and Angle remains the default.
In the future, when all modules have added support for the dynamic
path, as described below, the default configuration could be changed
to be the dynamic one. This would allow providing a single set of
binaries in the official builds instead of the current two.
When requesting dynamic GL, Angle is built but QT_OPENGL_ES[_2] are
never defined. Instead, the code path that has traditionally been
desktop GL only becomes the dynamic path that has to do runtime
checks. Qt modules and applications are not linked to opengl32.dll or
libegl/glesv2.dll in this case. Instead, QtGui exports all necessary
egl/egl/gl functions which will, under the hood, forward all requests
to a dynamically loaded EGL/WGL/GL implementation.
Porting guide (better said, changes needed to prepare your code to
work with dynamic GL builds when the fallback to Angle is utilized):
1. In !QT_OPENGL_ES[_2] code branches use QOpenGLFunctions::isES() to
differentiate between desktop and ES where needed. Keep in mind that
it is the desktop GL header (plus qopenglext.h) that is included,
not the GLES one.
QtGui's proxy will handle some differences, for example calling
glClearDepth will route to glClearDepthf when needed. The built-in
eglGetProcAddress is able to retrieve pointers for standard GLES2
functions too so code resolving OpenGL 2 functions will function
in any case.
2. QT_CONFIG will contain "opengl" and "dynamicgl" in dynamic builds,
but never "angle" or "opengles2".
3. The preprocessor define QT_OPENGL_DYNAMIC is also available in
dynamic builds. The usage of this is strongly discouraged and should
not be needed anywhere except for QtGui and the platform plugin.
4. Code in need of the library handle can use
QOpenGLFunctions::platformGLHandle().
The decision on which library to load is currently based on a simple
test that creates a dummy window/context and tries to resolve an
OpenGL 2 function. If this fails, it goes for Angle. This seems to work
well on Win7 PCs for example that do not have proper graphics drivers
providing OpenGL installed but are D3D9 capable using the default drivers.
Setting QT_OPENGL to desktop or angle skips the test and forces
usage of the given GL. There are also two new application attributes
that could be used for the same purpose.
If Angle is requested but the libraries are not present, desktop is
tried. If desktop is requested, or if angle is requested but nothing
works, the EGL/WGL functions will still be callable but will return 0.
This conveniently means that eglInitialize() and such will report a failure.
Debug messages can be enabled by setting QT_OPENGLPROXY_DEBUG. This will
tell which implementation is chosen.
The textures example application is ported to OpenGL 2, the GL 1
code path is removed.
[ChangeLog][QtGui] Qt builds on Windows can now be configured for
dynamic loading of the OpenGL implementation. This can be requested
by passing -opengl dynamic to configure. In this mode no modules will
link to opengl32.dll or Angle's libegl/libglesv2. Instead, QtGui will
dynamically choose between desktop and Angle during the first GL/EGL/WGL
call. This allows deploying applications with a single set of Qt libraries
with the ability of transparently falling back to Angle in case the
opengl32.dll is not suitable, due to missing graphics drivers for example.
Task-number: QTBUG-36483
Change-Id: I716fdebbf60b355b7d9ef57d1e069eef366b4ab9
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
Reviewed-by: Jørgen Lind <jorgen.lind@digia.com>
2014-01-27 13:45:11 +00:00
|
|
|
|
}
|
2020-04-14 11:48:28 +00:00
|
|
|
|
#endif // !QT_CONFIG(opengles2)
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
2014-12-08 14:31:37 +00:00
|
|
|
|
|
|
|
|
|
// This is a somewhat sneaky way of conceptually making the next call to
|
|
|
|
|
// updateTexture() use FoceUpdate for the TextureUpdateMode. We need this
|
|
|
|
|
// as new render hints may require updating the filter mode.
|
2011-08-22 08:49:28 +00:00
|
|
|
|
d->lastTextureUsed = GLuint(-1);
|
2014-12-08 14:31:37 +00:00
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
d->brushTextureDirty = true;
|
|
|
|
|
// qDebug("QOpenGL2PaintEngineEx::renderHintsChanged() not implemented!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::transformChanged()
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
d->matrixDirty = true;
|
|
|
|
|
state()->matrixChanged = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const QRectF scaleRect(const QRectF &r, qreal sx, qreal sy)
|
|
|
|
|
{
|
|
|
|
|
return QRectF(r.x() * sx, r.y() * sy, r.width() * sx, r.height() * sy);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::drawPixmap(const QRectF& dest, const QPixmap & pixmap, const QRectF & src)
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
QOpenGLContext *ctx = d->ctx;
|
|
|
|
|
|
2014-09-19 13:19:28 +00:00
|
|
|
|
// Draw pixmaps that are really images as images since drawImage has
|
|
|
|
|
// better handling of non-default image formats.
|
|
|
|
|
if (pixmap.paintEngine()->type() == QPaintEngine::Raster && !pixmap.isQBitmap())
|
|
|
|
|
return drawImage(dest, pixmap.toImage(), src);
|
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
int max_texture_size = ctx->d_func()->maxTextureSize();
|
|
|
|
|
if (pixmap.width() > max_texture_size || pixmap.height() > max_texture_size) {
|
|
|
|
|
QPixmap scaled = pixmap.scaled(max_texture_size, max_texture_size, Qt::KeepAspectRatio);
|
|
|
|
|
|
|
|
|
|
const qreal sx = scaled.width() / qreal(pixmap.width());
|
|
|
|
|
const qreal sy = scaled.height() / qreal(pixmap.height());
|
|
|
|
|
|
|
|
|
|
drawPixmap(dest, scaled, scaleRect(src, sx, sy));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensureActive();
|
|
|
|
|
d->transferMode(ImageDrawingMode);
|
|
|
|
|
|
2014-12-02 19:16:23 +00:00
|
|
|
|
GLenum filterMode = state()->renderHints & QPainter::SmoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
|
|
|
|
|
d->updateTexture(QT_IMAGE_TEXTURE_UNIT, pixmap, GL_CLAMP_TO_EDGE, filterMode);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
bool isBitmap = pixmap.isQBitmap();
|
|
|
|
|
bool isOpaque = !isBitmap && !pixmap.hasAlpha();
|
|
|
|
|
|
2014-09-19 13:19:28 +00:00
|
|
|
|
d->shaderManager->setSrcPixelType(isBitmap ? QOpenGLEngineShaderManager::PatternSrc : QOpenGLEngineShaderManager::ImageSrc);
|
2014-12-02 17:38:06 +00:00
|
|
|
|
|
|
|
|
|
QOpenGLRect srcRect(src.left(), src.top(), src.right(), src.bottom());
|
2011-08-22 08:49:28 +00:00
|
|
|
|
d->drawTexture(dest, srcRect, pixmap.size(), isOpaque, isBitmap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::drawImage(const QRectF& dest, const QImage& image, const QRectF& src,
|
|
|
|
|
Qt::ImageConversionFlags)
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
QOpenGLContext *ctx = d->ctx;
|
|
|
|
|
|
|
|
|
|
int max_texture_size = ctx->d_func()->maxTextureSize();
|
|
|
|
|
if (image.width() > max_texture_size || image.height() > max_texture_size) {
|
|
|
|
|
QImage scaled = image.scaled(max_texture_size, max_texture_size, Qt::KeepAspectRatio);
|
|
|
|
|
|
|
|
|
|
const qreal sx = scaled.width() / qreal(image.width());
|
|
|
|
|
const qreal sy = scaled.height() / qreal(image.height());
|
|
|
|
|
|
|
|
|
|
drawImage(dest, scaled, scaleRect(src, sx, sy));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensureActive();
|
|
|
|
|
d->transferMode(ImageDrawingMode);
|
|
|
|
|
|
2018-07-18 12:49:15 +00:00
|
|
|
|
QOpenGLTextureUploader::BindOptions bindOption = QOpenGLTextureUploader::PremultipliedAlphaBindOption;
|
2014-09-19 13:19:28 +00:00
|
|
|
|
// Use specialized bind for formats we have specialized shaders for.
|
|
|
|
|
switch (image.format()) {
|
|
|
|
|
case QImage::Format_RGBA8888:
|
|
|
|
|
case QImage::Format_ARGB32:
|
2016-12-07 13:37:12 +00:00
|
|
|
|
case QImage::Format_RGBA64:
|
2014-09-19 13:19:28 +00:00
|
|
|
|
d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::NonPremultipliedImageSrc);
|
2019-11-18 16:01:26 +00:00
|
|
|
|
bindOption = { };
|
2014-09-19 13:19:28 +00:00
|
|
|
|
break;
|
2014-08-04 09:47:11 +00:00
|
|
|
|
case QImage::Format_Alpha8:
|
|
|
|
|
if (ctx->functions()->hasOpenGLFeature(QOpenGLFunctions::TextureRGFormats)) {
|
|
|
|
|
d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::AlphaImageSrc);
|
2018-11-20 09:04:28 +00:00
|
|
|
|
bindOption = QOpenGLTextureUploader::UseRedForAlphaAndLuminanceBindOption;
|
2014-08-04 09:47:11 +00:00
|
|
|
|
} else
|
|
|
|
|
d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
|
|
|
|
|
break;
|
|
|
|
|
case QImage::Format_Grayscale8:
|
2018-11-20 09:04:28 +00:00
|
|
|
|
case QImage::Format_Grayscale16:
|
2014-08-04 09:47:11 +00:00
|
|
|
|
if (ctx->functions()->hasOpenGLFeature(QOpenGLFunctions::TextureRGFormats)) {
|
|
|
|
|
d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::GrayscaleImageSrc);
|
2018-11-20 09:04:28 +00:00
|
|
|
|
bindOption = QOpenGLTextureUploader::UseRedForAlphaAndLuminanceBindOption;
|
2014-08-04 09:47:11 +00:00
|
|
|
|
} else
|
|
|
|
|
d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
|
|
|
|
|
break;
|
2014-09-19 13:19:28 +00:00
|
|
|
|
default:
|
|
|
|
|
d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-12-02 19:16:23 +00:00
|
|
|
|
ImageWithBindOptions imageWithOptions = { image, bindOption };
|
|
|
|
|
GLenum filterMode = state()->renderHints & QPainter::SmoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
|
|
|
|
|
d->updateTexture(QT_IMAGE_TEXTURE_UNIT, imageWithOptions, GL_CLAMP_TO_EDGE, filterMode);
|
2014-09-19 13:19:28 +00:00
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
d->drawTexture(dest, src, image.size(), !image.hasAlphaChannel());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::drawStaticTextItem(QStaticTextItem *textItem)
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
|
|
|
|
|
ensureActive();
|
|
|
|
|
|
|
|
|
|
QPainterState *s = state();
|
|
|
|
|
|
|
|
|
|
QFontEngine *fontEngine = textItem->fontEngine();
|
2013-01-10 14:09:32 +00:00
|
|
|
|
if (shouldDrawCachedGlyphs(fontEngine, s->matrix)) {
|
2014-02-10 12:59:44 +00:00
|
|
|
|
QFontEngine::GlyphFormat glyphFormat = fontEngine->glyphFormat != QFontEngine::Format_None
|
|
|
|
|
? fontEngine->glyphFormat : d->glyphCacheFormat;
|
|
|
|
|
if (glyphFormat == QFontEngine::Format_A32) {
|
2011-09-07 08:31:03 +00:00
|
|
|
|
if (d->device->context()->format().alphaBufferSize() > 0 || s->matrix.type() > QTransform::TxTranslate
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|| (s->composition_mode != QPainter::CompositionMode_Source
|
|
|
|
|
&& s->composition_mode != QPainter::CompositionMode_SourceOver))
|
|
|
|
|
{
|
2014-02-10 12:59:44 +00:00
|
|
|
|
glyphFormat = QFontEngine::Format_A8;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-10 12:59:44 +00:00
|
|
|
|
d->drawCachedGlyphs(glyphFormat, textItem);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
} else {
|
|
|
|
|
QPaintEngineEx::drawStaticTextItem(textItem);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QOpenGL2PaintEngineEx::drawTexture(const QRectF &dest, GLuint textureId, const QSize &size, const QRectF &src)
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
if (!d->shaderManager)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
ensureActive();
|
|
|
|
|
d->transferMode(ImageDrawingMode);
|
|
|
|
|
|
2014-12-02 19:16:23 +00:00
|
|
|
|
GLenum filterMode = state()->renderHints & QPainter::SmoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
|
|
|
|
|
d->updateTexture(QT_IMAGE_TEXTURE_UNIT, textureId, GL_CLAMP_TO_EDGE, filterMode);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-09-19 13:19:28 +00:00
|
|
|
|
d->shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
|
2014-12-02 17:38:06 +00:00
|
|
|
|
|
|
|
|
|
QOpenGLRect srcRect(src.left(), src.bottom(), src.right(), src.top());
|
2011-08-22 08:49:28 +00:00
|
|
|
|
d->drawTexture(dest, srcRect, size, false);
|
2014-12-02 17:38:06 +00:00
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::drawTextItem(const QPointF &p, const QTextItem &textItem)
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
|
|
|
|
|
ensureActive();
|
|
|
|
|
QOpenGL2PaintEngineState *s = state();
|
|
|
|
|
|
|
|
|
|
const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
|
|
|
|
|
|
|
|
|
|
QTransform::TransformationType txtype = s->matrix.type();
|
|
|
|
|
|
2014-02-10 12:59:44 +00:00
|
|
|
|
QFontEngine::GlyphFormat glyphFormat = ti.fontEngine->glyphFormat != QFontEngine::Format_None
|
|
|
|
|
? ti.fontEngine->glyphFormat : d->glyphCacheFormat;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-02-10 12:59:44 +00:00
|
|
|
|
if (glyphFormat == QFontEngine::Format_A32) {
|
2011-09-07 08:31:03 +00:00
|
|
|
|
if (d->device->context()->format().alphaBufferSize() > 0 || txtype > QTransform::TxTranslate
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|| (state()->composition_mode != QPainter::CompositionMode_Source
|
|
|
|
|
&& state()->composition_mode != QPainter::CompositionMode_SourceOver))
|
|
|
|
|
{
|
2014-02-10 12:59:44 +00:00
|
|
|
|
glyphFormat = QFontEngine::Format_A8;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-10 14:09:32 +00:00
|
|
|
|
if (shouldDrawCachedGlyphs(ti.fontEngine, s->matrix)) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
QVarLengthArray<QFixedPoint> positions;
|
|
|
|
|
QVarLengthArray<glyph_t> glyphs;
|
|
|
|
|
QTransform matrix = QTransform::fromTranslate(p.x(), p.y());
|
|
|
|
|
ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
QStaticTextItem staticTextItem;
|
|
|
|
|
staticTextItem.setFontEngine(ti.fontEngine);
|
|
|
|
|
staticTextItem.glyphs = glyphs.data();
|
|
|
|
|
staticTextItem.numGlyphs = glyphs.size();
|
|
|
|
|
staticTextItem.glyphPositions = positions.data();
|
|
|
|
|
|
2014-02-10 12:59:44 +00:00
|
|
|
|
d->drawCachedGlyphs(glyphFormat, &staticTextItem);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QPaintEngineEx::drawTextItem(p, ti);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
class QOpenGLStaticTextUserData: public QStaticTextUserData
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
QOpenGLStaticTextUserData()
|
|
|
|
|
: QStaticTextUserData(OpenGLUserData), cacheSize(0, 0), cacheSerialNumber(0)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~QOpenGLStaticTextUserData()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QSize cacheSize;
|
|
|
|
|
QOpenGL2PEXVertexArray vertexCoordinateArray;
|
|
|
|
|
QOpenGL2PEXVertexArray textureCoordinateArray;
|
2014-02-10 12:59:44 +00:00
|
|
|
|
QFontEngine::GlyphFormat glyphFormat;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
int cacheSerialNumber;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// #define QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO
|
|
|
|
|
|
2013-01-10 14:09:32 +00:00
|
|
|
|
bool QOpenGL2PaintEngineEx::shouldDrawCachedGlyphs(QFontEngine *fontEngine, const QTransform &t) const
|
|
|
|
|
{
|
Restore smooth-scaled drawing of 0.5x-2.0x scaled glyphs in the GL engine
Commit b5922c89ba942 (Add support for retina glyph-based text drawing
in the GL engine), and commit 155a20628b91 (Use QPaintEngineEx to decide
if font is too big for using the glyph cache) together changed the
behavior of drawing scaled cached glyphs in the GL engine, producing
blocky text drawing when using the FreeType font engine.
Whereas before we would cache all glyphs without any transform, and
let the paint engine take care of the transform, commit b5922c added
support for a scaled GL glyph cache, resulting in a 2x cache, drawn
at 1x for example. The problem was that the FreeType engine claimed
to support producing glyphs at 2x, but did that using the QFontEngine
baseclass implementations, which use a simple fast-transform to scale
up the glyphs. The result was a 2x cache with horrible looking glyphs.
The first step in fixing this issue was to have the FreeType engine
claim to only support translations. This would then make the paint
engine choose path-based drawing for all scaled text, which is slow.
To restore the optimization that we would draw 0.5x-2.0x scaled text
using a smooth-scale in the GL engine (which was removed in 155a206),
we then needed to extend shouldDrawCachedGlyphs() and add a special
condition for this, coupled with a bit of logic in drawCachedGlyphs()
that ensures we don't propagate the painter scale to the glyph cache
if the engine is not able to produce scaled glyphs for it. This
means we get the old behavior of the GL engine doing a smooth scale
of the 1x glyph in the cache.
Finally, since the raster engine also checks if the font engine
supports the current transform, but for FreeType then ends up in
a separate code path when actually drawing the glyphs (as the
FreeType font engine supports internal glyph caching), we need
to add a corresponding check for hasInternalCaching() in the
shouldDrawCachedGlyphs() function, now that the FreeType engine
only reports that it supports translations.
Change-Id: Id03de896dec5f29535b281fb235332ef018045d8
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com>
2013-07-05 13:40:19 +00:00
|
|
|
|
// The paint engine does not support projected cached glyph drawing
|
|
|
|
|
if (t.type() == QTransform::TxProject)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// The font engine might not support filling the glyph cache
|
|
|
|
|
// with the given transform applied, in which case we need to
|
|
|
|
|
// fall back to the QPainterPath code-path.
|
|
|
|
|
if (!fontEngine->supportsTransformation(t)) {
|
|
|
|
|
// Except that drawing paths is slow, so for scales between
|
|
|
|
|
// 0.5 and 2.0 we leave the glyph cache untransformed and deal
|
|
|
|
|
// with the transform ourselves when painting, resulting in
|
|
|
|
|
// drawing 1x cached glyphs with a smooth-scale.
|
|
|
|
|
float det = t.determinant();
|
|
|
|
|
if (det >= 0.25f && det <= 4.f) {
|
|
|
|
|
// Assuming the baseclass still agrees
|
|
|
|
|
return QPaintEngineEx::shouldDrawCachedGlyphs(fontEngine, t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false; // Fall back to path-drawing
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return QPaintEngineEx::shouldDrawCachedGlyphs(fontEngine, t);
|
2013-01-10 14:09:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-02-10 12:59:44 +00:00
|
|
|
|
void QOpenGL2PaintEngineExPrivate::drawCachedGlyphs(QFontEngine::GlyphFormat glyphFormat,
|
2011-08-22 08:49:28 +00:00
|
|
|
|
QStaticTextItem *staticTextItem)
|
|
|
|
|
{
|
|
|
|
|
Q_Q(QOpenGL2PaintEngineEx);
|
|
|
|
|
|
|
|
|
|
QOpenGL2PaintEngineState *s = q->state();
|
|
|
|
|
|
2017-01-23 15:07:36 +00:00
|
|
|
|
void *cacheKey = ctx; // use context, not the shareGroup() -> the GL glyph cache uses FBOs which may not be shareable
|
2011-08-22 08:49:28 +00:00
|
|
|
|
bool recreateVertexArrays = false;
|
|
|
|
|
|
Restore smooth-scaled drawing of 0.5x-2.0x scaled glyphs in the GL engine
Commit b5922c89ba942 (Add support for retina glyph-based text drawing
in the GL engine), and commit 155a20628b91 (Use QPaintEngineEx to decide
if font is too big for using the glyph cache) together changed the
behavior of drawing scaled cached glyphs in the GL engine, producing
blocky text drawing when using the FreeType font engine.
Whereas before we would cache all glyphs without any transform, and
let the paint engine take care of the transform, commit b5922c added
support for a scaled GL glyph cache, resulting in a 2x cache, drawn
at 1x for example. The problem was that the FreeType engine claimed
to support producing glyphs at 2x, but did that using the QFontEngine
baseclass implementations, which use a simple fast-transform to scale
up the glyphs. The result was a 2x cache with horrible looking glyphs.
The first step in fixing this issue was to have the FreeType engine
claim to only support translations. This would then make the paint
engine choose path-based drawing for all scaled text, which is slow.
To restore the optimization that we would draw 0.5x-2.0x scaled text
using a smooth-scale in the GL engine (which was removed in 155a206),
we then needed to extend shouldDrawCachedGlyphs() and add a special
condition for this, coupled with a bit of logic in drawCachedGlyphs()
that ensures we don't propagate the painter scale to the glyph cache
if the engine is not able to produce scaled glyphs for it. This
means we get the old behavior of the GL engine doing a smooth scale
of the 1x glyph in the cache.
Finally, since the raster engine also checks if the font engine
supports the current transform, but for FreeType then ends up in
a separate code path when actually drawing the glyphs (as the
FreeType font engine supports internal glyph caching), we need
to add a corresponding check for hasInternalCaching() in the
shouldDrawCachedGlyphs() function, now that the FreeType engine
only reports that it supports translations.
Change-Id: Id03de896dec5f29535b281fb235332ef018045d8
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com>
2013-07-05 13:40:19 +00:00
|
|
|
|
QTransform glyphCacheTransform;
|
|
|
|
|
QFontEngine *fe = staticTextItem->fontEngine();
|
|
|
|
|
if (fe->supportsTransformation(s->matrix)) {
|
|
|
|
|
// The font-engine supports rendering glyphs with the current transform, so we
|
|
|
|
|
// build a glyph-cache with the scale pre-applied, so that the cache contains
|
|
|
|
|
// glyphs with the appropriate resolution in the case of retina displays.
|
|
|
|
|
glyphCacheTransform = s->matrix.type() < QTransform::TxRotate ?
|
|
|
|
|
QTransform::fromScale(qAbs(s->matrix.m11()), qAbs(s->matrix.m22())) :
|
|
|
|
|
QTransform::fromScale(
|
|
|
|
|
QVector2D(s->matrix.m11(), s->matrix.m12()).length(),
|
|
|
|
|
QVector2D(s->matrix.m21(), s->matrix.m22()).length());
|
|
|
|
|
}
|
2013-01-15 14:32:41 +00:00
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
QOpenGLTextureGlyphCache *cache =
|
2014-02-10 12:59:44 +00:00
|
|
|
|
(QOpenGLTextureGlyphCache *) fe->glyphCache(cacheKey, glyphFormat, glyphCacheTransform);
|
2019-11-22 13:46:58 +00:00
|
|
|
|
if (!cache || cache->glyphFormat() != glyphFormat || cache->contextGroup() == nullptr) {
|
2014-02-10 12:59:44 +00:00
|
|
|
|
cache = new QOpenGLTextureGlyphCache(glyphFormat, glyphCacheTransform);
|
2011-11-04 13:47:31 +00:00
|
|
|
|
fe->setGlyphCache(cacheKey, cache);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
recreateVertexArrays = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (staticTextItem->userDataNeedsUpdate) {
|
|
|
|
|
recreateVertexArrays = true;
|
2019-11-22 13:46:58 +00:00
|
|
|
|
} else if (staticTextItem->userData() == nullptr) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
recreateVertexArrays = true;
|
|
|
|
|
} else if (staticTextItem->userData()->type != QStaticTextUserData::OpenGLUserData) {
|
|
|
|
|
recreateVertexArrays = true;
|
|
|
|
|
} else {
|
|
|
|
|
QOpenGLStaticTextUserData *userData = static_cast<QOpenGLStaticTextUserData *>(staticTextItem->userData());
|
2014-02-10 12:59:44 +00:00
|
|
|
|
if (userData->glyphFormat != glyphFormat) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
recreateVertexArrays = true;
|
|
|
|
|
} else if (userData->cacheSerialNumber != cache->serialNumber()) {
|
|
|
|
|
recreateVertexArrays = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We only need to update the cache with new glyphs if we are actually going to recreate the vertex arrays.
|
|
|
|
|
// If the cache size has changed, we do need to regenerate the vertices, but we don't need to repopulate the
|
|
|
|
|
// cache so this text is performed before we test if the cache size has changed.
|
|
|
|
|
if (recreateVertexArrays) {
|
|
|
|
|
cache->setPaintEnginePrivate(this);
|
2011-11-04 13:47:31 +00:00
|
|
|
|
if (!cache->populate(fe, staticTextItem->numGlyphs,
|
2011-08-22 08:49:28 +00:00
|
|
|
|
staticTextItem->glyphs, staticTextItem->glyphPositions)) {
|
|
|
|
|
// No space for glyphs in cache. We need to reset it and try again.
|
|
|
|
|
cache->clear();
|
2011-11-04 13:47:31 +00:00
|
|
|
|
cache->populate(fe, staticTextItem->numGlyphs,
|
2011-08-22 08:49:28 +00:00
|
|
|
|
staticTextItem->glyphs, staticTextItem->glyphPositions);
|
|
|
|
|
}
|
2014-12-09 15:50:05 +00:00
|
|
|
|
|
|
|
|
|
if (cache->hasPendingGlyphs()) {
|
|
|
|
|
// Filling in the glyphs binds and sets parameters, so we need to
|
|
|
|
|
// ensure that the glyph cache doesn't mess with whatever unit
|
|
|
|
|
// is currently active. Note that the glyph cache internally
|
|
|
|
|
// uses the image texture unit for blitting to the cache, while
|
|
|
|
|
// we switch between image and mask units when drawing.
|
|
|
|
|
static const GLenum glypchCacheTextureUnit = QT_IMAGE_TEXTURE_UNIT;
|
2014-12-08 14:31:37 +00:00
|
|
|
|
activateTextureUnit(glypchCacheTextureUnit);
|
2014-12-09 15:50:05 +00:00
|
|
|
|
|
|
|
|
|
cache->fillInPendingGlyphs();
|
|
|
|
|
|
|
|
|
|
// We assume the cache can be trusted on which texture was bound
|
|
|
|
|
lastTextureUsed = cache->texture();
|
|
|
|
|
|
|
|
|
|
// But since the brush and image texture units are possibly shared
|
|
|
|
|
// we may have to re-bind brush textures after filling in the cache.
|
|
|
|
|
brushTextureDirty = (QT_BRUSH_TEXTURE_UNIT == glypchCacheTextureUnit);
|
|
|
|
|
}
|
2018-08-22 11:02:04 +00:00
|
|
|
|
cache->setPaintEnginePrivate(nullptr);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cache->width() == 0 || cache->height() == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
2014-02-10 12:59:44 +00:00
|
|
|
|
if (glyphFormat == QFontEngine::Format_ARGB)
|
2013-02-11 17:16:49 +00:00
|
|
|
|
transferMode(ImageArrayDrawingMode);
|
|
|
|
|
else
|
|
|
|
|
transferMode(TextDrawingMode);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-02-10 12:59:44 +00:00
|
|
|
|
int margin = fe->glyphMargin(glyphFormat);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
GLfloat dx = 1.0 / cache->width();
|
|
|
|
|
GLfloat dy = 1.0 / cache->height();
|
|
|
|
|
|
|
|
|
|
// Use global arrays by default
|
|
|
|
|
QOpenGL2PEXVertexArray *vertexCoordinates = &vertexCoordinateArray;
|
|
|
|
|
QOpenGL2PEXVertexArray *textureCoordinates = &textureCoordinateArray;
|
|
|
|
|
|
|
|
|
|
if (staticTextItem->useBackendOptimizations) {
|
2019-11-22 13:46:58 +00:00
|
|
|
|
QOpenGLStaticTextUserData *userData = nullptr;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2019-11-22 13:46:58 +00:00
|
|
|
|
if (staticTextItem->userData() == nullptr
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|| staticTextItem->userData()->type != QStaticTextUserData::OpenGLUserData) {
|
|
|
|
|
|
|
|
|
|
userData = new QOpenGLStaticTextUserData();
|
|
|
|
|
staticTextItem->setUserData(userData);
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
userData = static_cast<QOpenGLStaticTextUserData*>(staticTextItem->userData());
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-10 12:59:44 +00:00
|
|
|
|
userData->glyphFormat = glyphFormat;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
userData->cacheSerialNumber = cache->serialNumber();
|
|
|
|
|
|
|
|
|
|
// Use cache if backend optimizations is turned on
|
|
|
|
|
vertexCoordinates = &userData->vertexCoordinateArray;
|
|
|
|
|
textureCoordinates = &userData->textureCoordinateArray;
|
|
|
|
|
|
|
|
|
|
QSize size(cache->width(), cache->height());
|
|
|
|
|
if (userData->cacheSize != size) {
|
|
|
|
|
recreateVertexArrays = true;
|
|
|
|
|
userData->cacheSize = size;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (recreateVertexArrays) {
|
|
|
|
|
vertexCoordinates->clear();
|
|
|
|
|
textureCoordinates->clear();
|
|
|
|
|
|
2011-11-04 13:47:31 +00:00
|
|
|
|
bool supportsSubPixelPositions = fe->supportsSubPixelPositions();
|
2011-08-22 08:49:28 +00:00
|
|
|
|
for (int i=0; i<staticTextItem->numGlyphs; ++i) {
|
|
|
|
|
QFixed subPixelPosition;
|
|
|
|
|
if (supportsSubPixelPositions)
|
2011-11-04 13:47:31 +00:00
|
|
|
|
subPixelPosition = fe->subPixelPositionForX(staticTextItem->glyphPositions[i].x);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
QTextureGlyphCache::GlyphAndSubPixelPosition glyph(staticTextItem->glyphs[i], subPixelPosition);
|
|
|
|
|
|
|
|
|
|
const QTextureGlyphCache::Coord &c = cache->coords[glyph];
|
|
|
|
|
if (c.isNull())
|
|
|
|
|
continue;
|
|
|
|
|
|
2013-01-15 14:32:41 +00:00
|
|
|
|
int x = qFloor(staticTextItem->glyphPositions[i].x.toReal() * cache->transform().m11()) + c.baseLineX - margin;
|
|
|
|
|
int y = qRound(staticTextItem->glyphPositions[i].y.toReal() * cache->transform().m22()) - c.baseLineY - margin;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
vertexCoordinates->addQuad(QRectF(x, y, c.w, c.h));
|
|
|
|
|
textureCoordinates->addQuad(QRectF(c.x*dx, c.y*dy, c.w * dx, c.h * dy));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
staticTextItem->userDataNeedsUpdate = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int numGlyphs = vertexCoordinates->vertexCount() / 4;
|
|
|
|
|
if (numGlyphs == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (elementIndices.size() < numGlyphs*6) {
|
|
|
|
|
Q_ASSERT(elementIndices.size() % 6 == 0);
|
|
|
|
|
int j = elementIndices.size() / 6 * 4;
|
|
|
|
|
while (j < numGlyphs*4) {
|
|
|
|
|
elementIndices.append(j + 0);
|
|
|
|
|
elementIndices.append(j + 0);
|
|
|
|
|
elementIndices.append(j + 1);
|
|
|
|
|
elementIndices.append(j + 2);
|
|
|
|
|
elementIndices.append(j + 3);
|
|
|
|
|
elementIndices.append(j + 3);
|
|
|
|
|
|
|
|
|
|
j += 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
|
|
|
|
|
if (elementIndicesVBOId == 0)
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glGenBuffers(1, &elementIndicesVBOId);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementIndicesVBOId);
|
|
|
|
|
funcs.glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementIndices.size() * sizeof(GLushort),
|
|
|
|
|
elementIndices.constData(), GL_STATIC_DRAW);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#endif
|
|
|
|
|
} else {
|
|
|
|
|
#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementIndicesVBOId);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-10 12:59:44 +00:00
|
|
|
|
if (glyphFormat != QFontEngine::Format_ARGB || recreateVertexArrays) {
|
2016-07-27 13:45:31 +00:00
|
|
|
|
uploadData(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinates->data(), vertexCoordinates->vertexCount() * 2);
|
|
|
|
|
uploadData(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinates->data(), textureCoordinates->vertexCount() * 2);
|
2013-02-11 17:16:49 +00:00
|
|
|
|
}
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
if (!snapToPixelGrid) {
|
|
|
|
|
snapToPixelGrid = true;
|
|
|
|
|
matrixDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QBrush pensBrush = q->state()->pen.brush();
|
|
|
|
|
setBrush(pensBrush);
|
|
|
|
|
|
2014-02-10 12:59:44 +00:00
|
|
|
|
if (glyphFormat == QFontEngine::Format_A32) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
// Subpixel antialiasing without gamma correction
|
|
|
|
|
|
|
|
|
|
QPainter::CompositionMode compMode = q->state()->composition_mode;
|
|
|
|
|
Q_ASSERT(compMode == QPainter::CompositionMode_Source
|
|
|
|
|
|| compMode == QPainter::CompositionMode_SourceOver);
|
|
|
|
|
|
|
|
|
|
shaderManager->setMaskType(QOpenGLEngineShaderManager::SubPixelMaskPass1);
|
|
|
|
|
|
|
|
|
|
if (pensBrush.style() == Qt::SolidPattern) {
|
|
|
|
|
// Solid patterns can get away with only one pass.
|
|
|
|
|
QColor c = pensBrush.color();
|
|
|
|
|
qreal oldOpacity = q->state()->opacity;
|
|
|
|
|
if (compMode == QPainter::CompositionMode_Source) {
|
|
|
|
|
c = qt_premultiplyColor(c, q->state()->opacity);
|
|
|
|
|
q->state()->opacity = 1;
|
|
|
|
|
opacityUniformDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compositionModeDirty = false; // I can handle this myself, thank you very much
|
2013-01-15 14:32:41 +00:00
|
|
|
|
prepareForCachedGlyphDraw(*cache);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2013-01-15 14:32:41 +00:00
|
|
|
|
// prepareForCachedGlyphDraw() have set the opacity on the current shader, so the opacity state can now be reset.
|
2011-08-22 08:49:28 +00:00
|
|
|
|
if (compMode == QPainter::CompositionMode_Source) {
|
|
|
|
|
q->state()->opacity = oldOpacity;
|
|
|
|
|
opacityUniformDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glEnable(GL_BLEND);
|
|
|
|
|
funcs.glBlendFunc(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR);
|
2011-08-29 12:13:08 +00:00
|
|
|
|
funcs.glBlendColor(c.redF(), c.greenF(), c.blueF(), c.alphaF());
|
2011-08-22 08:49:28 +00:00
|
|
|
|
} else {
|
|
|
|
|
// Other brush styles need two passes.
|
|
|
|
|
|
|
|
|
|
qreal oldOpacity = q->state()->opacity;
|
|
|
|
|
if (compMode == QPainter::CompositionMode_Source) {
|
|
|
|
|
q->state()->opacity = 1;
|
|
|
|
|
opacityUniformDirty = true;
|
|
|
|
|
pensBrush = Qt::white;
|
|
|
|
|
setBrush(pensBrush);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compositionModeDirty = false; // I can handle this myself, thank you very much
|
2013-01-15 14:32:41 +00:00
|
|
|
|
prepareForCachedGlyphDraw(*cache);
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glEnable(GL_BLEND);
|
|
|
|
|
funcs.glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-12-02 19:16:23 +00:00
|
|
|
|
updateTexture(QT_MASK_TEXTURE_UNIT, cache->texture(), GL_REPEAT, GL_NEAREST, ForceUpdate);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, 0);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#else
|
2016-07-27 13:45:31 +00:00
|
|
|
|
const bool useIndexVbo = uploadIndexData(elementIndices.data(), GL_UNSIGNED_SHORT, 6 * numGlyphs);
|
|
|
|
|
funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, useIndexVbo ? nullptr : elementIndices.data());
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
shaderManager->setMaskType(QOpenGLEngineShaderManager::SubPixelMaskPass2);
|
|
|
|
|
|
|
|
|
|
if (compMode == QPainter::CompositionMode_Source) {
|
|
|
|
|
q->state()->opacity = oldOpacity;
|
|
|
|
|
opacityUniformDirty = true;
|
|
|
|
|
pensBrush = q->state()->pen.brush();
|
|
|
|
|
setBrush(pensBrush);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compositionModeDirty = false;
|
2013-01-15 14:32:41 +00:00
|
|
|
|
prepareForCachedGlyphDraw(*cache);
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glEnable(GL_BLEND);
|
|
|
|
|
funcs.glBlendFunc(GL_ONE, GL_ONE);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
compositionModeDirty = true;
|
2014-02-10 12:59:44 +00:00
|
|
|
|
} else if (glyphFormat == QFontEngine::Format_ARGB) {
|
2013-02-11 17:16:49 +00:00
|
|
|
|
currentBrush = noBrush;
|
|
|
|
|
shaderManager->setSrcPixelType(QOpenGLEngineShaderManager::ImageSrc);
|
|
|
|
|
if (prepareForCachedGlyphDraw(*cache))
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::ImageTexture), QT_IMAGE_TEXTURE_UNIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
} else {
|
2014-08-08 07:51:34 +00:00
|
|
|
|
// Grayscale/mono glyphs
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
shaderManager->setMaskType(QOpenGLEngineShaderManager::PixelMask);
|
2013-01-15 14:32:41 +00:00
|
|
|
|
prepareForCachedGlyphDraw(*cache);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-12-10 14:26:36 +00:00
|
|
|
|
GLenum textureUnit = QT_MASK_TEXTURE_UNIT;
|
|
|
|
|
if (glyphFormat == QFontEngine::Format_ARGB)
|
|
|
|
|
textureUnit = QT_IMAGE_TEXTURE_UNIT;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-12-10 14:26:36 +00:00
|
|
|
|
QOpenGLTextureGlyphCache::FilterMode filterMode = (s->matrix.type() > QTransform::TxTranslate) ?
|
|
|
|
|
QOpenGLTextureGlyphCache::Linear : QOpenGLTextureGlyphCache::Nearest;
|
2013-02-11 17:16:49 +00:00
|
|
|
|
|
2014-12-10 14:26:36 +00:00
|
|
|
|
GLenum glFilterMode = filterMode == QOpenGLTextureGlyphCache::Linear ? GL_LINEAR : GL_NEAREST;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-12-10 14:26:36 +00:00
|
|
|
|
TextureUpdateMode updateMode = UpdateIfNeeded;
|
|
|
|
|
if (cache->filterMode() != filterMode) {
|
|
|
|
|
updateMode = ForceUpdate;
|
|
|
|
|
cache->setFilterMode(filterMode);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-12-10 14:26:36 +00:00
|
|
|
|
updateTexture(textureUnit, cache->texture(), GL_REPEAT, glFilterMode, updateMode);
|
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO)
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, 0);
|
|
|
|
|
funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#else
|
2016-07-27 13:45:31 +00:00
|
|
|
|
const bool useIndexVbo = uploadIndexData(elementIndices.data(), GL_UNSIGNED_SHORT, 6 * numGlyphs);
|
|
|
|
|
funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, useIndexVbo ? nullptr : elementIndices.data());
|
2011-08-22 08:49:28 +00:00
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::drawPixmapFragments(const QPainter::PixmapFragment *fragments, int fragmentCount, const QPixmap &pixmap,
|
|
|
|
|
QPainter::PixmapFragmentHints hints)
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
// Use fallback for extended composition modes.
|
|
|
|
|
if (state()->composition_mode > QPainter::CompositionMode_Plus) {
|
|
|
|
|
QPaintEngineEx::drawPixmapFragments(fragments, fragmentCount, pixmap, hints);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensureActive();
|
|
|
|
|
int max_texture_size = d->ctx->d_func()->maxTextureSize();
|
|
|
|
|
if (pixmap.width() > max_texture_size || pixmap.height() > max_texture_size) {
|
|
|
|
|
QPixmap scaled = pixmap.scaled(max_texture_size, max_texture_size, Qt::KeepAspectRatio);
|
|
|
|
|
d->drawPixmapFragments(fragments, fragmentCount, scaled, hints);
|
|
|
|
|
} else {
|
|
|
|
|
d->drawPixmapFragments(fragments, fragmentCount, pixmap, hints);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::drawPixmapFragments(const QPainter::PixmapFragment *fragments,
|
|
|
|
|
int fragmentCount, const QPixmap &pixmap,
|
|
|
|
|
QPainter::PixmapFragmentHints hints)
|
|
|
|
|
{
|
|
|
|
|
GLfloat dx = 1.0f / pixmap.size().width();
|
|
|
|
|
GLfloat dy = 1.0f / pixmap.size().height();
|
|
|
|
|
|
|
|
|
|
vertexCoordinateArray.clear();
|
|
|
|
|
textureCoordinateArray.clear();
|
|
|
|
|
opacityArray.reset();
|
|
|
|
|
|
|
|
|
|
if (snapToPixelGrid) {
|
|
|
|
|
snapToPixelGrid = false;
|
|
|
|
|
matrixDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool allOpaque = true;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < fragmentCount; ++i) {
|
|
|
|
|
qreal s = 0;
|
|
|
|
|
qreal c = 1;
|
|
|
|
|
if (fragments[i].rotation != 0) {
|
2015-02-26 12:53:00 +00:00
|
|
|
|
s = qFastSin(qDegreesToRadians(fragments[i].rotation));
|
|
|
|
|
c = qFastCos(qDegreesToRadians(fragments[i].rotation));
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qreal right = 0.5 * fragments[i].scaleX * fragments[i].width;
|
|
|
|
|
qreal bottom = 0.5 * fragments[i].scaleY * fragments[i].height;
|
|
|
|
|
QOpenGLPoint bottomRight(right * c - bottom * s, right * s + bottom * c);
|
|
|
|
|
QOpenGLPoint bottomLeft(-right * c - bottom * s, -right * s + bottom * c);
|
|
|
|
|
|
|
|
|
|
vertexCoordinateArray.addVertex(bottomRight.x + fragments[i].x, bottomRight.y + fragments[i].y);
|
|
|
|
|
vertexCoordinateArray.addVertex(-bottomLeft.x + fragments[i].x, -bottomLeft.y + fragments[i].y);
|
|
|
|
|
vertexCoordinateArray.addVertex(-bottomRight.x + fragments[i].x, -bottomRight.y + fragments[i].y);
|
|
|
|
|
vertexCoordinateArray.addVertex(-bottomRight.x + fragments[i].x, -bottomRight.y + fragments[i].y);
|
|
|
|
|
vertexCoordinateArray.addVertex(bottomLeft.x + fragments[i].x, bottomLeft.y + fragments[i].y);
|
|
|
|
|
vertexCoordinateArray.addVertex(bottomRight.x + fragments[i].x, bottomRight.y + fragments[i].y);
|
|
|
|
|
|
|
|
|
|
QOpenGLRect src(fragments[i].sourceLeft * dx, fragments[i].sourceTop * dy,
|
|
|
|
|
(fragments[i].sourceLeft + fragments[i].width) * dx,
|
|
|
|
|
(fragments[i].sourceTop + fragments[i].height) * dy);
|
|
|
|
|
|
|
|
|
|
textureCoordinateArray.addVertex(src.right, src.bottom);
|
|
|
|
|
textureCoordinateArray.addVertex(src.right, src.top);
|
|
|
|
|
textureCoordinateArray.addVertex(src.left, src.top);
|
|
|
|
|
textureCoordinateArray.addVertex(src.left, src.top);
|
|
|
|
|
textureCoordinateArray.addVertex(src.left, src.bottom);
|
|
|
|
|
textureCoordinateArray.addVertex(src.right, src.bottom);
|
|
|
|
|
|
|
|
|
|
qreal opacity = fragments[i].opacity * q->state()->opacity;
|
|
|
|
|
opacityArray << opacity << opacity << opacity << opacity << opacity << opacity;
|
|
|
|
|
allOpaque &= (opacity >= 0.99f);
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-02 17:38:06 +00:00
|
|
|
|
transferMode(ImageOpacityArrayDrawingMode);
|
|
|
|
|
|
2014-12-02 19:16:23 +00:00
|
|
|
|
GLenum filterMode = q->state()->renderHints & QPainter::SmoothPixmapTransform ? GL_LINEAR : GL_NEAREST;
|
|
|
|
|
updateTexture(QT_IMAGE_TEXTURE_UNIT, pixmap, GL_CLAMP_TO_EDGE, filterMode);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
bool isBitmap = pixmap.isQBitmap();
|
|
|
|
|
bool isOpaque = !isBitmap && (!pixmap.hasAlpha() || (hints & QPainter::OpaqueHint)) && allOpaque;
|
|
|
|
|
|
|
|
|
|
// Setup for texture drawing
|
|
|
|
|
currentBrush = noBrush;
|
|
|
|
|
shaderManager->setSrcPixelType(isBitmap ? QOpenGLEngineShaderManager::PatternSrc
|
|
|
|
|
: QOpenGLEngineShaderManager::ImageSrc);
|
|
|
|
|
if (prepareForDraw(isOpaque))
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::ImageTexture), QT_IMAGE_TEXTURE_UNIT);
|
|
|
|
|
|
|
|
|
|
if (isBitmap) {
|
|
|
|
|
QColor col = qt_premultiplyColor(q->state()->pen.color(), (GLfloat)q->state()->opacity);
|
|
|
|
|
shaderManager->currentProgram()->setUniformValue(location(QOpenGLEngineShaderManager::PatternColor), col);
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDrawArrays(GL_TRIANGLES, 0, 6 * fragmentCount);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QOpenGL2PaintEngineEx::begin(QPaintDevice *pdev)
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
|
2011-09-07 08:31:03 +00:00
|
|
|
|
Q_ASSERT(pdev->devType() == QInternal::OpenGL);
|
|
|
|
|
d->device = static_cast<QOpenGLPaintDevice*>(pdev);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
if (!d->device)
|
|
|
|
|
return false;
|
|
|
|
|
|
2014-06-20 09:58:34 +00:00
|
|
|
|
d->device->ensureActiveTarget();
|
|
|
|
|
|
2015-10-16 05:36:59 +00:00
|
|
|
|
if (d->device->context() != QOpenGLContext::currentContext() || !d->device->context()) {
|
2011-09-07 08:31:03 +00:00
|
|
|
|
qWarning("QPainter::begin(): QOpenGLPaintDevice's context needs to be current");
|
2011-08-22 08:49:28 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-27 13:45:31 +00:00
|
|
|
|
if (d->ctx != QOpenGLContext::currentContext()
|
|
|
|
|
|| (d->ctx && QOpenGLContext::currentContext() && d->ctx->format() != QOpenGLContext::currentContext()->format())) {
|
|
|
|
|
d->vertexBuffer.destroy();
|
|
|
|
|
d->texCoordBuffer.destroy();
|
|
|
|
|
d->opacityBuffer.destroy();
|
|
|
|
|
d->indexBuffer.destroy();
|
|
|
|
|
d->vao.destroy();
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
d->ctx = QOpenGLContext::currentContext();
|
|
|
|
|
d->ctx->d_func()->active_engine = this;
|
|
|
|
|
|
2014-10-14 10:07:41 +00:00
|
|
|
|
QOpenGLPaintDevicePrivate::get(d->device)->beginPaint();
|
2014-10-14 10:07:41 +00:00
|
|
|
|
|
2012-06-12 08:21:37 +00:00
|
|
|
|
d->funcs.initializeOpenGLFunctions();
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2016-07-27 13:45:31 +00:00
|
|
|
|
// Generate a new Vertex Array Object if we don't have one already. We can
|
|
|
|
|
// only hit the VAO-based path when using a core profile context. This is
|
|
|
|
|
// because while non-core contexts can support VAOs via extensions, legacy
|
|
|
|
|
// components like the QtOpenGL module do not know about VAOs. There are
|
|
|
|
|
// still tests for QGL-QOpenGL paint engine interoperability, so keep the
|
|
|
|
|
// status quo for now, and avoid introducing a VAO in non-core contexts.
|
|
|
|
|
const bool needsVAO = d->ctx->format().profile() == QSurfaceFormat::CoreProfile
|
|
|
|
|
&& d->ctx->format().version() >= qMakePair(3, 2);
|
|
|
|
|
if (needsVAO && !d->vao.isCreated()) {
|
|
|
|
|
bool created = d->vao.create();
|
|
|
|
|
|
|
|
|
|
// If we managed to create it then we have a profile that supports VAOs
|
|
|
|
|
if (created) {
|
|
|
|
|
d->vao.bind();
|
|
|
|
|
|
|
|
|
|
// Generate a new Vertex Buffer Object if we don't have one already
|
|
|
|
|
if (!d->vertexBuffer.isCreated()) {
|
|
|
|
|
d->vertexBuffer.create();
|
|
|
|
|
// Set its usage to StreamDraw, we will use this buffer only a few times before refilling it
|
|
|
|
|
d->vertexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
|
|
|
|
|
}
|
|
|
|
|
if (!d->texCoordBuffer.isCreated()) {
|
|
|
|
|
d->texCoordBuffer.create();
|
|
|
|
|
d->texCoordBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
|
|
|
|
|
}
|
|
|
|
|
if (!d->opacityBuffer.isCreated()) {
|
|
|
|
|
d->opacityBuffer.create();
|
|
|
|
|
d->opacityBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
|
|
|
|
|
}
|
|
|
|
|
if (!d->indexBuffer.isCreated()) {
|
|
|
|
|
d->indexBuffer.create();
|
|
|
|
|
d->indexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i)
|
|
|
|
|
d->vertexAttributeArraysEnabledState[i] = false;
|
|
|
|
|
|
|
|
|
|
const QSize sz = d->device->size();
|
|
|
|
|
d->width = sz.width();
|
|
|
|
|
d->height = sz.height();
|
|
|
|
|
d->mode = BrushDrawingMode;
|
|
|
|
|
d->brushTextureDirty = true;
|
|
|
|
|
d->brushUniformsDirty = true;
|
|
|
|
|
d->matrixUniformDirty = true;
|
|
|
|
|
d->matrixDirty = true;
|
|
|
|
|
d->compositionModeDirty = true;
|
|
|
|
|
d->opacityUniformDirty = true;
|
|
|
|
|
d->needsSync = true;
|
|
|
|
|
d->useSystemClip = !systemClip().isEmpty();
|
|
|
|
|
d->currentBrush = QBrush();
|
|
|
|
|
|
|
|
|
|
d->dirtyStencilRegion = QRect(0, 0, d->width, d->height);
|
|
|
|
|
d->stencilClean = true;
|
|
|
|
|
|
|
|
|
|
d->shaderManager = new QOpenGLEngineShaderManager(d->ctx);
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
d->funcs.glDisable(GL_STENCIL_TEST);
|
|
|
|
|
d->funcs.glDisable(GL_DEPTH_TEST);
|
|
|
|
|
d->funcs.glDisable(GL_SCISSOR_TEST);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-02-10 12:59:44 +00:00
|
|
|
|
d->glyphCacheFormat = QFontEngine::Format_A8;
|
2014-02-17 12:10:52 +00:00
|
|
|
|
|
2020-04-14 11:48:28 +00:00
|
|
|
|
#if !QT_CONFIG(opengles2)
|
2014-04-24 15:11:23 +00:00
|
|
|
|
if (!QOpenGLContext::currentContext()->isOpenGLES()) {
|
2014-03-04 14:06:36 +00:00
|
|
|
|
d->funcs.glDisable(GL_MULTISAMPLE);
|
2014-02-10 12:59:44 +00:00
|
|
|
|
d->glyphCacheFormat = QFontEngine::Format_A32;
|
Dynamic GL switch on Windows
The patch introduces a new build configuration on Windows which
can be requested by passing -opengl dynamic to configure.
Platforms other than Windows (including WinRT) are not affected.
The existing Angle and desktop configurations are not affected.
These continue to function as before and Angle remains the default.
In the future, when all modules have added support for the dynamic
path, as described below, the default configuration could be changed
to be the dynamic one. This would allow providing a single set of
binaries in the official builds instead of the current two.
When requesting dynamic GL, Angle is built but QT_OPENGL_ES[_2] are
never defined. Instead, the code path that has traditionally been
desktop GL only becomes the dynamic path that has to do runtime
checks. Qt modules and applications are not linked to opengl32.dll or
libegl/glesv2.dll in this case. Instead, QtGui exports all necessary
egl/egl/gl functions which will, under the hood, forward all requests
to a dynamically loaded EGL/WGL/GL implementation.
Porting guide (better said, changes needed to prepare your code to
work with dynamic GL builds when the fallback to Angle is utilized):
1. In !QT_OPENGL_ES[_2] code branches use QOpenGLFunctions::isES() to
differentiate between desktop and ES where needed. Keep in mind that
it is the desktop GL header (plus qopenglext.h) that is included,
not the GLES one.
QtGui's proxy will handle some differences, for example calling
glClearDepth will route to glClearDepthf when needed. The built-in
eglGetProcAddress is able to retrieve pointers for standard GLES2
functions too so code resolving OpenGL 2 functions will function
in any case.
2. QT_CONFIG will contain "opengl" and "dynamicgl" in dynamic builds,
but never "angle" or "opengles2".
3. The preprocessor define QT_OPENGL_DYNAMIC is also available in
dynamic builds. The usage of this is strongly discouraged and should
not be needed anywhere except for QtGui and the platform plugin.
4. Code in need of the library handle can use
QOpenGLFunctions::platformGLHandle().
The decision on which library to load is currently based on a simple
test that creates a dummy window/context and tries to resolve an
OpenGL 2 function. If this fails, it goes for Angle. This seems to work
well on Win7 PCs for example that do not have proper graphics drivers
providing OpenGL installed but are D3D9 capable using the default drivers.
Setting QT_OPENGL to desktop or angle skips the test and forces
usage of the given GL. There are also two new application attributes
that could be used for the same purpose.
If Angle is requested but the libraries are not present, desktop is
tried. If desktop is requested, or if angle is requested but nothing
works, the EGL/WGL functions will still be callable but will return 0.
This conveniently means that eglInitialize() and such will report a failure.
Debug messages can be enabled by setting QT_OPENGLPROXY_DEBUG. This will
tell which implementation is chosen.
The textures example application is ported to OpenGL 2, the GL 1
code path is removed.
[ChangeLog][QtGui] Qt builds on Windows can now be configured for
dynamic loading of the OpenGL implementation. This can be requested
by passing -opengl dynamic to configure. In this mode no modules will
link to opengl32.dll or Angle's libegl/libglesv2. Instead, QtGui will
dynamically choose between desktop and Angle during the first GL/EGL/WGL
call. This allows deploying applications with a single set of Qt libraries
with the ability of transparently falling back to Angle in case the
opengl32.dll is not suitable, due to missing graphics drivers for example.
Task-number: QTBUG-36483
Change-Id: I716fdebbf60b355b7d9ef57d1e069eef366b4ab9
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
Reviewed-by: Jørgen Lind <jorgen.lind@digia.com>
2014-01-27 13:45:11 +00:00
|
|
|
|
d->multisamplingAlwaysEnabled = false;
|
|
|
|
|
} else
|
2020-04-14 11:48:28 +00:00
|
|
|
|
#endif // !QT_CONFIG(opengles2)
|
Dynamic GL switch on Windows
The patch introduces a new build configuration on Windows which
can be requested by passing -opengl dynamic to configure.
Platforms other than Windows (including WinRT) are not affected.
The existing Angle and desktop configurations are not affected.
These continue to function as before and Angle remains the default.
In the future, when all modules have added support for the dynamic
path, as described below, the default configuration could be changed
to be the dynamic one. This would allow providing a single set of
binaries in the official builds instead of the current two.
When requesting dynamic GL, Angle is built but QT_OPENGL_ES[_2] are
never defined. Instead, the code path that has traditionally been
desktop GL only becomes the dynamic path that has to do runtime
checks. Qt modules and applications are not linked to opengl32.dll or
libegl/glesv2.dll in this case. Instead, QtGui exports all necessary
egl/egl/gl functions which will, under the hood, forward all requests
to a dynamically loaded EGL/WGL/GL implementation.
Porting guide (better said, changes needed to prepare your code to
work with dynamic GL builds when the fallback to Angle is utilized):
1. In !QT_OPENGL_ES[_2] code branches use QOpenGLFunctions::isES() to
differentiate between desktop and ES where needed. Keep in mind that
it is the desktop GL header (plus qopenglext.h) that is included,
not the GLES one.
QtGui's proxy will handle some differences, for example calling
glClearDepth will route to glClearDepthf when needed. The built-in
eglGetProcAddress is able to retrieve pointers for standard GLES2
functions too so code resolving OpenGL 2 functions will function
in any case.
2. QT_CONFIG will contain "opengl" and "dynamicgl" in dynamic builds,
but never "angle" or "opengles2".
3. The preprocessor define QT_OPENGL_DYNAMIC is also available in
dynamic builds. The usage of this is strongly discouraged and should
not be needed anywhere except for QtGui and the platform plugin.
4. Code in need of the library handle can use
QOpenGLFunctions::platformGLHandle().
The decision on which library to load is currently based on a simple
test that creates a dummy window/context and tries to resolve an
OpenGL 2 function. If this fails, it goes for Angle. This seems to work
well on Win7 PCs for example that do not have proper graphics drivers
providing OpenGL installed but are D3D9 capable using the default drivers.
Setting QT_OPENGL to desktop or angle skips the test and forces
usage of the given GL. There are also two new application attributes
that could be used for the same purpose.
If Angle is requested but the libraries are not present, desktop is
tried. If desktop is requested, or if angle is requested but nothing
works, the EGL/WGL functions will still be callable but will return 0.
This conveniently means that eglInitialize() and such will report a failure.
Debug messages can be enabled by setting QT_OPENGLPROXY_DEBUG. This will
tell which implementation is chosen.
The textures example application is ported to OpenGL 2, the GL 1
code path is removed.
[ChangeLog][QtGui] Qt builds on Windows can now be configured for
dynamic loading of the OpenGL implementation. This can be requested
by passing -opengl dynamic to configure. In this mode no modules will
link to opengl32.dll or Angle's libegl/libglesv2. Instead, QtGui will
dynamically choose between desktop and Angle during the first GL/EGL/WGL
call. This allows deploying applications with a single set of Qt libraries
with the ability of transparently falling back to Angle in case the
opengl32.dll is not suitable, due to missing graphics drivers for example.
Task-number: QTBUG-36483
Change-Id: I716fdebbf60b355b7d9ef57d1e069eef366b4ab9
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
Reviewed-by: Jørgen Lind <jorgen.lind@digia.com>
2014-01-27 13:45:11 +00:00
|
|
|
|
{
|
|
|
|
|
// OpenGL ES can't switch MSAA off, so if the gl paint device is
|
|
|
|
|
// multisampled, it's always multisampled.
|
|
|
|
|
d->multisamplingAlwaysEnabled = d->device->context()->format().samples() > 1;
|
|
|
|
|
}
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QOpenGL2PaintEngineEx::end()
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
|
2014-10-14 10:07:41 +00:00
|
|
|
|
QOpenGLPaintDevicePrivate::get(d->device)->endPaint();
|
2014-10-14 10:07:41 +00:00
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
QOpenGLContext *ctx = d->ctx;
|
|
|
|
|
d->funcs.glUseProgram(0);
|
|
|
|
|
d->transferMode(BrushDrawingMode);
|
|
|
|
|
|
2019-11-22 13:46:58 +00:00
|
|
|
|
ctx->d_func()->active_engine = nullptr;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
d->resetGLState();
|
|
|
|
|
|
|
|
|
|
delete d->shaderManager;
|
2019-11-22 13:46:58 +00:00
|
|
|
|
d->shaderManager = nullptr;
|
2011-08-22 08:49:28 +00:00
|
|
|
|
d->currentBrush = QBrush();
|
|
|
|
|
|
|
|
|
|
#ifdef QT_OPENGL_CACHE_AS_VBOS
|
|
|
|
|
if (!d->unusedVBOSToClean.isEmpty()) {
|
|
|
|
|
glDeleteBuffers(d->unusedVBOSToClean.size(), d->unusedVBOSToClean.constData());
|
|
|
|
|
d->unusedVBOSToClean.clear();
|
|
|
|
|
}
|
|
|
|
|
if (!d->unusedIBOSToClean.isEmpty()) {
|
|
|
|
|
glDeleteBuffers(d->unusedIBOSToClean.size(), d->unusedIBOSToClean.constData());
|
|
|
|
|
d->unusedIBOSToClean.clear();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::ensureActive()
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
QOpenGLContext *ctx = d->ctx;
|
|
|
|
|
|
2016-07-27 13:45:31 +00:00
|
|
|
|
if (d->vao.isCreated())
|
|
|
|
|
d->vao.bind();
|
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
if (isActive() && ctx->d_func()->active_engine != this) {
|
|
|
|
|
ctx->d_func()->active_engine = this;
|
|
|
|
|
d->needsSync = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (d->needsSync) {
|
2012-07-10 17:13:19 +00:00
|
|
|
|
d->device->ensureActiveTarget();
|
|
|
|
|
|
2011-08-22 08:49:28 +00:00
|
|
|
|
d->transferMode(BrushDrawingMode);
|
2014-03-04 14:06:36 +00:00
|
|
|
|
d->funcs.glViewport(0, 0, d->width, d->height);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
d->needsSync = false;
|
|
|
|
|
d->shaderManager->setDirty();
|
|
|
|
|
d->syncGlState();
|
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
|
|
|
d->vertexAttribPointers[i] = (GLfloat*)-1; // Assume the pointers are clobbered
|
|
|
|
|
setState(state());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::updateClipScissorTest()
|
|
|
|
|
{
|
|
|
|
|
Q_Q(QOpenGL2PaintEngineEx);
|
|
|
|
|
if (q->state()->clipTestEnabled) {
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glEnable(GL_STENCIL_TEST);
|
|
|
|
|
funcs.glStencilFunc(GL_LEQUAL, q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
} else {
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDisable(GL_STENCIL_TEST);
|
|
|
|
|
funcs.glStencilFunc(GL_ALWAYS, 0, 0xff);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef QT_GL_NO_SCISSOR_TEST
|
|
|
|
|
currentScissorBounds = QRect(0, 0, width, height);
|
|
|
|
|
#else
|
|
|
|
|
QRect bounds = q->state()->rectangleClip;
|
|
|
|
|
if (!q->state()->clipEnabled) {
|
|
|
|
|
if (useSystemClip)
|
|
|
|
|
bounds = systemClip.boundingRect();
|
|
|
|
|
else
|
|
|
|
|
bounds = QRect(0, 0, width, height);
|
|
|
|
|
} else {
|
|
|
|
|
if (useSystemClip)
|
|
|
|
|
bounds = bounds.intersected(systemClip.boundingRect());
|
|
|
|
|
else
|
|
|
|
|
bounds = bounds.intersected(QRect(0, 0, width, height));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentScissorBounds = bounds;
|
|
|
|
|
|
|
|
|
|
if (bounds == QRect(0, 0, width, height)) {
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glDisable(GL_SCISSOR_TEST);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
} else {
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glEnable(GL_SCISSOR_TEST);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
setScissor(bounds);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::setScissor(const QRect &rect)
|
|
|
|
|
{
|
|
|
|
|
const int left = rect.left();
|
|
|
|
|
const int width = rect.width();
|
|
|
|
|
int bottom = height - (rect.top() + rect.height());
|
2011-09-07 08:31:03 +00:00
|
|
|
|
if (device->paintFlipped()) {
|
2011-08-22 08:49:28 +00:00
|
|
|
|
bottom = rect.top();
|
|
|
|
|
}
|
|
|
|
|
const int height = rect.height();
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glScissor(left, bottom, width, height);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::clipEnabledChanged()
|
|
|
|
|
{
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
|
|
|
|
|
state()->clipChanged = true;
|
|
|
|
|
|
|
|
|
|
if (painter()->hasClipping())
|
|
|
|
|
d->regenerateClip();
|
|
|
|
|
else
|
|
|
|
|
d->systemStateChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::clearClip(uint value)
|
|
|
|
|
{
|
|
|
|
|
dirtyStencilRegion -= currentScissorBounds;
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilMask(0xff);
|
|
|
|
|
funcs.glClearStencil(value);
|
|
|
|
|
funcs.glClear(GL_STENCIL_BUFFER_BIT);
|
|
|
|
|
funcs.glStencilMask(0x0);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
q->state()->needsClipBufferClear = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::writeClip(const QVectorPath &path, uint value)
|
|
|
|
|
{
|
|
|
|
|
transferMode(BrushDrawingMode);
|
|
|
|
|
|
|
|
|
|
if (snapToPixelGrid) {
|
|
|
|
|
snapToPixelGrid = false;
|
|
|
|
|
matrixDirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (matrixDirty)
|
|
|
|
|
updateMatrix();
|
|
|
|
|
|
|
|
|
|
stencilClean = false;
|
|
|
|
|
|
|
|
|
|
const bool singlePass = !path.hasWindingFill()
|
|
|
|
|
&& (((q->state()->currentClip == maxClip - 1) && q->state()->clipTestEnabled)
|
|
|
|
|
|| q->state()->needsClipBufferClear);
|
|
|
|
|
const uint referenceClipValue = q->state()->needsClipBufferClear ? 1 : q->state()->currentClip;
|
|
|
|
|
|
|
|
|
|
if (q->state()->needsClipBufferClear)
|
|
|
|
|
clearClip(1);
|
|
|
|
|
|
|
|
|
|
if (path.isEmpty()) {
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glEnable(GL_STENCIL_TEST);
|
|
|
|
|
funcs.glStencilFunc(GL_LEQUAL, value, ~GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (q->state()->clipTestEnabled)
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_LEQUAL, q->state()->currentClip, ~GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
else
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_ALWAYS, 0, 0xff);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
vertexCoordinateArray.clear();
|
|
|
|
|
vertexCoordinateArray.addPath(path, inverseScale, false);
|
|
|
|
|
|
|
|
|
|
if (!singlePass)
|
|
|
|
|
fillStencilWithVertexArray(vertexCoordinateArray, path.hasWindingFill());
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glColorMask(false, false, false, false);
|
|
|
|
|
funcs.glEnable(GL_STENCIL_TEST);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
useSimpleShader();
|
|
|
|
|
|
|
|
|
|
if (singlePass) {
|
|
|
|
|
// Under these conditions we can set the new stencil value in a single
|
|
|
|
|
// pass, by using the current value and the "new value" as the toggles
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_LEQUAL, referenceClipValue, ~GL_STENCIL_HIGH_BIT);
|
|
|
|
|
funcs.glStencilOp(GL_KEEP, GL_INVERT, GL_INVERT);
|
|
|
|
|
funcs.glStencilMask(value ^ referenceClipValue);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
drawVertexArrays(vertexCoordinateArray, GL_TRIANGLE_FAN);
|
|
|
|
|
} else {
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
|
|
|
|
|
funcs.glStencilMask(0xff);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
if (!q->state()->clipTestEnabled && path.hasWindingFill()) {
|
|
|
|
|
// Pass when any clip bit is set, set high bit
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_NOTEQUAL, GL_STENCIL_HIGH_BIT, ~GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
composite(vertexCoordinateArray.boundingRect());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pass when high bit is set, replace stencil value with new clip value
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_NOTEQUAL, value, GL_STENCIL_HIGH_BIT);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
|
|
|
|
composite(vertexCoordinateArray.boundingRect());
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glStencilFunc(GL_LEQUAL, value, ~GL_STENCIL_HIGH_BIT);
|
|
|
|
|
funcs.glStencilMask(0);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
|
2014-03-04 14:06:36 +00:00
|
|
|
|
funcs.glColorMask(true, true, true, true);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::clip(const QVectorPath &path, Qt::ClipOperation op)
|
|
|
|
|
{
|
|
|
|
|
// qDebug("QOpenGL2PaintEngineEx::clip()");
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
|
|
|
|
|
state()->clipChanged = true;
|
|
|
|
|
|
|
|
|
|
ensureActive();
|
|
|
|
|
|
|
|
|
|
if (op == Qt::ReplaceClip) {
|
|
|
|
|
op = Qt::IntersectClip;
|
|
|
|
|
if (d->hasClipOperations()) {
|
|
|
|
|
d->systemStateChanged();
|
|
|
|
|
state()->canRestoreClip = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifndef QT_GL_NO_SCISSOR_TEST
|
|
|
|
|
if (!path.isEmpty() && op == Qt::IntersectClip && (path.shape() == QVectorPath::RectangleHint)) {
|
|
|
|
|
const QPointF* const points = reinterpret_cast<const QPointF*>(path.points());
|
|
|
|
|
QRectF rect(points[0], points[2]);
|
|
|
|
|
|
|
|
|
|
if (state()->matrix.type() <= QTransform::TxScale
|
|
|
|
|
|| (state()->matrix.type() == QTransform::TxRotate
|
|
|
|
|
&& qFuzzyIsNull(state()->matrix.m11())
|
|
|
|
|
&& qFuzzyIsNull(state()->matrix.m22())))
|
|
|
|
|
{
|
2013-12-12 19:48:35 +00:00
|
|
|
|
state()->rectangleClip = state()->rectangleClip.intersected(state()->matrix.mapRect(rect).toAlignedRect());
|
2011-08-22 08:49:28 +00:00
|
|
|
|
d->updateClipScissorTest();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
const QRect pathRect = state()->matrix.mapRect(path.controlPointRect()).toAlignedRect();
|
|
|
|
|
|
|
|
|
|
switch (op) {
|
|
|
|
|
case Qt::NoClip:
|
|
|
|
|
if (d->useSystemClip) {
|
|
|
|
|
state()->clipTestEnabled = true;
|
|
|
|
|
state()->currentClip = 1;
|
|
|
|
|
} else {
|
|
|
|
|
state()->clipTestEnabled = false;
|
|
|
|
|
}
|
|
|
|
|
state()->rectangleClip = QRect(0, 0, d->width, d->height);
|
|
|
|
|
state()->canRestoreClip = false;
|
|
|
|
|
d->updateClipScissorTest();
|
|
|
|
|
break;
|
|
|
|
|
case Qt::IntersectClip:
|
|
|
|
|
state()->rectangleClip = state()->rectangleClip.intersected(pathRect);
|
|
|
|
|
d->updateClipScissorTest();
|
|
|
|
|
d->resetClipIfNeeded();
|
|
|
|
|
++d->maxClip;
|
|
|
|
|
d->writeClip(path, d->maxClip);
|
|
|
|
|
state()->currentClip = d->maxClip;
|
|
|
|
|
state()->clipTestEnabled = true;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::regenerateClip()
|
|
|
|
|
{
|
|
|
|
|
systemStateChanged();
|
|
|
|
|
replayClipOperations();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::systemStateChanged()
|
|
|
|
|
{
|
|
|
|
|
Q_Q(QOpenGL2PaintEngineEx);
|
|
|
|
|
|
|
|
|
|
q->state()->clipChanged = true;
|
|
|
|
|
|
|
|
|
|
if (systemClip.isEmpty()) {
|
|
|
|
|
useSystemClip = false;
|
|
|
|
|
} else {
|
|
|
|
|
if (q->paintDevice()->devType() == QInternal::Widget && currentClipDevice) {
|
|
|
|
|
//QWidgetPrivate *widgetPrivate = qt_widget_private(static_cast<QWidget *>(currentClipDevice)->window());
|
|
|
|
|
//useSystemClip = widgetPrivate->extra && widgetPrivate->extra->inRenderWithPainter;
|
|
|
|
|
useSystemClip = true;
|
|
|
|
|
} else {
|
|
|
|
|
useSystemClip = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
q->state()->clipTestEnabled = false;
|
|
|
|
|
q->state()->needsClipBufferClear = true;
|
|
|
|
|
|
|
|
|
|
q->state()->currentClip = 1;
|
|
|
|
|
maxClip = 1;
|
|
|
|
|
|
|
|
|
|
q->state()->rectangleClip = useSystemClip ? systemClip.boundingRect() : QRect(0, 0, width, height);
|
|
|
|
|
updateClipScissorTest();
|
|
|
|
|
|
|
|
|
|
if (systemClip.rectCount() == 1) {
|
|
|
|
|
if (systemClip.boundingRect() == QRect(0, 0, width, height))
|
|
|
|
|
useSystemClip = false;
|
|
|
|
|
#ifndef QT_GL_NO_SCISSOR_TEST
|
|
|
|
|
// scissoring takes care of the system clip
|
|
|
|
|
return;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (useSystemClip) {
|
|
|
|
|
clearClip(0);
|
|
|
|
|
|
|
|
|
|
QPainterPath path;
|
|
|
|
|
path.addRegion(systemClip);
|
|
|
|
|
|
|
|
|
|
q->state()->currentClip = 0;
|
|
|
|
|
writeClip(qtVectorPathForPath(q->state()->matrix.inverted().map(path)), 1);
|
|
|
|
|
q->state()->currentClip = 1;
|
|
|
|
|
q->state()->clipTestEnabled = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineEx::setState(QPainterState *new_state)
|
|
|
|
|
{
|
|
|
|
|
// qDebug("QOpenGL2PaintEngineEx::setState()");
|
|
|
|
|
|
|
|
|
|
Q_D(QOpenGL2PaintEngineEx);
|
|
|
|
|
|
|
|
|
|
QOpenGL2PaintEngineState *s = static_cast<QOpenGL2PaintEngineState *>(new_state);
|
|
|
|
|
QOpenGL2PaintEngineState *old_state = state();
|
|
|
|
|
|
|
|
|
|
QPaintEngineEx::setState(s);
|
|
|
|
|
|
|
|
|
|
if (s->isNew) {
|
|
|
|
|
// Newly created state object. The call to setState()
|
|
|
|
|
// will either be followed by a call to begin(), or we are
|
|
|
|
|
// setting the state as part of a save().
|
|
|
|
|
s->isNew = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Setting the state as part of a restore().
|
|
|
|
|
|
|
|
|
|
if (old_state == s || old_state->renderHintsChanged)
|
|
|
|
|
renderHintsChanged();
|
|
|
|
|
|
|
|
|
|
if (old_state == s || old_state->matrixChanged)
|
|
|
|
|
d->matrixDirty = true;
|
|
|
|
|
|
|
|
|
|
if (old_state == s || old_state->compositionModeChanged)
|
|
|
|
|
d->compositionModeDirty = true;
|
|
|
|
|
|
|
|
|
|
if (old_state == s || old_state->opacityChanged)
|
|
|
|
|
d->opacityUniformDirty = true;
|
|
|
|
|
|
|
|
|
|
if (old_state == s || old_state->clipChanged) {
|
|
|
|
|
if (old_state && old_state != s && old_state->canRestoreClip) {
|
|
|
|
|
d->updateClipScissorTest();
|
2014-03-04 14:06:36 +00:00
|
|
|
|
d->funcs.glDepthFunc(GL_LEQUAL);
|
2011-08-22 08:49:28 +00:00
|
|
|
|
} else {
|
|
|
|
|
d->regenerateClip();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QPainterState *QOpenGL2PaintEngineEx::createState(QPainterState *orig) const
|
|
|
|
|
{
|
|
|
|
|
if (orig)
|
|
|
|
|
const_cast<QOpenGL2PaintEngineEx *>(this)->ensureActive();
|
|
|
|
|
|
|
|
|
|
QOpenGL2PaintEngineState *s;
|
|
|
|
|
if (!orig)
|
|
|
|
|
s = new QOpenGL2PaintEngineState();
|
|
|
|
|
else
|
|
|
|
|
s = new QOpenGL2PaintEngineState(*static_cast<QOpenGL2PaintEngineState *>(orig));
|
|
|
|
|
|
|
|
|
|
s->matrixChanged = false;
|
|
|
|
|
s->compositionModeChanged = false;
|
|
|
|
|
s->opacityChanged = false;
|
|
|
|
|
s->renderHintsChanged = false;
|
|
|
|
|
s->clipChanged = false;
|
|
|
|
|
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QOpenGL2PaintEngineState::QOpenGL2PaintEngineState(QOpenGL2PaintEngineState &other)
|
|
|
|
|
: QPainterState(other)
|
|
|
|
|
{
|
|
|
|
|
isNew = true;
|
|
|
|
|
needsClipBufferClear = other.needsClipBufferClear;
|
|
|
|
|
clipTestEnabled = other.clipTestEnabled;
|
|
|
|
|
currentClip = other.currentClip;
|
|
|
|
|
canRestoreClip = other.canRestoreClip;
|
|
|
|
|
rectangleClip = other.rectangleClip;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QOpenGL2PaintEngineState::QOpenGL2PaintEngineState()
|
|
|
|
|
{
|
|
|
|
|
isNew = true;
|
|
|
|
|
needsClipBufferClear = true;
|
|
|
|
|
clipTestEnabled = false;
|
|
|
|
|
canRestoreClip = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QOpenGL2PaintEngineState::~QOpenGL2PaintEngineState()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::setVertexAttribArrayEnabled(int arrayIndex, bool enabled)
|
|
|
|
|
{
|
|
|
|
|
Q_ASSERT(arrayIndex < QT_GL_VERTEX_ARRAY_TRACKED_COUNT);
|
|
|
|
|
|
|
|
|
|
if (vertexAttributeArraysEnabledState[arrayIndex] && !enabled)
|
|
|
|
|
funcs.glDisableVertexAttribArray(arrayIndex);
|
|
|
|
|
|
|
|
|
|
if (!vertexAttributeArraysEnabledState[arrayIndex] && enabled)
|
|
|
|
|
funcs.glEnableVertexAttribArray(arrayIndex);
|
|
|
|
|
|
|
|
|
|
vertexAttributeArraysEnabledState[arrayIndex] = enabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QOpenGL2PaintEngineExPrivate::syncGlState()
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i) {
|
|
|
|
|
if (vertexAttributeArraysEnabledState[i])
|
|
|
|
|
funcs.glEnableVertexAttribArray(i);
|
|
|
|
|
else
|
|
|
|
|
funcs.glDisableVertexAttribArray(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QT_END_NAMESPACE
|