/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 by Fredrik Höglund Copyright (C) 2010 Martin Gräßlin 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 . *********************************************************************/ #include "lanczosfilter.h" #include "effects.h" #include "options.h" #include #include #include #include #include #include namespace KWin { LanczosFilter::LanczosFilter(QObject* parent) : QObject(parent) , m_offscreenTex(0) , m_offscreenTarget(0) , m_shader(0) , m_inited(false) { } LanczosFilter::~LanczosFilter() { delete m_offscreenTarget; delete m_offscreenTex; } void LanczosFilter::init() { if (m_inited) return; m_inited = true; const bool force = (qstrcmp(qgetenv("KWIN_FORCE_LANCZOS"), "1") == 0); if (force) { kWarning(1212) << "Lanczos Filter forced on by environment variable"; } if (!force && options->glSmoothScale() != 2) return; // disabled by config // The lanczos filter is reported to be broken with the Intel driver and Mesa 7.10 GLPlatform *gl = GLPlatform::instance(); if (!force && gl->driver() == Driver_Intel && gl->mesaVersion() >= kVersionNumber(7, 10) && gl->chipClass() < SandyBridge) return; // With fglrx the ARB Shader crashes KWin (see Bug #270818 and #286795) and GLSL Shaders are not functional if (!force && gl->driver() == Driver_Catalyst) { return; } m_shader = new LanczosShader(this); if (!m_shader->init()) { delete m_shader; m_shader = 0; } } void LanczosFilter::updateOffscreenSurfaces() { int w = displayWidth(); int h = displayHeight(); if (!GLTexture::NPOTTextureSupported()) { w = nearestPowerOfTwo(w); h = nearestPowerOfTwo(h); } if (!m_offscreenTex || m_offscreenTex->width() != w || m_offscreenTex->height() != h) { if (m_offscreenTex) { delete m_offscreenTex; delete m_offscreenTarget; } m_offscreenTex = new GLTexture(w, h); m_offscreenTex->setFilter(GL_LINEAR); m_offscreenTex->setWrapMode(GL_CLAMP_TO_EDGE); m_offscreenTarget = new GLRenderTarget(*m_offscreenTex); } } static float sinc(float x) { return std::sin(x * M_PI) / (x * M_PI); } static float lanczos(float x, float a) { if (qFuzzyCompare(x + 1.0, 1.0)) return 1.0; if (qAbs(x) >= a) return 0.0; return sinc(x) * sinc(x / a); } void LanczosShader::createKernel(float delta, int *size) { const float a = 2.0; // The two outermost samples always fall at points where the lanczos // function returns 0, so we'll skip them. const int sampleCount = qBound(3, qCeil(delta * a) * 2 + 1 - 2, 29); const int center = sampleCount / 2; const int kernelSize = center + 1; const float factor = 1.0 / delta; QVector values(kernelSize); float sum = 0; for (int i = 0; i < kernelSize; i++) { const float val = lanczos(i * factor, a); sum += i > 0 ? val * 2 : val; values[i] = val; } memset(m_kernel, 0, 16 * sizeof(QVector4D)); // Normalize the kernel for (int i = 0; i < kernelSize; i++) { const float val = values[i] / sum; m_kernel[i] = QVector4D(val, val, val, val); } *size = kernelSize; } void LanczosShader::createOffsets(int count, float width, Qt::Orientation direction) { memset(m_offsets, 0, 16 * sizeof(QVector2D)); for (int i = 0; i < count; i++) { m_offsets[i] = (direction == Qt::Horizontal) ? QVector2D(i / width, 0) : QVector2D(0, i / width); } } void LanczosFilter::performPaint(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (effects->compositingType() == KWin::OpenGLCompositing && (data.xScale < 0.9 || data.yScale < 0.9) && KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) { if (!m_inited) init(); const QRect screenRect = Workspace::self()->clientArea(ScreenArea, w->screen(), w->desktop()); // window geometry may not be bigger than screen geometry to fit into the FBO if (m_shader && w->width() <= screenRect.width() && w->height() <= screenRect.height()) { double left = 0; double top = 0; double right = w->width(); double bottom = w->height(); foreach (const WindowQuad & quad, data.quads) { // we need this loop to include the decoration padding left = qMin(left, quad.left()); top = qMin(top, quad.top()); right = qMax(right, quad.right()); bottom = qMax(bottom, quad.bottom()); } double width = right - left; double height = bottom - top; if (width > screenRect.width() || height > screenRect.height()) { // window with padding does not fit into the framebuffer // so cut of the shadow left = 0; top = 0; width = w->width(); height = w->height(); } int tx = data.xTranslate + w->x() + left * data.xScale; int ty = data.yTranslate + w->y() + top * data.yScale; int tw = width * data.xScale; int th = height * data.yScale; const QRect textureRect(tx, ty, tw, th); int sw = width; int sh = height; GLTexture *cachedTexture = static_cast< GLTexture*>(w->data(LanczosCacheRole).value()); if (cachedTexture) { if (cachedTexture->width() == tw && cachedTexture->height() == th) { cachedTexture->bind(); if (ShaderManager::instance()->isValid()) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); const float rgb = data.brightness * data.opacity; const float a = data.opacity; GLShader *shader = ShaderManager::instance()->pushShader(ShaderManager::SimpleShader); shader->setUniform(GLShader::Offset, QVector2D(0, 0)); shader->setUniform(GLShader::ModulationConstant, QVector4D(rgb, rgb, rgb, a)); shader->setUniform(GLShader::Saturation, data.saturation); shader->setUniform(GLShader::AlphaToOne, 0); cachedTexture->render(textureRect, textureRect); ShaderManager::instance()->popShader(); glDisable(GL_BLEND); } else { prepareRenderStates(cachedTexture, data.opacity, data.brightness, data.saturation); cachedTexture->render(textureRect, textureRect); restoreRenderStates(cachedTexture, data.opacity, data.brightness, data.saturation); } cachedTexture->unbind(); m_timer.start(5000, this); return; } else { // offscreen texture not matching - delete delete cachedTexture; cachedTexture = 0; w->setData(LanczosCacheRole, QVariant()); } } WindowPaintData thumbData = data; thumbData.xScale = 1.0; thumbData.yScale = 1.0; thumbData.xTranslate = -w->x() - left; thumbData.yTranslate = -w->y() - top; thumbData.brightness = 1.0; thumbData.opacity = 1.0; thumbData.saturation = 1.0; // Bind the offscreen FBO and draw the window on it unscaled updateOffscreenSurfaces(); GLRenderTarget::pushRenderTarget(m_offscreenTarget); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); w->sceneWindow()->performPaint(mask, infiniteRegion(), thumbData); // Create a scratch texture and copy the rendered window into it GLTexture tex(sw, sh); tex.setFilter(GL_LINEAR); tex.setWrapMode(GL_CLAMP_TO_EDGE); tex.bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, m_offscreenTex->height() - sh, sw, sh); // Set up the shader for horizontal scaling float dx = sw / float(tw); int kernelSize; m_shader->createKernel(dx, &kernelSize); m_shader->createOffsets(kernelSize, sw, Qt::Horizontal); m_shader->bind(); m_shader->setUniforms(); // Draw the window back into the FBO, this time scaled horizontally glClear(GL_COLOR_BUFFER_BIT); QVector verts; QVector texCoords; verts.reserve(12); texCoords.reserve(12); texCoords << 1.0 << 0.0; verts << tw << 0.0; // Top right texCoords << 0.0 << 0.0; verts << 0.0 << 0.0; // Top left texCoords << 0.0 << 1.0; verts << 0.0 << sh; // Bottom left texCoords << 0.0 << 1.0; verts << 0.0 << sh; // Bottom left texCoords << 1.0 << 1.0; verts << tw << sh; // Bottom right texCoords << 1.0 << 0.0; verts << tw << 0.0; // Top right GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setData(6, 2, verts.constData(), texCoords.constData()); vbo->render(GL_TRIANGLES); // At this point we don't need the scratch texture anymore tex.unbind(); tex.discard(); // create scratch texture for second rendering pass GLTexture tex2(tw, sh); tex2.setFilter(GL_LINEAR); tex2.setWrapMode(GL_CLAMP_TO_EDGE); tex2.bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, m_offscreenTex->height() - sh, tw, sh); // Set up the shader for vertical scaling float dy = sh / float(th); m_shader->createKernel(dy, &kernelSize); m_shader->createOffsets(kernelSize, m_offscreenTex->height(), Qt::Vertical); m_shader->setUniforms(); // Now draw the horizontally scaled window in the FBO at the right // coordinates on the screen, while scaling it vertically and blending it. glClear(GL_COLOR_BUFFER_BIT); verts.clear(); verts << tw << 0.0; // Top right verts << 0.0 << 0.0; // Top left verts << 0.0 << th; // Bottom left verts << 0.0 << th; // Bottom left verts << tw << th; // Bottom right verts << tw << 0.0; // Top right vbo->setData(6, 2, verts.constData(), texCoords.constData()); vbo->render(GL_TRIANGLES); tex2.unbind(); tex2.discard(); m_shader->unbind(); // create cache texture GLTexture *cache = new GLTexture(tw, th); cache->setFilter(GL_LINEAR); cache->setWrapMode(GL_CLAMP_TO_EDGE); cache->bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, m_offscreenTex->height() - th, tw, th); GLRenderTarget::popRenderTarget(); if (ShaderManager::instance()->isValid()) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); const float rgb = data.brightness * data.opacity; const float a = data.opacity; GLShader *shader = ShaderManager::instance()->pushShader(ShaderManager::SimpleShader); shader->setUniform(GLShader::Offset, QVector2D(0, 0)); shader->setUniform(GLShader::ModulationConstant, QVector4D(rgb, rgb, rgb, a)); shader->setUniform(GLShader::Saturation, data.saturation); shader->setUniform(GLShader::AlphaToOne, 0); cache->render(textureRect, textureRect); ShaderManager::instance()->popShader(); glDisable(GL_BLEND); } else { prepareRenderStates(cache, data.opacity, data.brightness, data.saturation); cache->render(textureRect, textureRect); restoreRenderStates(cache, data.opacity, data.brightness, data.saturation); } cache->unbind(); w->setData(LanczosCacheRole, QVariant::fromValue(static_cast(cache))); // Delete the offscreen surface after 5 seconds m_timer.start(5000, this); return; } } // if ( effects->compositingType() == KWin::OpenGLCompositing ) w->sceneWindow()->performPaint(mask, region, data); } // End of function void LanczosFilter::timerEvent(QTimerEvent *event) { if (event->timerId() == m_timer.timerId()) { m_timer.stop(); delete m_offscreenTarget; delete m_offscreenTex; m_offscreenTarget = 0; m_offscreenTex = 0; foreach (EffectWindow * w, effects->stackingOrder()) { QVariant cachedTextureVariant = w->data(LanczosCacheRole); if (cachedTextureVariant.isValid()) { GLTexture *cachedTexture = static_cast< GLTexture*>(cachedTextureVariant.value()); delete cachedTexture; cachedTexture = 0; w->setData(LanczosCacheRole, QVariant()); } } } } void LanczosFilter::prepareRenderStates(GLTexture* tex, double opacity, double brightness, double saturation) { #ifdef KWIN_HAVE_OPENGLES Q_UNUSED(tex) Q_UNUSED(opacity) Q_UNUSED(brightness) Q_UNUSED(saturation) #else const bool alpha = true; // setup blending of transparent windows glPushAttrib(GL_ENABLE_BIT); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); if (saturation != 1.0 && tex->saturationSupported()) { // First we need to get the color from [0; 1] range to [0.5; 1] range glActiveTexture(GL_TEXTURE0); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA); const float scale_constant[] = { 1.0, 1.0, 1.0, 0.5}; glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, scale_constant); tex->bind(); // Then we take dot product of the result of previous pass and // saturation_constant. This gives us completely unsaturated // (greyscale) image // Note that both operands have to be in range [0.5; 1] since opengl // automatically substracts 0.5 from them glActiveTexture(GL_TEXTURE1); float saturation_constant[] = { 0.5 + 0.5 * 0.30, 0.5 + 0.5 * 0.59, 0.5 + 0.5 * 0.11, saturation }; glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_DOT3_RGB); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, saturation_constant); tex->bind(); // Finally we need to interpolate between the original image and the // greyscale image to get wanted level of saturation glActiveTexture(GL_TEXTURE2); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA); glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, saturation_constant); // Also replace alpha by primary color's alpha here glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); // And make primary color contain the wanted opacity glColor4f(opacity, opacity, opacity, opacity); tex->bind(); if (alpha || brightness != 1.0f) { glActiveTexture(GL_TEXTURE3); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); // The color has to be multiplied by both opacity and brightness float opacityByBrightness = opacity * brightness; glColor4f(opacityByBrightness, opacityByBrightness, opacityByBrightness, opacity); if (alpha) { // Multiply original texture's alpha by our opacity glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE0); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); } else { // Alpha will be taken from previous stage glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); } tex->bind(); } glActiveTexture(GL_TEXTURE0); } else if (opacity != 1.0 || brightness != 1.0) { // the window is additionally configured to have its opacity adjusted, // do it float opacityByBrightness = opacity * brightness; if (alpha) { glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glColor4f(opacityByBrightness, opacityByBrightness, opacityByBrightness, opacity); } else { // Multiply color by brightness and replace alpha by opacity float constant[] = { opacityByBrightness, opacityByBrightness, opacityByBrightness, opacity }; glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_CONSTANT); glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, constant); } } #endif } void LanczosFilter::restoreRenderStates(GLTexture* tex, double opacity, double brightness, double saturation) { #ifdef KWIN_HAVE_OPENGLES Q_UNUSED(tex) Q_UNUSED(opacity) Q_UNUSED(brightness) Q_UNUSED(saturation) #else if (opacity != 1.0 || saturation != 1.0 || brightness != 1.0f) { if (saturation != 1.0 && tex->saturationSupported()) { glActiveTexture(GL_TEXTURE3); glDisable(tex->target()); glActiveTexture(GL_TEXTURE2); glDisable(tex->target()); glActiveTexture(GL_TEXTURE1); glDisable(tex->target()); glActiveTexture(GL_TEXTURE0); } } glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glColor4f(0, 0, 0, 0); glPopAttrib(); // ENABLE_BIT #endif } /************************************************ * LanczosShader ************************************************/ LanczosShader::LanczosShader(QObject* parent) : QObject(parent) , m_shader(0) , m_arbProgram(0) { } LanczosShader::~LanczosShader() { delete m_shader; #ifndef KWIN_HAVE_OPENGLES if (m_arbProgram) { glDeleteProgramsARB(1, &m_arbProgram); m_arbProgram = 0; } #endif } void LanczosShader::bind() { if (m_shader) ShaderManager::instance()->pushShader(m_shader); #ifndef KWIN_HAVE_OPENGLES else { glEnable(GL_FRAGMENT_PROGRAM_ARB); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_arbProgram); } #endif } void LanczosShader::unbind() { if (m_shader) ShaderManager::instance()->popShader(); #ifndef KWIN_HAVE_OPENGLES else { int boundObject; glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_BINDING_ARB, &boundObject); if (boundObject == m_arbProgram) { glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); glDisable(GL_FRAGMENT_PROGRAM_ARB); } } #endif } void LanczosShader::setUniforms() { if (m_shader) { glUniform1i(m_uTexUnit, 0); glUniform2fv(m_uOffsets, 16, (const GLfloat*)m_offsets); glUniform4fv(m_uKernel, 16, (const GLfloat*)m_kernel); } #ifndef KWIN_HAVE_OPENGLES else { for (int i = 0; i < 16; ++i) { glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, i, m_offsets[i].x(), m_offsets[i].y(), 0, 0); } for (int i = 0; i < 16; ++i) { glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, i + 16, m_kernel[i].x(), m_kernel[i].y(), m_kernel[i].z(), m_kernel[i].w()); } } #endif } bool LanczosShader::init() { GLPlatform *gl = GLPlatform::instance(); if (gl->supports(GLSL) && ShaderManager::instance()->isValid() && GLRenderTarget::supported() && !(gl->isRadeon() && gl->chipClass() < R600)) { m_shader = ShaderManager::instance()->loadFragmentShader(ShaderManager::SimpleShader, ":/resources/lanczos-fragment.glsl"); if (m_shader->isValid()) { ShaderManager::instance()->pushShader(m_shader); m_uTexUnit = m_shader->uniformLocation("texUnit"); m_uKernel = m_shader->uniformLocation("kernel"); m_uOffsets = m_shader->uniformLocation("offsets"); ShaderManager::instance()->popShader(); return true; } else { kDebug(1212) << "Shader is not valid"; m_shader = 0; // try ARB shader } } #ifdef KWIN_HAVE_OPENGLES // no ARB shader in GLES return false; #else // try to create an ARB Shader if (!hasGLExtension("GL_ARB_fragment_program")) return false; QByteArray text; QTextStream stream(&text); // Note: This program uses 31 temporaries, 61 ALU instructions, 31 texture // fetches, 3 texture indirections and 93 instructions. // The R300 limitations are 32, 64, 32, 4 and 96 respectively. stream << "!!ARBfp1.0\n"; stream << "TEMP sum;\n"; // Declare 30 temporaries for holding texcoords and TEX results for (int i = 0; i < 30; i++) stream << "TEMP temp" << i << ";\n"; // Compute the texture coordinates for (int i = 0, j = 0; i < 30 / 2; i++) { stream << "ADD temp" << j++ << ", fragment.texcoord, program.local[" << i + 1 << "];\n"; stream << "SUB temp" << j++ << ", fragment.texcoord, program.local[" << i + 1 << "];\n"; } // Sample the texture coordinates stream << "TEX sum, fragment.texcoord, texture[0], 2D;\n"; for (int i = 0; i < 30; i++) stream << "TEX temp" << i << ", temp" << i << ", texture[0], 2D;\n"; // Process the results stream << "MUL sum, sum, program.local[16];\n"; // sum = sum * kernel[0] for (int i = 0, j = 0; i < 30 / 2; i++) { stream << "MAD sum, temp" << j++ << ", program.local[" << (17 + i) << "], sum;\n"; stream << "MAD sum, temp" << j++ << ", program.local[" << (17 + i) << "], sum;\n"; } stream << "MOV result.color, sum;\n"; stream << "END\n"; stream.flush(); glEnable(GL_FRAGMENT_PROGRAM_ARB); glGenProgramsARB(1, &m_arbProgram); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_arbProgram); glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, text.length(), text.constData()); if (glGetError()) { const char *error = (const char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB); kError() << "Failed to compile fragment program:" << error; glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); glDeleteProgramsARB(1, &m_arbProgram); glDisable(GL_FRAGMENT_PROGRAM_ARB); m_arbProgram = 0; return false; } glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); glDisable(GL_FRAGMENT_PROGRAM_ARB); kDebug(1212) << "ARB Shader compiled, id: " << m_arbProgram; return true; #endif } } // namespace