kwin/libkwineffects/kwingltexture.cpp
Martin Gräßlin fb2fbed035 Generate texture coordinates for limited NPOT support
Fixes rendering issues with R300 and similar GPUs. If the texture
uses GL_TEXTURE_RECTANGLE_ARB as target the tex coordinates need
to be adjusted. This at least fixes missing text on EffectFrames
with graphicssystem native on R300. Hopefully more issues are
resolved by the change.

BUG: 269576
CCBUG: 282882
FIXED-IN: 4.7.3
2011-10-15 14:14:44 +02:00

517 lines
14 KiB
C++

/********************************************************************
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>
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/>.
*********************************************************************/
#include "kwinconfig.h" // KWIN_HAVE_OPENGL
#include "kwinglplatform.h"
#include "kwineffects.h"
#include "kwinglutils_funcs.h"
#include "kwinglutils.h"
#include "kwingltexture_p.h"
#include <QPixmap>
#include <QImage>
#include <QVector2D>
#include <QVector3D>
#include <QVector4D>
#include <QMatrix4x4>
namespace KWin
{
//****************************************
// GLTexture
//****************************************
bool GLTexturePrivate::sNPOTTextureSupported = false;
bool GLTexturePrivate::sFramebufferObjectSupported = false;
bool GLTexturePrivate::sSaturationSupported = false;
GLTexture::GLTexture()
: d_ptr(new GLTexturePrivate())
{
}
GLTexture::GLTexture(GLTexturePrivate& dd)
: d_ptr(&dd)
{
}
GLTexture::GLTexture(const GLTexture& tex)
: d_ptr(tex.d_ptr)
{
}
GLTexture::GLTexture(const QImage& image, GLenum target)
: d_ptr(new GLTexturePrivate())
{
load(image, target);
}
GLTexture::GLTexture(const QPixmap& pixmap, GLenum target)
: d_ptr(new GLTexturePrivate())
{
load(pixmap, target);
}
GLTexture::GLTexture(const QString& fileName)
: d_ptr(new GLTexturePrivate())
{
load(fileName);
}
GLTexture::GLTexture(int width, int height)
: d_ptr(new GLTexturePrivate())
{
Q_D(GLTexture);
if (NPOTTextureSupported() || (isPowerOfTwo(width) && isPowerOfTwo(height))) {
d->m_target = GL_TEXTURE_2D;
d->m_scale.setWidth(1.0 / width);
d->m_scale.setHeight(1.0 / height);
d->m_size = QSize(width, height);
d->m_canUseMipmaps = true;
glGenTextures(1, &d->m_texture);
bind();
#ifdef KWIN_HAVE_OPENGLES
// 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
glTexImage2D(d->m_target, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
#else
glTexImage2D(d->m_target, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0);
#endif
unbind();
}
}
GLTexture::~GLTexture()
{
}
GLTexture& GLTexture::operator = (const GLTexture& tex)
{
d_ptr = tex.d_ptr;
return *this;
}
GLTexturePrivate::GLTexturePrivate()
{
m_texture = 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;
}
GLTexturePrivate::~GLTexturePrivate()
{
if (m_vbo != 0) {
delete m_vbo;
}
if (m_texture != 0) {
glDeleteTextures(1, &m_texture);
}
}
void GLTexturePrivate::initStatic()
{
#ifdef KWIN_HAVE_OPENGLES
sNPOTTextureSupported = true;
sFramebufferObjectSupported = true;
sSaturationSupported = true;
#else
sNPOTTextureSupported = hasGLExtension("GL_ARB_texture_non_power_of_two");
sFramebufferObjectSupported = hasGLExtension("GL_EXT_framebuffer_object");
sSaturationSupported = ((hasGLExtension("GL_ARB_texture_env_crossbar")
&& hasGLExtension("GL_ARB_texture_env_dot3")) || hasGLVersion(1, 4))
&& (glTextureUnitsCount >= 4) && glActiveTexture != NULL;
#endif
}
bool GLTexture::isNull() const
{
Q_D(const GLTexture);
return None == d->m_texture;
}
QSize GLTexture::size() const
{
Q_D(const GLTexture);
return d->m_size;
}
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())
return false;
QImage img = image;
d->m_target = target;
#ifndef KWIN_HAVE_OPENGLES
if (d->m_target != GL_TEXTURE_RECTANGLE_ARB) {
#endif
if (!NPOTTextureSupported()
&& (!isPowerOfTwo(image.width()) || !isPowerOfTwo(image.height()))) {
// non-rectangular target requires POT texture
img = img.scaled(nearestPowerOfTwo(image.width()),
nearestPowerOfTwo(image.height()));
}
d->m_scale.setWidth(1.0 / img.width());
d->m_scale.setHeight(1.0 / img.height());
d->m_canUseMipmaps = true;
#ifndef KWIN_HAVE_OPENGLES
} else {
d->m_scale.setWidth(1.0);
d->m_scale.setHeight(1.0);
d->m_canUseMipmaps = false;
}
#endif
setFilter(GL_LINEAR);
d->m_size = img.size();
d->m_yInverted = true;
img = d->convertToGLFormat(img);
setDirty();
if (isNull()) {
glGenTextures(1, &d->m_texture);
}
bind();
#ifdef KWIN_HAVE_OPENGLES
// 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
glTexImage2D(d->m_target, 0, GL_RGBA, img.width(), img.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, img.bits());
#else
glTexImage2D(d->m_target, 0, GL_RGBA8, img.width(), img.height(), 0,
GL_BGRA, GL_UNSIGNED_BYTE, img.bits());
#endif
unbind();
return true;
}
bool GLTexture::load(const QPixmap& pixmap, GLenum target)
{
if (pixmap.isNull())
return false;
return load(pixmap.toImage(), target);
}
bool GLTexture::load(const QString& fileName)
{
if (fileName.isEmpty())
return false;
return load(QImage(fileName));
}
void GLTexture::discard()
{
d_ptr = new GLTexturePrivate();
}
void GLTexturePrivate::bind()
{
#ifndef KWIN_HAVE_OPENGLES
glEnable(m_target);
#endif
glBindTexture(m_target, m_texture);
enableFilter();
}
void GLTexture::bind()
{
Q_D(GLTexture);
d->bind();
}
void GLTexturePrivate::unbind()
{
glBindTexture(m_target, 0);
#ifndef KWIN_HAVE_OPENGLES
glDisable(m_target);
#endif
}
void GLTexture::unbind()
{
Q_D(GLTexture);
d->unbind();
}
void GLTexture::render(QRegion region, const QRect& rect)
{
Q_D(GLTexture);
if (rect.size() != d->m_cachedSize) {
d->m_cachedSize = rect.size();
QRect r(rect);
r.moveTo(0, 0);
if (!d->m_vbo) {
d->m_vbo = new GLVertexBuffer(KWin::GLVertexBuffer::Static);
}
const float verts[ 4 * 2 ] = {
// NOTICE: r.x/y could be replaced by "0", but that would make it unreadable...
r.x(), r.y(),
r.x(), r.y() + rect.height(),
r.x() + rect.width(), r.y(),
r.x() + rect.width(), r.y() + rect.height()
};
#ifdef KWIN_HAVE_OPENGLES
const float texWidth = 1.0f;
const float texHeight = 1.0f;
#else
const float texWidth = (target() == GL_TEXTURE_RECTANGLE_ARB) ? width() : 1.0f;
const float texHeight = (target() == GL_TEXTURE_RECTANGLE_ARB) ? height() : 1.0f;
#endif
const float texcoords[ 4 * 2 ] = {
0.0f, d->m_yInverted ? 0.0f : texHeight, // y needs to be swapped (normalized coords)
0.0f, d->m_yInverted ? texHeight : 0.0f,
texWidth, d->m_yInverted ? 0.0f : texHeight,
texWidth, d->m_yInverted ? texHeight : 0.0f
};
d->m_vbo->setData(4, 2, verts, texcoords);
}
QMatrix4x4 translation;
translation.translate(rect.x(), rect.y());
if (ShaderManager::instance()->isShaderBound()) {
GLShader *shader = ShaderManager::instance()->getBoundShader();
shader->setUniform(GLShader::Offset, QVector2D(rect.x(), rect.y()));
shader->setUniform(GLShader::WindowTransformation, translation);
} else {
pushMatrix(translation);
}
d->m_vbo->render(region, GL_TRIANGLE_STRIP);
if (ShaderManager::instance()->isShaderBound()) {
GLShader *shader = ShaderManager::instance()->getBoundShader();
shader->setUniform(GLShader::WindowTransformation, QMatrix4x4());
} else {
popMatrix();
}
}
GLuint GLTexture::texture() const
{
Q_D(const GLTexture);
return d->m_texture;
}
GLenum GLTexture::target() const
{
Q_D(const GLTexture);
return d->m_target;
}
GLenum GLTexture::filter() const
{
Q_D(const GLTexture);
return d->m_filter;
}
bool GLTexture::isDirty() const
{
Q_D(const GLTexture);
return d->m_hasValidMipmaps;
}
void GLTexture::setTexture(GLuint texture)
{
Q_D(GLTexture);
discard();
d->m_texture = texture;
}
void GLTexture::setTarget(GLenum target)
{
Q_D(GLTexture);
d->m_target = target;
}
void GLTexture::setFilter(GLenum filter)
{
Q_D(GLTexture);
d->m_filter = filter;
}
void GLTexture::setWrapMode(GLenum mode)
{
Q_D(GLTexture);
bind();
glTexParameteri(d->m_target, GL_TEXTURE_WRAP_S, mode);
glTexParameteri(d->m_target, GL_TEXTURE_WRAP_T, mode);
unbind();
}
void GLTexture::setDirty()
{
Q_D(GLTexture);
d->m_hasValidMipmaps = false;
}
void GLTexturePrivate::enableFilter()
{
if (m_filter == GL_LINEAR_MIPMAP_LINEAR) {
// trilinear filtering requested, but is it possible?
if (sNPOTTextureSupported
&& sFramebufferObjectSupported
&& m_canUseMipmaps) {
glTexParameteri(m_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(m_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if (!m_hasValidMipmaps) {
glGenerateMipmap(m_target);
m_hasValidMipmaps = true;
}
} else {
// can't use trilinear, so use bilinear
m_filter = GL_LINEAR;
glTexParameteri(m_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(m_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
} else if (m_filter == GL_LINEAR) {
glTexParameteri(m_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(m_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} else {
// if neither trilinear nor bilinear, default to fast filtering
m_filter = GL_NEAREST;
glTexParameteri(m_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(m_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
}
QImage GLTexturePrivate::convertToGLFormat(const QImage& img) const
{
// Copied from Qt's QGLWidget::convertToGLFormat()
QImage res;
#ifdef KWIN_HAVE_OPENGLES
res = QImage(img.size(), QImage::Format_ARGB32);
QImage imgARGB32 = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
const int width = img.width();
const int height = img.height();
const uint32_t *p = (const uint32_t*) imgARGB32.scanLine(0);
uint32_t *q = (uint32_t*) res.scanLine(0);
if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
for (int i = 0; i < height; ++i) {
const uint32_t *end = p + width;
while (p < end) {
*q = (*p << 8) | ((*p >> 24) & 0xFF);
p++;
q++;
}
}
} else {
// GL_BGRA -> GL_RGBA
for (int i = 0; i < height; ++i) {
const uint32_t *end = p + width;
while (p < end) {
*q = ((*p << 16) & 0xff0000) | ((*p >> 16) & 0xff) | (*p & 0xff00ff00);
p++;
q++;
}
}
}
#else
if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
res = QImage(img.size(), QImage::Format_ARGB32);
QImage imgARGB32 = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
const int width = img.width();
const int height = img.height();
const uint32_t *p = (const uint32_t*) imgARGB32.scanLine(0);
uint32_t *q = (uint32_t*) res.scanLine(0);
// swizzle
for (int i = 0; i < height; ++i) {
const uint32_t *end = p + width;
while (p < end) {
*q = ((*p << 24) & 0xff000000)
| ((*p >> 24) & 0x000000ff)
| ((*p << 8) & 0x00ff0000)
| ((*p >> 8) & 0x0000ff00);
p++;
q++;
}
}
} else {
res = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
#endif
return res;
}
bool GLTexture::isYInverted() const
{
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