Port IconItem to native QSGTexture

Port IconItem to native QSGTexture including the animation.
This will save constantly uploading a new texture to OpenGL throughout the animation.

REVIEW: 116024
This commit is contained in:
David Edmundson 2014-05-12 18:47:49 +02:00 committed by David Edmundson
parent 5600cb0fea
commit 096c6d247b
5 changed files with 280 additions and 176 deletions

View File

@ -21,6 +21,7 @@ set(corebindings_SRCS
datasource.cpp
# runnermodel.cpp
svgitem.cpp
fadingnode.cpp
framesvgitem.cpp
tooltip.cpp
tooltipdialog.cpp

View File

@ -0,0 +1,145 @@
/*
* Copyright (C) 2014 David Edmundson <davidedmundson@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "fadingnode_p.h"
#include <QSGSimpleMaterialShader>
#include <QtOpenGL/QGLContext>
#include <QtOpenGL/QGLFunctions>
struct FadingMaterialState
{
QSGTexture *source;
QSGTexture *target;
qreal progress;
};
class FadingMaterialShader : public QSGSimpleMaterialShader<FadingMaterialState>
{
QSG_DECLARE_SIMPLE_SHADER(FadingMaterialShader, FadingMaterialState)
public:
virtual const char* fragmentShader() const;
virtual const char* vertexShader() const;
virtual void updateState(const FadingMaterialState* newState, const FadingMaterialState* oldState);
virtual QList<QByteArray> attributes() const;
virtual void initialize();
private:
QOpenGLFunctions *glFuncs;
int m_progressId;
};
QList<QByteArray> FadingMaterialShader::attributes() const
{
return QList<QByteArray>() << "qt_Vertex" << "qt_MultiTexCoord0";
}
const char* FadingMaterialShader::vertexShader() const
{
return "uniform highp mat4 qt_Matrix;"
"attribute highp vec4 qt_Vertex;"
"attribute highp vec2 qt_MultiTexCoord0;"
"varying highp vec2 v_coord;"
"void main() {"
" v_coord = qt_MultiTexCoord0;"
" gl_Position = qt_Matrix * qt_Vertex;"
" }";
}
const char* FadingMaterialShader::fragmentShader() const
{
return "varying highp vec2 v_coord;"
"uniform sampler2D u_src;"
"uniform sampler2D u_target;"
"uniform highp float u_transitionProgress;"
"uniform lowp float qt_Opacity;"
"void main() {"
"lowp vec4 tex1 = texture2D(u_target, v_coord);"
"lowp vec4 tex2 = texture2D(u_src, v_coord);"
"gl_FragColor.rgb = mix(tex1.rgb, tex2.rgb, u_transitionProgress);"
"gl_FragColor.a = mix(tex1.a, tex2.a, u_transitionProgress) * qt_Opacity;"
"}";
}
void FadingMaterialShader::updateState(const FadingMaterialState* newState, const FadingMaterialState* oldState)
{
if (!oldState || oldState->source != newState->source) {
glFuncs->glActiveTexture(GL_TEXTURE1);
newState->target->bind();
// reset the active texture back to 0 after we changed it to something else
glFuncs->glActiveTexture(GL_TEXTURE0);
}
if (!oldState || oldState->target != newState->target) {
glFuncs->glActiveTexture(GL_TEXTURE0);
newState->source->bind();
}
if (!oldState || oldState->progress != newState->progress) {
program()->setUniformValue(m_progressId, (GLfloat) newState->progress);
}
}
void FadingMaterialShader::initialize()
{
QSGSimpleMaterialShader< FadingMaterialState >::initialize();
glFuncs = QOpenGLContext::currentContext()->functions();
program()->bind();
program()->setUniformValue("u_src", 0);
program()->setUniformValue("u_target", 1);
m_progressId = program()->uniformLocation("u_transitionProgress");
}
FadingNode::FadingNode(QSGTexture *source, QSGTexture *target):
m_source(source),
m_target(target)
{
QSGSimpleMaterial<FadingMaterialState> *m = FadingMaterialShader::createMaterial();
m->setFlag(QSGMaterial::Blending);
setMaterial(m);
setFlag(OwnsMaterial, true);
setProgress(1.0);
QSGGeometry *g = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
QSGGeometry::updateTexturedRectGeometry(g, QRect(), QRect());
setGeometry(g);
setFlag(QSGNode::OwnsGeometry, true);
}
FadingNode::~FadingNode()
{
}
void FadingNode::setRect(const QRectF &bounds)
{
QSGGeometry::updateTexturedRectGeometry(geometry(), bounds, QRectF(0, 0, 1, 1));
markDirty(QSGNode::DirtyGeometry);
}
void FadingNode::setProgress(qreal progress)
{
QSGSimpleMaterial<FadingMaterialState> *m = static_cast<QSGSimpleMaterial<FadingMaterialState>*>(material());
m->state()->source = m_source.data();
m->state()->target = m_target.data();
m->state()->progress = progress;
markDirty(QSGNode::DirtyMaterial);
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2014 David Edmundson <davidedmundson@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef FADINGNODE_H
#define FADINGNODE_H
#include <QSGGeometryNode>
#include <QSGTexture>
#include <QRectF>
/**
* This node fades between two textures using a shader
*/
class FadingNode : public QSGGeometryNode
{
public:
/**
* Ownership of the textures is transferred to the node
*/
FadingNode(QSGTexture *source, QSGTexture *target);
~FadingNode();
/**
* Set the progress fading between source and target
*/
void setProgress(qreal progress);
void setRect(const QRectF &bounds);
private:
QScopedPointer<QSGTexture> m_source;
QScopedPointer<QSGTexture> m_target;
};
#endif // PLASMAFADINGNODE_H

View File

@ -1,5 +1,6 @@
/*
* Copyright 2012 Marco Martin <mart@kde.org>
* Copyright 2014 David Edmundson <davidedmudnson@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
@ -23,138 +24,21 @@
#include <QPaintEngine>
#include <QPainter>
#include <QPropertyAnimation>
#include <QPixmap>
#include <QSGSimpleTextureNode>
#include <QQuickWindow>
#include <QPixmap>
#include <kiconloader.h>
#include <kiconeffect.h>
#include <plasma/svg.h>
QPixmap transition(const QPixmap &from, const QPixmap &to, qreal amount)
{
if (from.isNull() && to.isNull()) {
return from;
}
if (qFuzzyCompare(amount + 1, qreal(1.0))) {
return from;
}
QRect startRect(from.rect());
QRect targetRect(to.rect());
QSize pixmapSize = startRect.size().expandedTo(targetRect.size());
QRect toRect = QRect(QPoint(0, 0), pixmapSize);
targetRect.moveCenter(toRect.center());
startRect.moveCenter(toRect.center());
//paint to in the center of from
QColor color;
color.setAlphaF(amount);
// If the native paint engine supports Porter/Duff compositing and CompositionMode_Plus
QPaintEngine *paintEngine = from.paintEngine();
if (paintEngine &&
paintEngine->hasFeature(QPaintEngine::PorterDuff) &&
paintEngine->hasFeature(QPaintEngine::BlendModes)) {
QPixmap startPixmap(pixmapSize);
startPixmap.fill(Qt::transparent);
QPixmap targetPixmap(pixmapSize);
targetPixmap.fill(Qt::transparent);
QPainter p;
p.begin(&targetPixmap);
p.drawPixmap(targetRect, to);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.fillRect(targetRect, color);
p.end();
p.begin(&startPixmap);
p.drawPixmap(startRect, from);
p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
p.fillRect(startRect, color);
p.setCompositionMode(QPainter::CompositionMode_Plus);
p.drawPixmap(targetRect, targetPixmap);
p.end();
return startPixmap;
}
#warning Cannot use XRender with QPixmap anymore. Find equivalent with Qt API.
#if 0 // HAVE_X11 && defined(HAVE_XRENDER)
// We have Xrender support
else if (paintEngine && paintEngine->hasFeature(QPaintEngine::PorterDuff)) {
// QX11PaintEngine doesn't implement CompositionMode_Plus in Qt 4.3,
// which we need to be able to do a transition from one pixmap to
// another.
//
// In order to avoid the overhead of converting the pixmaps to images
// and doing the operation entirely in software, this function has a
// specialized path for X11 that uses Xrender directly to do the
// transition. This operation can be fully accelerated in HW.
//
// This specialization can be removed when QX11PaintEngine supports
// CompositionMode_Plus.
QPixmap source(targetPixmap), destination(startPixmap);
source.detach();
destination.detach();
Display *dpy = QX11Info::display();
XRenderPictFormat *format = XRenderFindStandardFormat(dpy, PictStandardA8);
XRenderPictureAttributes pa;
pa.repeat = 1; // RepeatNormal
// Create a 1x1 8 bit repeating alpha picture
Pixmap pixmap = XCreatePixmap(dpy, destination.handle(), 1, 1, 8);
Picture alpha = XRenderCreatePicture(dpy, pixmap, format, CPRepeat, &pa);
XFreePixmap(dpy, pixmap);
// Fill the alpha picture with the opacity value
XRenderColor xcolor;
xcolor.alpha = quint16(0xffff * amount);
XRenderFillRectangle(dpy, PictOpSrc, alpha, &xcolor, 0, 0, 1, 1);
// Reduce the alpha of the destination with 1 - opacity
XRenderComposite(dpy, PictOpOutReverse, alpha, None, destination.x11PictureHandle(),
0, 0, 0, 0, 0, 0, destination.width(), destination.height());
// Add source * opacity to the destination
XRenderComposite(dpy, PictOpAdd, source.x11PictureHandle(), alpha,
destination.x11PictureHandle(),
toRect.x(), toRect.y(), 0, 0, 0, 0, destination.width(), destination.height());
XRenderFreePicture(dpy, alpha);
return destination;
}
#endif
else {
// Fall back to using QRasterPaintEngine to do the transition.
QImage under(pixmapSize, QImage::Format_ARGB32_Premultiplied);
under.fill(Qt::transparent);
QImage over(pixmapSize, QImage::Format_ARGB32_Premultiplied);
over.fill(Qt::transparent);
QPainter p;
p.begin(&over);
p.drawPixmap(targetRect, to);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.fillRect(over.rect(), color);
p.end();
p.begin(&under);
p.drawPixmap(startRect, from);
p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
p.fillRect(startRect, color);
p.setCompositionMode(QPainter::CompositionMode_Plus);
p.drawImage(toRect.topLeft(), over);
p.end();
return QPixmap::fromImage(under);
}
}
#include "fadingnode_p.h"
#include "svgtexturenode.h"
IconItem::IconItem(QQuickItem *parent)
: QQuickPaintedItem(parent),
: QQuickItem(parent),
m_svgIcon(0),
m_smooth(false),
m_active(false),
@ -173,7 +57,7 @@ IconItem::IconItem(QQuickItem *parent)
this, SLOT(animationFinished()));
m_animation->setTargetObject(this);
m_animation->setEasingCurve(QEasingCurve::InOutQuad);
m_animation->setDuration(250);
m_animation->setDuration(250); //FIXME from theme
setFlag(ItemHasContents, true);
@ -303,38 +187,60 @@ bool IconItem::isValid() const
return !m_icon.isNull() || m_svgIcon || !m_pixmapIcon.isNull() || !m_imageIcon.isNull();
}
void IconItem::paint(QPainter *painter)
QSGNode* IconItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
{
if (m_iconPixmaps.isEmpty()) {
return;
Q_UNUSED(updatePaintNodeData)
if (m_iconPixmap.isNull()) {
delete oldNode;
return Q_NULLPTR;
}
painter->save();
painter->setRenderHint(QPainter::Antialiasing, m_smooth);
painter->setRenderHint(QPainter::SmoothPixmapTransform, m_smooth);
const int iconSize = adjustedSize(qMin(boundingRect().size().width(), boundingRect().size().height()));
const QRect destRect(QPointF(boundingRect().center() - QPointF(iconSize / 2, iconSize / 2)).toPoint(),
QSize(iconSize, iconSize));
if (m_animation->state() == QAbstractAnimation::Running) {
QPixmap result = m_iconPixmaps.first();
result = transition(result,
m_iconPixmaps.last(), m_animValue);
painter->drawPixmap(destRect, result);
//simpler logic for just paint
} else {
painter->drawPixmap(destRect, m_iconPixmaps.first());
FadingNode *animatingNode = static_cast<FadingNode*>(oldNode);
if (!animatingNode || m_textureChanged) {
delete oldNode;
QSGTexture *source = window()->createTextureFromImage(m_iconPixmap.toImage());
QSGTexture *target = window()->createTextureFromImage(m_oldIconPixmap.toImage());
animatingNode = new FadingNode(source, target);
m_sizeChanged = true;
m_textureChanged = false;
}
painter->restore();
}
animatingNode->setProgress(m_animValue);
void IconItem::animationFinished()
{
while (m_iconPixmaps.count() > 1) {
m_iconPixmaps.pop_front();
if (m_sizeChanged) {
const int iconSize = adjustedSize(qMin(boundingRect().size().width(), boundingRect().size().height()));
const QRect destRect(QPointF(boundingRect().center() - QPointF(iconSize/2, iconSize/2)).toPoint(),
QSize(iconSize, iconSize));
animatingNode->setRect(destRect);
m_sizeChanged = false;
}
return animatingNode;
} else {
Plasma::SVGTextureNode *textureNode = static_cast<Plasma::SVGTextureNode*>(oldNode);
if (!textureNode || m_textureChanged) {
delete oldNode;
textureNode = new Plasma::SVGTextureNode;
textureNode->setTexture(window()->createTextureFromImage(m_iconPixmap.toImage()));
m_sizeChanged = true;
m_textureChanged = false;
}
if (m_sizeChanged) {
const int iconSize = adjustedSize(qMin(boundingRect().size().width(), boundingRect().size().height()));
const QRect destRect(QPointF(boundingRect().center() - QPointF(iconSize/2, iconSize/2)).toPoint(),
QSize(iconSize, iconSize));
textureNode->setRect(destRect);
m_sizeChanged = false;
}
return textureNode;
}
}
@ -344,6 +250,12 @@ void IconItem::valueChanged(const QVariant &value)
update();
}
void IconItem::animationFinished()
{
m_oldIconPixmap = QPixmap();
m_textureChanged = true;
}
int IconItem::adjustedSize(int size)
{
//FIXME: Heuristic: allow 24x24 for icons/ that are in the systray(ugly)
@ -390,7 +302,7 @@ void IconItem::loadPixmap()
} else if (!m_imageIcon.isNull()) {
result = QPixmap::fromImage(m_imageIcon);
} else {
m_iconPixmaps.clear();
m_iconPixmap = QPixmap();
m_animation->stop();
update();
return;
@ -402,28 +314,17 @@ void IconItem::loadPixmap()
result = KIconLoader::global()->iconEffect()->apply(result, KIconLoader::Desktop, KIconLoader::ActiveState);
}
//this happen only when loadPixmap has been called when an anim is running
while (m_iconPixmaps.count() > 1) {
m_iconPixmaps.pop_front();
}
m_oldIconPixmap = m_iconPixmap;
m_iconPixmap = result;
m_textureChanged = true;
m_iconPixmaps << result;
//if there is only one image, don't animate
//if an animation was already running, immediate transition, to not overload
if (m_iconPixmaps.first().size() != result.size()) {
m_animation->stop();
if (m_iconPixmaps.count() > 1) {
m_iconPixmaps.pop_front();
}
} else if (m_animation->state() == QAbstractAnimation::Running) {
m_animation->stop();
m_iconPixmaps.pop_front();
} else if (m_iconPixmaps.count() > 1) {
//don't animate initial setting
if (!m_oldIconPixmap.isNull()) {
m_animation->setStartValue((qreal)0);
m_animation->setEndValue((qreal)1);
m_animation->start();
} else {
m_animValue = 1.0;
}
update();
}
@ -433,7 +334,10 @@ void IconItem::geometryChanged(const QRectF &newGeometry,
{
if (newGeometry.size() != oldGeometry.size()) {
if (newGeometry.width() > 0 && newGeometry.height() > 0) {
m_sizeChanged = true;
if (!m_loadPixmapTimer.isActive()) {
m_loadPixmapTimer.start();
}
update();
}
}

View File

@ -1,5 +1,6 @@
/*
* Copyright 2012 Marco Martin <mart@kde.org>
* Copyright 2014 David Edmundson <davidedmudnson@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
@ -21,7 +22,7 @@
#define ICONITEM_H
#include <QIcon>
#include <QQuickPaintedItem>
#include <QQuickItem>
#include <QPixmap>
#include <QVariant>
#include <QTimer>
@ -33,7 +34,7 @@ namespace Plasma
class Svg;
}
class IconItem : public QQuickPaintedItem
class IconItem : public QQuickItem
{
Q_OBJECT
@ -58,7 +59,7 @@ public:
bool isValid() const;
void paint(QPainter *painter);
QSGNode* updatePaintNode(QSGNode * oldNode, UpdatePaintNodeData * updatePaintNodeData);
void geometryChanged(const QRectF &newGeometry,
const QRectF &oldGeometry);
@ -90,9 +91,12 @@ private:
bool m_smooth;
bool m_active;
//This list contains at most 2 sources, when a pixmap transition is due,
//a new pixmap is queued, the old one is removed when the animation finishes
QList<QPixmap> m_iconPixmaps;
bool m_textureChanged;
bool m_sizeChanged;
QPixmap m_iconPixmap;
QPixmap m_oldIconPixmap;
//animation on pixmap change
QPropertyAnimation *m_animation;