kwin: Implement "use by value" and implicit sharing for GLTexture.

Additionally:
- hide the GLTexture implementation using dpointers
- drop the unused function SceneOpenGL::Texture::optimizeBindDamage()
- Texture::load now loads a new texture and does not update the existing one

REVIEW: 101999
icc-effect-5.14.5
Philipp Knechtges 2011-07-18 17:55:39 +02:00
parent 9c2d6f8fdc
commit 4f50a8df3c
8 changed files with 462 additions and 293 deletions

View File

@ -26,6 +26,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "kwinglutils_funcs.h" #include "kwinglutils_funcs.h"
#include "kwinglutils.h" #include "kwinglutils.h"
#include "kwingltexture_p.h"
#include <QPixmap> #include <QPixmap>
#include <QImage> #include <QImage>
#include <QVector2D> #include <QVector2D>
@ -40,52 +42,62 @@ namespace KWin
// GLTexture // GLTexture
//**************************************** //****************************************
bool GLTexture::sNPOTTextureSupported = false; bool GLTexturePrivate::sNPOTTextureSupported = false;
bool GLTexture::sFramebufferObjectSupported = false; bool GLTexturePrivate::sFramebufferObjectSupported = false;
bool GLTexture::sSaturationSupported = false; bool GLTexturePrivate::sSaturationSupported = false;
GLTexture::GLTexture() GLTexture::GLTexture()
: d_ptr(new GLTexturePrivate())
{
}
GLTexture::GLTexture(GLTexturePrivate& dd)
: d_ptr(&dd)
{
}
GLTexture::GLTexture(const GLTexture& tex)
: d_ptr(tex.d_ptr)
{ {
init();
} }
GLTexture::GLTexture(const QImage& image, GLenum target) GLTexture::GLTexture(const QImage& image, GLenum target)
: d_ptr(new GLTexturePrivate())
{ {
init();
load(image, target); load(image, target);
} }
GLTexture::GLTexture(const QPixmap& pixmap, GLenum target) GLTexture::GLTexture(const QPixmap& pixmap, GLenum target)
: d_ptr(new GLTexturePrivate())
{ {
init();
load(pixmap, target); load(pixmap, target);
} }
GLTexture::GLTexture(const QString& fileName) GLTexture::GLTexture(const QString& fileName)
: d_ptr(new GLTexturePrivate())
{ {
init();
load(fileName); load(fileName);
} }
GLTexture::GLTexture(int width, int height) GLTexture::GLTexture(int width, int height)
: d_ptr(new GLTexturePrivate())
{ {
init(); Q_D(GLTexture);
if (NPOTTextureSupported() || (isPowerOfTwo(width) && isPowerOfTwo(height))) { if (NPOTTextureSupported() || (isPowerOfTwo(width) && isPowerOfTwo(height))) {
mTarget = GL_TEXTURE_2D; d->m_target = GL_TEXTURE_2D;
mScale.setWidth(1.0 / width); d->m_scale.setWidth(1.0 / width);
mScale.setHeight(1.0 / height); d->m_scale.setHeight(1.0 / height);
mSize = QSize(width, height); d->m_size = QSize(width, height);
can_use_mipmaps = true; d->m_canUseMipmaps = true;
glGenTextures(1, &mTexture); glGenTextures(1, &d->m_texture);
bind(); bind();
#ifdef KWIN_HAVE_OPENGLES #ifdef KWIN_HAVE_OPENGLES
// format and internal format have to match in ES, GL_RGBA8 and GL_BGRA are not available // format and internal format have to match in ES, GL_RGBA8 and GL_BGRA are not available
// see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glTexImage2D.xml // see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glTexImage2D.xml
glTexImage2D(mTarget, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); glTexImage2D(d->m_target, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
#else #else
glTexImage2D(mTarget, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); glTexImage2D(d->m_target, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0);
#endif #endif
unbind(); unbind();
} }
@ -93,26 +105,38 @@ GLTexture::GLTexture(int width, int height)
GLTexture::~GLTexture() GLTexture::~GLTexture()
{ {
delete m_vbo;
discard();
assert(mUnnormalizeActive == 0);
assert(mNormalizeActive == 0);
} }
void GLTexture::init() GLTexture& GLTexture::operator = (const GLTexture& tex)
{ {
mTexture = None; d_ptr = tex.d_ptr;
mTarget = 0; return *this;
mFilter = 0; }
y_inverted = false;
can_use_mipmaps = false; GLTexturePrivate::GLTexturePrivate()
has_valid_mipmaps = false; {
mUnnormalizeActive = 0; m_texture = 0;
mNormalizeActive = 0; m_target = 0;
m_filter = 0;
m_yInverted = false;
m_canUseMipmaps = false;
m_hasValidMipmaps = false;
m_unnormalizeActive = 0;
m_normalizeActive = 0;
m_vbo = 0; m_vbo = 0;
} }
void GLTexture::initStatic() GLTexturePrivate::~GLTexturePrivate()
{
if (m_vbo != 0) {
delete m_vbo;
}
if (m_texture != 0) {
glDeleteTextures(1, &m_texture);
}
}
void GLTexturePrivate::initStatic()
{ {
#ifdef KWIN_HAVE_OPENGLES #ifdef KWIN_HAVE_OPENGLES
sNPOTTextureSupported = true; sNPOTTextureSupported = true;
@ -129,22 +153,28 @@ void GLTexture::initStatic()
bool GLTexture::isNull() const bool GLTexture::isNull() const
{ {
return mTexture == None; Q_D(const GLTexture);
return None == d->m_texture;
} }
QSize GLTexture::size() const QSize GLTexture::size() const
{ {
return mSize; Q_D(const GLTexture);
return d->m_size;
} }
bool GLTexture::load(const QImage& image, GLenum target) bool GLTexture::load(const QImage& image, GLenum target)
{ {
// decrease the reference counter for the old texture
d_ptr = new GLTexturePrivate();
Q_D(GLTexture);
if (image.isNull()) if (image.isNull())
return false; return false;
QImage img = image; QImage img = image;
mTarget = target; d->m_target = target;
#ifndef KWIN_HAVE_OPENGLES #ifndef KWIN_HAVE_OPENGLES
if (mTarget != GL_TEXTURE_RECTANGLE_ARB) { if (d->m_target != GL_TEXTURE_RECTANGLE_ARB) {
#endif #endif
if (!NPOTTextureSupported() if (!NPOTTextureSupported()
&& (!isPowerOfTwo(image.width()) || !isPowerOfTwo(image.height()))) { && (!isPowerOfTwo(image.width()) || !isPowerOfTwo(image.height()))) {
@ -152,33 +182,33 @@ bool GLTexture::load(const QImage& image, GLenum target)
img = img.scaled(nearestPowerOfTwo(image.width()), img = img.scaled(nearestPowerOfTwo(image.width()),
nearestPowerOfTwo(image.height())); nearestPowerOfTwo(image.height()));
} }
mScale.setWidth(1.0 / img.width()); d->m_scale.setWidth(1.0 / img.width());
mScale.setHeight(1.0 / img.height()); d->m_scale.setHeight(1.0 / img.height());
can_use_mipmaps = true; d->m_canUseMipmaps = true;
#ifndef KWIN_HAVE_OPENGLES #ifndef KWIN_HAVE_OPENGLES
} else { } else {
mScale.setWidth(1.0); d->m_scale.setWidth(1.0);
mScale.setHeight(1.0); d->m_scale.setHeight(1.0);
can_use_mipmaps = false; d->m_canUseMipmaps = false;
} }
#endif #endif
setFilter(GL_LINEAR); setFilter(GL_LINEAR);
mSize = img.size(); d->m_size = img.size();
y_inverted = true; d->m_yInverted = true;
img = convertToGLFormat(img); img = d->convertToGLFormat(img);
setDirty(); setDirty();
if (isNull()) { if (isNull()) {
glGenTextures(1, &mTexture); glGenTextures(1, &d->m_texture);
} }
bind(); bind();
#ifdef KWIN_HAVE_OPENGLES #ifdef KWIN_HAVE_OPENGLES
// format and internal format have to match in ES, GL_RGBA8 and GL_BGRA are not available // format and internal format have to match in ES, GL_RGBA8 and GL_BGRA are not available
// see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glTexImage2D.xml // see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glTexImage2D.xml
glTexImage2D(mTarget, 0, GL_RGBA, img.width(), img.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, img.bits()); glTexImage2D(d->m_target, 0, GL_RGBA, img.width(), img.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, img.bits());
#else #else
glTexImage2D(mTarget, 0, GL_RGBA8, img.width(), img.height(), 0, glTexImage2D(d->m_target, 0, GL_RGBA8, img.width(), img.height(), 0,
GL_BGRA, GL_UNSIGNED_BYTE, img.bits()); GL_BGRA, GL_UNSIGNED_BYTE, img.bits());
#endif #endif
unbind(); unbind();
@ -201,37 +231,47 @@ bool GLTexture::load(const QString& fileName)
void GLTexture::discard() void GLTexture::discard()
{ {
setDirty(); d_ptr = new GLTexturePrivate();
if (mTexture != None) }
glDeleteTextures(1, &mTexture);
mTexture = None; void GLTexturePrivate::bind()
{
#ifndef KWIN_HAVE_OPENGLES
glEnable(m_target);
#endif
glBindTexture(m_target, m_texture);
enableFilter();
} }
void GLTexture::bind() void GLTexture::bind()
{ {
Q_D(GLTexture);
d->bind();
}
void GLTexturePrivate::unbind()
{
glBindTexture(m_target, 0);
#ifndef KWIN_HAVE_OPENGLES #ifndef KWIN_HAVE_OPENGLES
glEnable(mTarget); glDisable(m_target);
#endif #endif
glBindTexture(mTarget, mTexture);
enableFilter();
} }
void GLTexture::unbind() void GLTexture::unbind()
{ {
glBindTexture(mTarget, 0); Q_D(GLTexture);
#ifndef KWIN_HAVE_OPENGLES d->unbind();
glDisable(mTarget);
#endif
} }
void GLTexture::render(QRegion region, const QRect& rect) void GLTexture::render(QRegion region, const QRect& rect)
{ {
if (rect.size() != m_cachedSize) { Q_D(GLTexture);
m_cachedSize = rect.size(); if (rect.size() != d->m_cachedSize) {
d->m_cachedSize = rect.size();
QRect r(rect); QRect r(rect);
r.moveTo(0, 0); r.moveTo(0, 0);
if (!m_vbo) { if (!d->m_vbo) {
m_vbo = new GLVertexBuffer(KWin::GLVertexBuffer::Static); d->m_vbo = new GLVertexBuffer(KWin::GLVertexBuffer::Static);
} }
const float verts[ 4 * 2 ] = { const float verts[ 4 * 2 ] = {
// NOTICE: r.x/y could be replaced by "0", but that would make it unreadable... // NOTICE: r.x/y could be replaced by "0", but that would make it unreadable...
@ -241,12 +281,12 @@ void GLTexture::render(QRegion region, const QRect& rect)
r.x() + rect.width(), r.y() + rect.height() r.x() + rect.width(), r.y() + rect.height()
}; };
const float texcoords[ 4 * 2 ] = { const float texcoords[ 4 * 2 ] = {
0.0f, y_inverted ? 0.0f : 1.0f, // y needs to be swapped (normalized coords) 0.0f, d->m_yInverted ? 0.0f : 1.0f, // y needs to be swapped (normalized coords)
0.0f, y_inverted ? 1.0f : 0.0f, 0.0f, d->m_yInverted ? 1.0f : 0.0f,
1.0f, y_inverted ? 0.0f : 1.0f, 1.0f, d->m_yInverted ? 0.0f : 1.0f,
1.0f, y_inverted ? 1.0f : 0.0f 1.0f, d->m_yInverted ? 1.0f : 0.0f
}; };
m_vbo->setData(4, 2, verts, texcoords); d->m_vbo->setData(4, 2, verts, texcoords);
} }
QMatrix4x4 translation; QMatrix4x4 translation;
translation.translate(rect.x(), rect.y()); translation.translate(rect.x(), rect.y());
@ -257,7 +297,7 @@ void GLTexture::render(QRegion region, const QRect& rect)
} else { } else {
pushMatrix(translation); pushMatrix(translation);
} }
m_vbo->render(region, GL_TRIANGLE_STRIP); d->m_vbo->render(region, GL_TRIANGLE_STRIP);
if (ShaderManager::instance()->isShaderBound()) { if (ShaderManager::instance()->isShaderBound()) {
GLShader *shader = ShaderManager::instance()->getBoundShader(); GLShader *shader = ShaderManager::instance()->getBoundShader();
shader->setUniform(GLShader::WindowTransformation, QMatrix4x4()); shader->setUniform(GLShader::WindowTransformation, QMatrix4x4());
@ -268,85 +308,94 @@ void GLTexture::render(QRegion region, const QRect& rect)
GLuint GLTexture::texture() const GLuint GLTexture::texture() const
{ {
return mTexture; Q_D(const GLTexture);
return d->m_texture;
} }
GLenum GLTexture::target() const GLenum GLTexture::target() const
{ {
return mTarget; Q_D(const GLTexture);
return d->m_target;
} }
GLenum GLTexture::filter() const GLenum GLTexture::filter() const
{ {
return mFilter; Q_D(const GLTexture);
return d->m_filter;
} }
bool GLTexture::isDirty() const bool GLTexture::isDirty() const
{ {
return has_valid_mipmaps; Q_D(const GLTexture);
return d->m_hasValidMipmaps;
} }
void GLTexture::setTexture(GLuint texture) void GLTexture::setTexture(GLuint texture)
{ {
Q_D(GLTexture);
discard(); discard();
mTexture = texture; d->m_texture = texture;
} }
void GLTexture::setTarget(GLenum target) void GLTexture::setTarget(GLenum target)
{ {
mTarget = target; Q_D(GLTexture);
d->m_target = target;
} }
void GLTexture::setFilter(GLenum filter) void GLTexture::setFilter(GLenum filter)
{ {
mFilter = filter; Q_D(GLTexture);
d->m_filter = filter;
} }
void GLTexture::setWrapMode(GLenum mode) void GLTexture::setWrapMode(GLenum mode)
{ {
Q_D(GLTexture);
bind(); bind();
glTexParameteri(mTarget, GL_TEXTURE_WRAP_S, mode); glTexParameteri(d->m_target, GL_TEXTURE_WRAP_S, mode);
glTexParameteri(mTarget, GL_TEXTURE_WRAP_T, mode); glTexParameteri(d->m_target, GL_TEXTURE_WRAP_T, mode);
unbind(); unbind();
} }
void GLTexture::setDirty() void GLTexture::setDirty()
{ {
has_valid_mipmaps = false; Q_D(GLTexture);
d->m_hasValidMipmaps = false;
} }
void GLTexture::enableFilter() void GLTexturePrivate::enableFilter()
{ {
if (mFilter == GL_LINEAR_MIPMAP_LINEAR) { if (m_filter == GL_LINEAR_MIPMAP_LINEAR) {
// trilinear filtering requested, but is it possible? // trilinear filtering requested, but is it possible?
if (NPOTTextureSupported() if (sNPOTTextureSupported
&& framebufferObjectSupported() && sFramebufferObjectSupported
&& can_use_mipmaps) { && m_canUseMipmaps) {
glTexParameteri(mTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(m_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(mTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(m_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if (!has_valid_mipmaps) { if (!m_hasValidMipmaps) {
glGenerateMipmap(mTarget); glGenerateMipmap(m_target);
has_valid_mipmaps = true; m_hasValidMipmaps = true;
} }
} else { } else {
// can't use trilinear, so use bilinear // can't use trilinear, so use bilinear
setFilter(GL_LINEAR); m_filter = GL_LINEAR;
glTexParameteri(mTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(m_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(mTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(m_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} }
} else if (mFilter == GL_LINEAR) { } else if (m_filter == GL_LINEAR) {
glTexParameteri(mTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(m_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(mTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(m_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} else { } else {
// if neither trilinear nor bilinear, default to fast filtering // if neither trilinear nor bilinear, default to fast filtering
setFilter(GL_NEAREST); m_filter = GL_NEAREST;
glTexParameteri(mTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(m_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(mTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(m_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
} }
} }
QImage GLTexture::convertToGLFormat(const QImage& img) const QImage GLTexturePrivate::convertToGLFormat(const QImage& img) const
{ {
// Copied from Qt's QGLWidget::convertToGLFormat() // Copied from Qt's QGLWidget::convertToGLFormat()
QImage res; QImage res;
@ -410,7 +459,52 @@ QImage GLTexture::convertToGLFormat(const QImage& img) const
bool GLTexture::isYInverted() const bool GLTexture::isYInverted() const
{ {
return y_inverted; Q_D(const GLTexture);
return d->m_yInverted;
}
void GLTexture::setYInverted(bool inverted)
{
Q_D(GLTexture);
d->m_yInverted = inverted;
}
int GLTexture::width() const
{
Q_D(const GLTexture);
return d->m_size.width();
}
int GLTexture::height() const
{
Q_D(const GLTexture);
return d->m_size.height();
}
bool GLTexture::NPOTTextureSupported()
{
return GLTexturePrivate::sNPOTTextureSupported;
}
bool GLTexture::framebufferObjectSupported()
{
return GLTexturePrivate::sFramebufferObjectSupported;
}
bool GLTexture::saturationSupported()
{
return GLTexturePrivate::sSaturationSupported;
}
void GLTexture::release()
{
Q_D(GLTexture);
d->release();
}
void GLTexturePrivate::release()
{
// nothing to do because we are not bound to any specific data
} }
} // namespace KWin } // namespace KWin

View File

@ -25,7 +25,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "kwinglobals.h" #include "kwinglobals.h"
#include <QtCore/QSize> #include <QtCore/QSize>
#include <QtCore/QSharedData> #include <QSharedPointer>
#include <QExplicitlySharedDataPointer>
class QImage; class QImage;
class QPixmap; class QPixmap;
@ -37,37 +38,45 @@ namespace KWin
{ {
class GLVertexBuffer; class GLVertexBuffer;
class GLTexturePrivate;
class KWIN_EXPORT GLTexture class KWIN_EXPORT GLTexture
: public QSharedData
{ {
public: public:
GLTexture(); GLTexture();
GLTexture(const GLTexture& tex);
explicit GLTexture(const QImage& image, GLenum target = GL_TEXTURE_2D); explicit GLTexture(const QImage& image, GLenum target = GL_TEXTURE_2D);
explicit GLTexture(const QPixmap& pixmap, GLenum target = GL_TEXTURE_2D); explicit GLTexture(const QPixmap& pixmap, GLenum target = GL_TEXTURE_2D);
GLTexture(const QString& fileName); GLTexture(const QString& fileName);
GLTexture(int width, int height); GLTexture(int width, int height);
virtual ~GLTexture(); virtual ~GLTexture();
GLTexture & operator = (const GLTexture& tex);
bool isNull() const; bool isNull() const;
QSize size() const; QSize size() const;
int width() const { int width() const;
return mSize.width(); /// @since 4.5 int height() const;
}
int height() const {
return mSize.height(); /// @since 4.5
}
/** /**
* @since 4.7 * @since 4.7
**/ **/
bool isYInverted() const; bool isYInverted() const;
/**
* @since 4.8
**/
void setYInverted(bool inverted);
virtual bool load(const QImage& image, GLenum target = GL_TEXTURE_2D); virtual bool load(const QImage& image, GLenum target = GL_TEXTURE_2D);
virtual bool load(const QPixmap& pixmap, GLenum target = GL_TEXTURE_2D); virtual bool load(const QPixmap& pixmap, GLenum target = GL_TEXTURE_2D);
virtual bool load(const QString& fileName); virtual bool load(const QString& fileName);
virtual void discard(); virtual void discard();
virtual void bind(); void bind();
virtual void unbind(); void unbind();
/**
* Release the data which is bound to the texture.
* @since 4.8
**/
void release();
void render(QRegion region, const QRect& rect); void render(QRegion region, const QRect& rect);
GLuint texture() const; GLuint texture() const;
@ -80,41 +89,19 @@ public:
void setWrapMode(GLenum mode); void setWrapMode(GLenum mode);
virtual void setDirty(); virtual void setDirty();
static void initStatic(); static bool NPOTTextureSupported();
static bool NPOTTextureSupported() { static bool framebufferObjectSupported();
return sNPOTTextureSupported; static bool saturationSupported();
}
static bool framebufferObjectSupported() {
return sFramebufferObjectSupported;
}
static bool saturationSupported() {
return sSaturationSupported;
}
protected: protected:
void enableFilter(); void enableFilter();
QImage convertToGLFormat(const QImage& img) const; QImage convertToGLFormat(const QImage& img) const;
GLuint mTexture; QExplicitlySharedDataPointer<GLTexturePrivate> d_ptr;
GLenum mTarget; GLTexture(GLTexturePrivate& dd);
GLenum mFilter;
QSize mSize;
QSizeF mScale; // to un-normalize GL_TEXTURE_2D
bool y_inverted; // texture has y inverted
bool can_use_mipmaps;
bool has_valid_mipmaps;
private: private:
void init(); Q_DECLARE_PRIVATE(GLTexture);
int mUnnormalizeActive; // 0 - no, otherwise refcount
int mNormalizeActive; // 0 - no, otherwise refcount
GLVertexBuffer* m_vbo;
QSize m_cachedSize;
static bool sNPOTTextureSupported;
static bool sFramebufferObjectSupported;
static bool sSaturationSupported;
Q_DISABLE_COPY(GLTexture)
}; };
} // namespace } // namespace

View File

@ -0,0 +1,74 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2006-2007 Rivo Laks <rivolaks@hot.ee>
Copyright (C) 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2011 Philipp Knechtges <philipp-dev@knechtges.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef KWIN_GLTEXTURE_P_H
#define KWIN_GLTEXTURE_P_H
#include "kwinconfig.h" // KWIN_HAVE_OPENGL
#include "kwinglobals.h"
#include <QtCore/QSize>
#include <QtCore/QSharedData>
namespace KWin
{
class KWIN_EXPORT GLTexturePrivate
: public QSharedData
{
public:
GLTexturePrivate();
virtual ~GLTexturePrivate();
virtual void bind();
virtual void unbind();
virtual void release();
void enableFilter();
QImage convertToGLFormat(const QImage& img) const;
GLuint m_texture;
GLenum m_target;
GLenum m_filter;
QSize m_size;
QSizeF m_scale; // to un-normalize GL_TEXTURE_2D
bool m_yInverted; // texture has y inverted
bool m_canUseMipmaps;
bool m_hasValidMipmaps;
int m_unnormalizeActive; // 0 - no, otherwise refcount
int m_normalizeActive; // 0 - no, otherwise refcount
GLVertexBuffer* m_vbo;
QSize m_cachedSize;
static void initStatic();
static bool sNPOTTextureSupported;
static bool sFramebufferObjectSupported;
static bool sSaturationSupported;
private:
Q_DISABLE_COPY(GLTexturePrivate)
};
} // namespace
#endif

View File

@ -21,6 +21,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "kwinglutils.h" #include "kwinglutils.h"
// need to call GLTexturePrivate::initStatic()
#include "kwingltexture_p.h"
#include "kwinglobals.h" #include "kwinglobals.h"
#include "kwineffects.h" #include "kwineffects.h"
#include "kwinglplatform.h" #include "kwinglplatform.h"
@ -113,7 +116,7 @@ void initGL()
// handle OpenGL extensions functions // handle OpenGL extensions functions
glResolveFunctions(); glResolveFunctions();
GLTexture::initStatic(); GLTexturePrivate::initStatic();
GLRenderTarget::initStatic(); GLRenderTarget::initStatic();
GLVertexBuffer::initStatic(); GLVertexBuffer::initStatic();
} }

View File

@ -268,55 +268,43 @@ void SceneOpenGL::windowOpacityChanged(KWin::Toplevel* t)
// SceneOpenGL::Texture // SceneOpenGL::Texture
//**************************************** //****************************************
SceneOpenGL::Texture::Texture() : GLTexture() SceneOpenGL::Texture::Texture() : GLTexture(*new TexturePrivate())
{ {
init();
} }
SceneOpenGL::Texture::Texture(const Pixmap& pix, const QSize& size, int depth) : GLTexture() SceneOpenGL::Texture::Texture(TexturePrivate& dd) : GLTexture(dd)
{
}
SceneOpenGL::Texture::Texture(const SceneOpenGL::Texture& tex) : GLTexture(*tex.d_ptr)
{
}
SceneOpenGL::Texture::Texture(const Pixmap& pix, const QSize& size, int depth)
: GLTexture(*new TexturePrivate())
{ {
init();
load(pix, size, depth); load(pix, size, depth);
} }
SceneOpenGL::Texture::Texture(const QPixmap& pix, GLenum target) SceneOpenGL::Texture::Texture(const QPixmap& pix, GLenum target)
: GLTexture() : GLTexture(*new TexturePrivate())
{ {
init();
load(pix, target); load(pix, target);
} }
SceneOpenGL::Texture::~Texture() SceneOpenGL::Texture::~Texture()
{ {
discard();
} }
void SceneOpenGL::Texture::createTexture() SceneOpenGL::Texture& SceneOpenGL::Texture::operator = (const SceneOpenGL::Texture& tex)
{ {
glGenTextures(1, &mTexture); d_ptr = tex.d_ptr;
return *this;
} }
void SceneOpenGL::Texture::discard() void SceneOpenGL::Texture::discard()
{ {
if (mTexture != None) d_ptr = new TexturePrivate();
release();
GLTexture::discard();
}
QRegion SceneOpenGL::Texture::optimizeBindDamage(const QRegion& reg, int limit)
{
if (reg.rects().count() <= 1)
return reg;
// try to reduce the number of rects, as especially with SHM mode every rect
// causes X roundtrip, even for very small areas - so, when the size difference
// between all the areas and the bounding rectangle is small, simply use
// only the bounding rectangle
int size = 0;
foreach (const QRect & r, reg.rects())
size += r.width() * r.height();
if (reg.boundingRect().width() * reg.boundingRect().height() - size < limit)
return reg.boundingRect();
return reg;
} }
bool SceneOpenGL::Texture::load(const Pixmap& pix, const QSize& size, bool SceneOpenGL::Texture::load(const Pixmap& pix, const QSize& size,
@ -337,6 +325,7 @@ bool SceneOpenGL::Texture::load(const QImage& image, GLenum target)
bool SceneOpenGL::Texture::load(const QPixmap& pixmap, GLenum target) bool SceneOpenGL::Texture::load(const QPixmap& pixmap, GLenum target)
{ {
Q_D(Texture);
if (pixmap.isNull()) if (pixmap.isNull())
return false; return false;
@ -345,8 +334,6 @@ bool SceneOpenGL::Texture::load(const QPixmap& pixmap, GLenum target)
return GLTexture::load(pixmap.toImage(), target); return GLTexture::load(pixmap.toImage(), target);
} }
y_inverted = true;
// use the X11 pixmap provided by Qt // use the X11 pixmap provided by Qt
return load(pixmap.handle(), pixmap.size(), pixmap.depth()); return load(pixmap.handle(), pixmap.size(), pixmap.depth());
} }
@ -374,9 +361,16 @@ SceneOpenGL::Window::~Window()
bool SceneOpenGL::Window::bindTexture() bool SceneOpenGL::Window::bindTexture()
{ {
#ifndef KWIN_HAVE_OPENGLES #ifndef KWIN_HAVE_OPENGLES
if (texture.texture() != None && toplevel->damage().isEmpty()) { if (!texture.isNull()) {
if (toplevel->damage().isEmpty()) {
// texture doesn't need updating, just bind it // texture doesn't need updating, just bind it
glBindTexture(texture.target(), texture.texture()); glBindTexture(texture.target(), texture.texture());
} else {
// bind() updates the texture automatically e.g. in case the glx pixmap binding
// is strict
texture.bind();
toplevel->resetDamage(QRect(toplevel->clientPos(), toplevel->clientSize()));
}
return true; return true;
} }
#endif #endif
@ -384,8 +378,10 @@ bool SceneOpenGL::Window::bindTexture()
Pixmap pix = toplevel->windowPixmap(); Pixmap pix = toplevel->windowPixmap();
if (pix == None) if (pix == None)
return false; return false;
bool success = texture.load(pix, toplevel->size(), toplevel->depth(), bool success = texture.load(pix, toplevel->size(), toplevel->depth(),
toplevel->damage()); toplevel->damage());
if (success) if (success)
toplevel->resetDamage(QRect(toplevel->clientPos(), toplevel->clientSize())); toplevel->resetDamage(QRect(toplevel->clientPos(), toplevel->clientSize()));
else else
@ -417,7 +413,7 @@ void SceneOpenGL::Window::checkTextureSize()
// when the window's composite pixmap is discarded, undo binding it to the texture // when the window's composite pixmap is discarded, undo binding it to the texture
void SceneOpenGL::Window::pixmapDiscarded() void SceneOpenGL::Window::pixmapDiscarded()
{ {
texture.release(); texture.discard();
} }
QMatrix4x4 SceneOpenGL::Window::transformation(int mask, const WindowPaintData &data) const QMatrix4x4 SceneOpenGL::Window::transformation(int mask, const WindowPaintData &data) const
@ -640,7 +636,7 @@ void SceneOpenGL::Window::paintDecoration(const QPixmap* decoration, TextureType
} }
if (decorationTexture->texture() != None && !updateDeco) { if (decorationTexture->texture() != None && !updateDeco) {
// texture doesn't need updating, just bind it // texture doesn't need updating, just bind it
glBindTexture(decorationTexture->target(), decorationTexture->texture()); decorationTexture->bind();
} else if (!decoration->isNull()) { } else if (!decoration->isNull()) {
bool success = decorationTexture->load(*decoration); bool success = decorationTexture->load(*decoration);
if (!success) { if (!success) {

View File

@ -26,6 +26,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "shadow.h" #include "shadow.h"
#include "kwinglutils.h" #include "kwinglutils.h"
#include "kwingltexture_p.h"
#ifdef HAVE_XSHM #ifdef HAVE_XSHM
#include <X11/extensions/XShm.h> #include <X11/extensions/XShm.h>
@ -41,6 +42,7 @@ class SceneOpenGL
public: public:
class EffectFrame; class EffectFrame;
class Texture; class Texture;
class TexturePrivate;
class Window; class Window;
SceneOpenGL(Workspace* ws); SceneOpenGL(Workspace* ws);
virtual ~SceneOpenGL(); virtual ~SceneOpenGL();
@ -100,40 +102,51 @@ private:
bool debug; bool debug;
}; };
class SceneOpenGL::TexturePrivate
: public GLTexturePrivate
{
public:
TexturePrivate();
virtual ~TexturePrivate();
virtual void bind();
virtual void unbind();
virtual void release();
#ifndef KWIN_HAVE_OPENGLES
GLXPixmap m_glxpixmap; // the glx pixmap the texture is bound to, only for tfp_mode
#endif
private:
Q_DISABLE_COPY(TexturePrivate)
};
class SceneOpenGL::Texture class SceneOpenGL::Texture
: public GLTexture : public GLTexture
{ {
public: public:
Texture(); Texture();
Texture(const Texture& tex);
Texture(const QPixmap& pix, GLenum target = GL_TEXTURE_2D); Texture(const QPixmap& pix, GLenum target = GL_TEXTURE_2D);
virtual ~Texture(); virtual ~Texture();
Texture & operator = (const Texture& tex);
using GLTexture::load; using GLTexture::load;
virtual bool load(const QImage& image, GLenum target = GL_TEXTURE_2D); virtual bool load(const QImage& image, GLenum target = GL_TEXTURE_2D);
virtual bool load(const QPixmap& pixmap, GLenum target = GL_TEXTURE_2D); virtual bool load(const QPixmap& pixmap, GLenum target = GL_TEXTURE_2D);
virtual void discard(); virtual void discard();
virtual void release(); // undo the tfp_mode binding
virtual void bind();
virtual void unbind();
void setYInverted(bool inverted) {
y_inverted = inverted;
}
protected: protected:
Texture(const Pixmap& pix, const QSize& size, int depth); Texture(const Pixmap& pix, const QSize& size, int depth);
void findTarget(); void findTarget();
QRegion optimizeBindDamage(const QRegion& reg, int limit);
void createTexture();
virtual bool load(const Pixmap& pix, const QSize& size, int depth, virtual bool load(const Pixmap& pix, const QSize& size, int depth,
QRegion region); QRegion region);
virtual bool load(const Pixmap& pix, const QSize& size, int depth); virtual bool load(const Pixmap& pix, const QSize& size, int depth);
private: Texture(TexturePrivate& dd);
void init();
#ifndef KWIN_HAVE_OPENGLES private:
GLXPixmap glxpixmap; // the glx pixmap the texture is bound to, only for tfp_mode Q_DECLARE_PRIVATE(Texture);
#endif
friend class SceneOpenGL::Window; friend class SceneOpenGL::Window;
}; };

View File

@ -210,32 +210,39 @@ void SceneOpenGL::flushBuffer(int mask, QRegion damage)
// SceneOpenGL::Texture // SceneOpenGL::Texture
//**************************************** //****************************************
void SceneOpenGL::Texture::init() SceneOpenGL::TexturePrivate::TexturePrivate()
{ {
findTarget(); m_target = GL_TEXTURE_2D;
} }
void SceneOpenGL::Texture::release() SceneOpenGL::TexturePrivate::~TexturePrivate()
{ {
mTexture = None;
} }
void SceneOpenGL::Texture::findTarget() void SceneOpenGL::Texture::findTarget()
{ {
mTarget = GL_TEXTURE_2D; Q_D(Texture);
d->m_target = GL_TEXTURE_2D;
}
void SceneOpenGL::TexturePrivate::release()
{
} }
bool SceneOpenGL::Texture::load(const Pixmap& pix, const QSize& size, bool SceneOpenGL::Texture::load(const Pixmap& pix, const QSize& size,
int depth, QRegion region) int depth, QRegion region)
{ {
// decrease the reference counter for the old texture
d_ptr = new TexturePrivate();
Q_D(Texture);
Q_UNUSED(depth) Q_UNUSED(depth)
Q_UNUSED(region) Q_UNUSED(region)
if (pix == None) if (pix == None)
return false; return false;
if (mTexture == None) { glGenTextures(1, &d->m_texture);
createTexture();
bind(); bind();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
@ -258,17 +265,16 @@ bool SceneOpenGL::Texture::load(const Pixmap& pix, const QSize& size,
unbind(); unbind();
checkGLError("load texture"); checkGLError("load texture");
setYInverted(true); setYInverted(true);
mSize = size; d->m_size = size;
}
return true; return true;
} }
void SceneOpenGL::Texture::bind() void SceneOpenGL::TexturePrivate::bind()
{ {
GLTexture::bind(); GLTexturePrivate::bind();
} }
void SceneOpenGL::Texture::unbind() void SceneOpenGL::TexturePrivate::unbind()
{ {
GLTexture::unbind(); GLTexturePrivate::unbind();
} }

View File

@ -55,6 +55,7 @@ SceneOpenGL::SceneOpenGL(Workspace* ws)
return; // error return; // error
// Initialize OpenGL // Initialize OpenGL
initGL(); initGL();
GLPlatform *glPlatform = GLPlatform::instance(); GLPlatform *glPlatform = GLPlatform::instance();
if (glPlatform->isSoftwareEmulation()) { if (glPlatform->isSoftwareEmulation()) {
kError(1212) << "OpenGL Software Rasterizer detected. Falling back to XRender."; kError(1212) << "OpenGL Software Rasterizer detected. Falling back to XRender.";
@ -544,33 +545,38 @@ void SceneOpenGL::flushBuffer(int mask, QRegion damage)
// SceneOpenGL::Texture // SceneOpenGL::Texture
//**************************************** //****************************************
void SceneOpenGL::Texture::init() SceneOpenGL::TexturePrivate::TexturePrivate()
{ {
glxpixmap = None; m_glxpixmap = None;
} }
void SceneOpenGL::Texture::release() SceneOpenGL::TexturePrivate::~TexturePrivate()
{ {
if (glxpixmap != None) { release();
}
void SceneOpenGL::TexturePrivate::release()
{
if (m_glxpixmap != None) {
if (!options->glStrictBinding) { if (!options->glStrictBinding) {
glXReleaseTexImageEXT(display(), glxpixmap, GLX_FRONT_LEFT_EXT); glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT);
} }
glXDestroyPixmap(display(), glxpixmap); glXDestroyPixmap(display(), m_glxpixmap);
glxpixmap = None;
} }
} }
void SceneOpenGL::Texture::findTarget() void SceneOpenGL::Texture::findTarget()
{ {
Q_D(Texture);
unsigned int new_target = 0; unsigned int new_target = 0;
if (glXQueryDrawable && glxpixmap != None) if (glXQueryDrawable && d->m_glxpixmap != None)
glXQueryDrawable(display(), glxpixmap, GLX_TEXTURE_TARGET_EXT, &new_target); glXQueryDrawable(display(), d->m_glxpixmap, GLX_TEXTURE_TARGET_EXT, &new_target);
// HACK: this used to be a hack for Xgl. // HACK: this used to be a hack for Xgl.
// without this hack the NVIDIA blob aborts when trying to bind a texture from // without this hack the NVIDIA blob aborts when trying to bind a texture from
// a pixmap icon // a pixmap icon
if (new_target == 0) { if (new_target == 0) {
if (NPOTTextureSupported() || if (NPOTTextureSupported() ||
(isPowerOfTwo(mSize.width()) && isPowerOfTwo(mSize.height()))) { (isPowerOfTwo(d->m_size.width()) && isPowerOfTwo(d->m_size.height()))) {
new_target = GLX_TEXTURE_2D_EXT; new_target = GLX_TEXTURE_2D_EXT;
} else { } else {
new_target = GLX_TEXTURE_RECTANGLE_EXT; new_target = GLX_TEXTURE_RECTANGLE_EXT;
@ -578,14 +584,14 @@ void SceneOpenGL::Texture::findTarget()
} }
switch(new_target) { switch(new_target) {
case GLX_TEXTURE_2D_EXT: case GLX_TEXTURE_2D_EXT:
mTarget = GL_TEXTURE_2D; d->m_target = GL_TEXTURE_2D;
mScale.setWidth(1.0f / mSize.width()); d->m_scale.setWidth(1.0f / d->m_size.width());
mScale.setHeight(1.0f / mSize.height()); d->m_scale.setHeight(1.0f / d->m_size.height());
break; break;
case GLX_TEXTURE_RECTANGLE_EXT: case GLX_TEXTURE_RECTANGLE_EXT:
mTarget = GL_TEXTURE_RECTANGLE_ARB; d->m_target = GL_TEXTURE_RECTANGLE_ARB;
mScale.setWidth(1.0f); d->m_scale.setWidth(1.0f);
mScale.setHeight(1.0f); d->m_scale.setHeight(1.0f);
break; break;
default: default:
abort(); abort();
@ -595,6 +601,10 @@ void SceneOpenGL::Texture::findTarget()
bool SceneOpenGL::Texture::load(const Pixmap& pix, const QSize& size, bool SceneOpenGL::Texture::load(const Pixmap& pix, const QSize& size,
int depth, QRegion region) int depth, QRegion region)
{ {
// decrease the reference counter for the old texture
d_ptr = new TexturePrivate();
Q_D(Texture);
#ifdef CHECK_GL_ERROR #ifdef CHECK_GL_ERROR
checkGLError("TextureLoad1"); checkGLError("TextureLoad1");
#endif #endif
@ -606,27 +616,22 @@ bool SceneOpenGL::Texture::load(const Pixmap& pix, const QSize& size,
return false; return false;
} }
mSize = size; d->m_size = size;
if (mTexture == None || !region.isEmpty()) { d->m_yInverted = true;
// new texture, or texture contents changed; mipmaps now invalid // new texture, or texture contents changed; mipmaps now invalid
setDirty(); setDirty();
}
#ifdef CHECK_GL_ERROR #ifdef CHECK_GL_ERROR
checkGLError("TextureLoad2"); checkGLError("TextureLoad2");
#endif #endif
// tfp mode, simply bind the pixmap to texture // tfp mode, simply bind the pixmap to texture
if (mTexture == None) glGenTextures(1, &d->m_texture);
createTexture();
// The GLX pixmap references the contents of the original pixmap, so it doesn't // The GLX pixmap references the contents of the original pixmap, so it doesn't
// need to be recreated when the contents change. // need to be recreated when the contents change.
// The texture may or may not use the same storage depending on the EXT_tfp // The texture may or may not use the same storage depending on the EXT_tfp
// implementation. When options->glStrictBinding is true, the texture uses // implementation. When options->glStrictBinding is true, the texture uses
// a different storage and needs to be updated with a call to // a different storage and needs to be updated with a call to
// glXBindTexImageEXT() when the contents of the pixmap has changed. // glXBindTexImageEXT() when the contents of the pixmap has changed.
if (glxpixmap != None)
glBindTexture(mTarget, mTexture);
else {
int attrs[] = { int attrs[] = {
GLX_TEXTURE_FORMAT_EXT, fbcdrawableinfo[ depth ].bind_texture_format, GLX_TEXTURE_FORMAT_EXT, fbcdrawableinfo[ depth ].bind_texture_format,
GLX_MIPMAP_TEXTURE_EXT, fbcdrawableinfo[ depth ].mipmap, GLX_MIPMAP_TEXTURE_EXT, fbcdrawableinfo[ depth ].mipmap,
@ -645,39 +650,31 @@ bool SceneOpenGL::Texture::load(const Pixmap& pix, const QSize& size,
attrs[ 5 ] = GLX_TEXTURE_RECTANGLE_EXT; attrs[ 5 ] = GLX_TEXTURE_RECTANGLE_EXT;
} }
} }
glxpixmap = glXCreatePixmap(display(), fbcdrawableinfo[ depth ].fbconfig, pix, attrs); d->m_glxpixmap = glXCreatePixmap(display(), fbcdrawableinfo[ depth ].fbconfig, pix, attrs);
#ifdef CHECK_GL_ERROR #ifdef CHECK_GL_ERROR
checkGLError("TextureLoadTFP1"); checkGLError("TextureLoadTFP1");
#endif #endif
findTarget(); findTarget();
y_inverted = fbcdrawableinfo[ depth ].y_inverted ? true : false; d->m_yInverted = fbcdrawableinfo[ depth ].y_inverted ? true : false;
can_use_mipmaps = fbcdrawableinfo[ depth ].mipmap ? true : false; d->m_canUseMipmaps = fbcdrawableinfo[ depth ].mipmap ? true : false;
glBindTexture(mTarget, mTexture); glBindTexture(d->m_target, d->m_texture);
#ifdef CHECK_GL_ERROR #ifdef CHECK_GL_ERROR
checkGLError("TextureLoadTFP2"); checkGLError("TextureLoadTFP2");
#endif #endif
if (!options->glStrictBinding) glXBindTexImageEXT(display(), d->m_glxpixmap, GLX_FRONT_LEFT_EXT, NULL);
glXBindTexImageEXT(display(), glxpixmap, GLX_FRONT_LEFT_EXT, NULL);
}
if (options->glStrictBinding)
// Mark the texture as damaged so it will be updated on the next call to bind()
glXBindTexImageEXT(display(), glxpixmap, GLX_FRONT_LEFT_EXT, NULL);
#ifdef CHECK_GL_ERROR #ifdef CHECK_GL_ERROR
checkGLError("TextureLoad0"); checkGLError("TextureLoad0");
#endif #endif
return true; return true;
} }
void SceneOpenGL::Texture::bind() void SceneOpenGL::TexturePrivate::bind()
{ {
glEnable(mTarget); GLTexturePrivate::bind();
glBindTexture(mTarget, mTexture); if (options->glStrictBinding && m_glxpixmap) {
// if one of the GLTexture::load functions is called, the glxpixmap doesn't glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT);
// have to exist glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, NULL);
if (options->glStrictBinding && glxpixmap) { m_hasValidMipmaps = false; // Mipmaps have to be regenerated after updating the texture
glXReleaseTexImageEXT(display(), glxpixmap, GLX_FRONT_LEFT_EXT);
glXBindTexImageEXT(display(), glxpixmap, GLX_FRONT_LEFT_EXT, NULL);
setDirty(); // Mipmaps have to be regenerated after updating the texture
} }
enableFilter(); enableFilter();
if (hasGLVersion(1, 4, 0)) { if (hasGLVersion(1, 4, 0)) {
@ -686,17 +683,16 @@ void SceneOpenGL::Texture::bind()
} }
} }
void SceneOpenGL::Texture::unbind() void SceneOpenGL::TexturePrivate::unbind()
{ {
if (hasGLVersion(1, 4, 0)) { if (hasGLVersion(1, 4, 0)) {
glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, 0.0f); glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, 0.0f);
} }
// if one of the GLTexture::load functions is called, the glxpixmap doesn't if (options->glStrictBinding && m_glxpixmap) {
// have to exist glBindTexture(m_target, m_texture);
if (options->glStrictBinding && glxpixmap) { glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT);
glBindTexture(mTarget, mTexture);
glXReleaseTexImageEXT(display(), glxpixmap, GLX_FRONT_LEFT_EXT);
} }
GLTexture::unbind(); GLTexturePrivate::unbind();
} }