diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -156,6 +156,7 @@ kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp + opengl/kis_opengl_accumulator.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp --- a/libs/ui/canvas/kis_canvas2.cpp +++ b/libs/ui/canvas/kis_canvas2.cpp @@ -113,6 +113,7 @@ KisSelectedShapesProxy selectedShapesProxy; bool currentCanvasIsOpenGL; int openGLFilterMode; + bool useFBOForOpenGLUpdates; KisToolProxy toolProxy; KisPrescaledProjectionSP prescaledProjection; bool vastScrolling; @@ -458,6 +459,7 @@ { KisConfig cfg; m_d->openGLFilterMode = cfg.openGLFilteringMode(); + m_d->useFBOForOpenGLUpdates = cfg.useFBOForOpenGLUpdates(); m_d->currentCanvasIsOpenGL = true; KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter); @@ -542,7 +544,9 @@ KisConfig cfg; bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) || (m_d->currentCanvasIsOpenGL && - m_d->openGLFilterMode != cfg.openGLFilteringMode()); + m_d->openGLFilterMode != cfg.openGLFilteringMode()) || + (m_d->currentCanvasIsOpenGL && + m_d->useFBOForOpenGLUpdates != cfg.useFBOForOpenGLUpdates()); if (needReset) { createCanvas(useOpenGL); diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -870,6 +870,7 @@ grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); + chkIncrementalUpdates->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { @@ -877,6 +878,8 @@ grpOpenGL->setChecked(cfg.useOpenGL()); chkUseTextureBuffer->setEnabled(cfg.useOpenGL()); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer()); + chkIncrementalUpdates->setEnabled(cfg.useOpenGL() && KisOpenGL::supportsFBO()); + chkIncrementalUpdates->setChecked(cfg.useFBOForOpenGLUpdates()); chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings()); chkDisableVsync->setEnabled(cfg.useOpenGL()); chkDisableVsync->setChecked(cfg.disableVSync()); @@ -938,6 +941,7 @@ grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); + chkIncrementalUpdates->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } @@ -946,6 +950,8 @@ grpOpenGL->setChecked(cfg.useOpenGL(true)); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true)); chkUseTextureBuffer->setEnabled(true); + chkIncrementalUpdates->setChecked(cfg.useFBOForOpenGLUpdates(true)); + chkIncrementalUpdates->setEnabled(cfg.useOpenGL() && KisOpenGL::supportsFBO()); chkDisableVsync->setEnabled(true); chkDisableVsync->setChecked(cfg.disableVSync(true)); cmbFilterMode->setEnabled(true); @@ -978,6 +984,7 @@ void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked) { chkUseTextureBuffer->setEnabled(isChecked); + chkIncrementalUpdates->setEnabled(isChecked && KisOpenGL::supportsFBO()); chkDisableVsync->setEnabled(isChecked); cmbFilterMode->setEnabled(isChecked); } @@ -1254,6 +1261,7 @@ cfg.setCanvasState("TRY_OPENGL"); cfg.setUseOpenGL(dialog->m_displaySettings->grpOpenGL->isChecked()); cfg.setUseOpenGLTextureBuffer(dialog->m_displaySettings->chkUseTextureBuffer->isChecked()); + cfg.setUseFBOForOpenGLUpdates(dialog->m_displaySettings->chkIncrementalUpdates->isChecked()); cfg.setOpenGLFilteringMode(dialog->m_displaySettings->cmbFilterMode->currentIndex()); cfg.setDisableVSync(dialog->m_displaySettings->chkDisableVsync->isChecked()); diff --git a/libs/ui/forms/wdgdisplaysettings.ui b/libs/ui/forms/wdgdisplaysettings.ui --- a/libs/ui/forms/wdgdisplaysettings.ui +++ b/libs/ui/forms/wdgdisplaysettings.ui @@ -71,7 +71,7 @@ - + @@ -90,6 +90,22 @@ + + + + + 0 + 0 + + + + <html><head/><body><p>This applies incremental updates to an FBO; even though the FBO is still drawn as whole, on large canvases with small changes this can be faster than the default update mechanism.</p></body></html> + + + Incremental screen updates + + + diff --git a/libs/ui/kis_config.h b/libs/ui/kis_config.h --- a/libs/ui/kis_config.h +++ b/libs/ui/kis_config.h @@ -172,6 +172,9 @@ bool useOpenGLTextureBuffer(bool defaultValue = false) const; void setUseOpenGLTextureBuffer(bool useBuffer); + bool useFBOForOpenGLUpdates(bool defaultValue = false) const; + void setUseFBOForOpenGLUpdates(bool useFBO); + bool disableVSync(bool defaultValue = false) const; void setDisableVSync(bool disableVSync); diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -670,6 +670,23 @@ m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer); } +bool KisConfig::useFBOForOpenGLUpdates(bool defaultValue) const +{ + return (defaultValue ? false : m_cfg.readEntry("useFBOForOpenGLUpdates", false)); +} + +void KisConfig::setUseFBOForOpenGLUpdates(bool useFBO) +{ + if (useFBO == useFBOForOpenGLUpdates(true)) { + // never write the default value but instead construct it when reading again. + if (m_cfg.hasKey("useFBOForOpenGLUpdates")) { + m_cfg.deleteEntry("useFBOForOpenGLUpdates"); + } + } else { + m_cfg.writeEntry("useFBOForOpenGLUpdates", useFBO); + } +} + int KisConfig::openGLTextureSize(bool defaultValue) const { return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256)); diff --git a/libs/ui/opengl/kis_opengl.h b/libs/ui/opengl/kis_opengl.h --- a/libs/ui/opengl/kis_opengl.h +++ b/libs/ui/opengl/kis_opengl.h @@ -76,6 +76,7 @@ static const QString &getDebugText(); static bool supportsLoD(); + static bool supportsFBO(); static bool hasOpenGL3(); static bool hasOpenGLES(); diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp --- a/libs/ui/opengl/kis_opengl.cpp +++ b/libs/ui/opengl/kis_opengl.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -58,6 +59,8 @@ int glMinorVersion = 0; bool supportsDeprecatedFunctions = false; bool isOpenGLES = false; + bool supportsFramebufferObjects = false; + bool supportsFramebufferBlit = false; QString rendererString; QString driverVersionString; @@ -74,6 +77,8 @@ glMinorVersion = context.format().minorVersion(); supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); isOpenGLES = context.isOpenGLES(); + supportsFramebufferObjects = QOpenGLFramebufferObject::hasOpenGLFramebufferObjects(); + supportsFramebufferBlit = QOpenGLFramebufferObject::hasOpenGLFramebufferBlit(); } bool isSupportedVersion() const { @@ -91,6 +96,10 @@ return (glMajorVersion * 100 + glMinorVersion) >= 300; } + bool supportsFBO() const { + return supportsFramebufferObjects && supportsFramebufferBlit; + } + bool hasOpenGL3() const { return (glMajorVersion * 100 + glMinorVersion) >= 302; } @@ -427,6 +436,8 @@ debugOut.resetFormat(); debugOut << "\n Supports deprecated functions" << openGLCheckResult->supportsDeprecatedFunctions; debugOut << "\n is OpenGL ES:" << openGLCheckResult->isOpenGLES; + debugOut << "\n Supports FBO objects:" << openGLCheckResult->supportsFramebufferObjects; + debugOut << "\n Supports FBO blit:" << openGLCheckResult->supportsFramebufferBlit; #ifdef Q_OS_WIN debugOut << "\n\nQPA OpenGL Detection Info"; debugOut << "\n supportsDesktopGL:" << windowsOpenGLStatus.supportsDesktopGL; @@ -535,6 +546,12 @@ return openGLCheckResult->supportsLoD(); } +bool KisOpenGL::supportsFBO() +{ + initialize(); + return openGLCheckResult->supportsFBO(); +} + bool KisOpenGL::hasOpenGL3() { initialize(); diff --git a/libs/ui/opengl/kis_opengl_accumulator.h b/libs/ui/opengl/kis_opengl_accumulator.h new file mode 100644 --- /dev/null +++ b/libs/ui/opengl/kis_opengl_accumulator.h @@ -0,0 +1,124 @@ +/* + * Copyright (c) 20017 Bernhard Liebl + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef KIS_OPENGL_ACCUMULATOR_H_ +#define KIS_OPENGL_ACCUMULATOR_H_ + +#include +#include +#include "kis_opengl_image_textures.h" + +#include +#include + +class KisOpenGLAccumulator; +typedef KisSharedPtr KisOpenGLAccumulatorSP; + +class QOpenGLWidget; +class KisCoordinatesConverter; +class KisOpenGLImageTextures; + +class KisOpenGLAccumulator : public KisShared +{ +public: + KisOpenGLAccumulator( + QOpenGLWidget *widget, + KisCoordinatesConverter *converter, + const KisOpenGLImageTexturesSP &textures); + + ~KisOpenGLAccumulator(); + + inline bool updateColumn(int effectiveCol, bool incremental) { + const quint64 version = m_textures->getColumnVersion(effectiveCol); + return m_columnVersions.update(effectiveCol, version, m_drawId, incremental); + } + + inline bool updateTile(int effectiveCol, int effectiveRow, bool incremental) { + const int tileIndex = m_textures->getTextureTileIndexCR(effectiveCol, effectiveRow); + const quint64 version = m_textures->getTextureTileVersionCR(effectiveCol, effectiveRow); + return m_tileVersions.update(tileIndex, version, m_drawId, incremental); + } + + bool bind(const QColor &backgroundColor); + + void blit(); + + void update(); + +private: + QOpenGLWidget *m_widget; + KisCoordinatesConverter *m_converter; + KisOpenGLImageTexturesSP m_textures; + + class Versions { + public: + inline bool update(int index, quint64 version, quint64 drawId, bool incremental) { + if (index >= m_versions.size()) { + m_versions.resize(index + 1); + } + + Record &r = m_versions[index]; + + // "version" is the version number of the item to be drawn. "drawId" is the sequence number of + // the draw call (i.e. each image composition call gets a number). + + if (incremental) { + if (version != r.version) { + // if the version of the item is not up to date, we need to draw. remember the version + // we draw. + + r.version = version; + r.drawId = drawId; + return true; + } else if (drawId == r.drawId) { + // with wrapped mode, it happens that the same tile is drawn multiple times within + // the same draw call (i.e. drawId) - as we want to draw all instances of this changed + // item, and not only the first one, we also draw if the drawId is the same (i.e. this + // item was updated to the newest version during this current draw call). + + return true; + } else { + return false; + } + } else { + // always draw. remember the version we draw. + + r.version = version; + r.drawId = drawId; + return true; + } + } + + private: + struct Record { + quint64 version; + quint64 drawId; + }; + + QVector m_versions; + }; + + QOpenGLFramebufferObject *m_foreground; + quint64 m_drawId; + bool m_isDirty; + + QMatrix4x4 m_modelMatrix; + Versions m_tileVersions; + Versions m_columnVersions; +}; + +#endif // KIS_OPENGL_ACCUMULATOR_H_ diff --git a/libs/ui/opengl/kis_opengl_accumulator.cpp b/libs/ui/opengl/kis_opengl_accumulator.cpp new file mode 100644 --- /dev/null +++ b/libs/ui/opengl/kis_opengl_accumulator.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 20017 Bernhard Liebl + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kis_opengl_accumulator.h" + +#include +#include +#include + +#include + +KisOpenGLAccumulator::KisOpenGLAccumulator( + QOpenGLWidget *widget, + KisCoordinatesConverter *converter, + const KisOpenGLImageTexturesSP &textures) + : m_widget(widget) + , m_converter(converter) + , m_textures(textures) + , m_foreground(nullptr) + , m_drawId(1) + , m_isDirty(true) +{ + m_modelMatrix.scale(0.0f); // force update +} + +KisOpenGLAccumulator::~KisOpenGLAccumulator() +{ + delete m_foreground; +} + +bool KisOpenGLAccumulator::bind(const QColor &backgroundColor) +{ + m_drawId++; + + QOpenGLContext *context = m_widget->context(); + QOpenGLFunctions *f = context->functions(); + bool incremental = true; + + const QMatrix4x4 modelMatrix(m_converter->imageToWidgetTransform()); + + if (m_modelMatrix != modelMatrix) { + incremental = false; + m_modelMatrix = modelMatrix; + } + + const qreal retinaScale = m_widget->devicePixelRatioF(); + const int width = m_widget->width() * retinaScale; + const int height = m_widget->height() * retinaScale; + + if (!m_foreground || m_foreground->width() != width || m_foreground->height() != height) { + delete m_foreground; + m_foreground = nullptr; + + QOpenGLFramebufferObjectFormat foregroundFormat; + foregroundFormat.setAttachment(QOpenGLFramebufferObject::NoAttachment); + m_foreground = new QOpenGLFramebufferObject(width, height, foregroundFormat); + + incremental = false; + } + + KIS_ASSERT(m_foreground && m_foreground->isValid()); + + if (m_isDirty) { + incremental = false; + m_isDirty = false; + } + + m_foreground->bind(); + f->glViewport(0, 0, width, height); + + if (!incremental) { + f->glClearColor(backgroundColor.redF(), backgroundColor.greenF(), backgroundColor.blueF(), 1.0); + f->glClear(GL_COLOR_BUFFER_BIT); + } + + return incremental; +} + +void KisOpenGLAccumulator::blit() +{ + Q_ASSERT(m_foreground); + m_foreground->release(); + + const QSize size = m_foreground->size(); + QOpenGLFramebufferObject::bindDefault(); + QOpenGLFramebufferObject::blitFramebuffer( + 0, QRect(0, 0, size.width(), size.height()), + m_foreground, QRect(0, 0, size.width(), size.height()), GL_COLOR_BUFFER_BIT); +} + +void KisOpenGLAccumulator::update() +{ + m_isDirty = true; +} diff --git a/libs/ui/opengl/kis_opengl_canvas2.h b/libs/ui/opengl/kis_opengl_canvas2.h --- a/libs/ui/opengl/kis_opengl_canvas2.h +++ b/libs/ui/opengl/kis_opengl_canvas2.h @@ -111,9 +111,10 @@ void initializeDisplayShader(); void reportFailedShaderCompilation(const QString &context); - void drawImage(); + void drawImage(bool incremental); void drawCheckers(); void drawGrid(); + bool bindDisplayShader(); private: diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -22,6 +22,7 @@ #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl_canvas2_p.h" +#include "opengl/kis_opengl_accumulator.h" #include "opengl/kis_opengl_shader_loader.h" #include "opengl/kis_opengl_canvas_debugger.h" #include "canvas/kis_canvas2.h" @@ -31,6 +32,7 @@ #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_debug.h" +#include "kis_group_layer.h" #include #include @@ -41,7 +43,9 @@ #include #include #include +#include #include +#include #include #ifndef Q_OS_OSX @@ -73,6 +77,7 @@ bool canvasInitialized{false}; KisOpenGLImageTexturesSP openGLImageTextures; + KisOpenGLAccumulatorSP accumulator; KisOpenGLShaderLoader shaderLoader; KisShaderProgram *displayShader{0}; @@ -92,6 +97,7 @@ bool wrapAroundMode{false}; // Stores a quad for drawing the canvas + bool hasQuadVAO{false}; QOpenGLVertexArrayObject quadVAO; QOpenGLBuffer quadBuffers[2]; @@ -132,7 +138,6 @@ return rowsPerImage * numWraps + openGLImageTextures->yToRow(remainder); } - }; KisOpenGLCanvas2::KisOpenGLCanvas2(KisCanvas2 *canvas, @@ -202,6 +207,9 @@ void KisOpenGLCanvas2::setWrapAroundViewingMode(bool value) { + if (d->accumulator && value != d->wrapAroundMode) { + d->accumulator->update(); + } d->wrapAroundMode = value; update(); } @@ -250,25 +258,27 @@ // If we support OpenGL 3.2, then prepare our VAOs and VBOs for drawing if (KisOpenGL::hasOpenGL3()) { - d->quadVAO.create(); - d->quadVAO.bind(); - - glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); - glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE); - - // Create the vertex buffer object, it has 6 vertices with 3 components - d->quadBuffers[0].create(); - d->quadBuffers[0].setUsagePattern(QOpenGLBuffer::StaticDraw); - d->quadBuffers[0].bind(); - d->quadBuffers[0].allocate(d->vertices, 6 * 3 * sizeof(float)); - glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); - - // Create the texture buffer object, it has 6 texture coordinates with 2 components - d->quadBuffers[1].create(); - d->quadBuffers[1].setUsagePattern(QOpenGLBuffer::StaticDraw); - d->quadBuffers[1].bind(); - d->quadBuffers[1].allocate(d->texCoords, 6 * 2 * sizeof(float)); - glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0); + d->hasQuadVAO = d->quadVAO.create(); + if (d->hasQuadVAO) { + d->quadVAO.bind(); + + glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); + glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE); + + // Create the vertex buffer object, it has 6 vertices with 3 components + d->quadBuffers[0].create(); + d->quadBuffers[0].setUsagePattern(QOpenGLBuffer::StaticDraw); + d->quadBuffers[0].bind(); + d->quadBuffers[0].allocate(d->vertices, 6 * 3 * sizeof(float)); + glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); + + // Create the texture buffer object, it has 6 texture coordinates with 2 components + d->quadBuffers[1].create(); + d->quadBuffers[1].setUsagePattern(QOpenGLBuffer::StaticDraw); + d->quadBuffers[1].bind(); + d->quadBuffers[1].allocate(d->texCoords, 6 * 2 * sizeof(float)); + glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0); + } // Create the outline buffer, this buffer will store the outlines of // tools and will frequently change data @@ -286,6 +296,14 @@ Sync::init(context()); + if (cfg.useFBOForOpenGLUpdates()) { + if (KisOpenGL::supportsFBO()) { + d->accumulator = new KisOpenGLAccumulator(this, coordinatesConverter(), d->openGLImageTextures); + } else { + qWarning() << "cannot enable FBO updates as there is no support on this system."; + } + } + d->canvasInitialized = true; } @@ -606,40 +624,15 @@ glDisable(GL_BLEND); } -void KisOpenGLCanvas2::drawImage() +void KisOpenGLCanvas2::drawImage(bool incremental) { - if (!d->displayShader) { - return; - } - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - KisCoordinatesConverter *converter = coordinatesConverter(); - d->displayShader->bind(); - - QMatrix4x4 projectionMatrix; - projectionMatrix.setToIdentity(); - projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); - - // Set view/projection matrices - QMatrix4x4 modelMatrix(converter->imageToWidgetTransform()); - modelMatrix.optimize(); - modelMatrix = projectionMatrix * modelMatrix; - d->displayShader->setUniformValue(d->displayShader->location(Uniform::ModelViewProjection), modelMatrix); - - QMatrix4x4 textureMatrix; - textureMatrix.setToIdentity(); - d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix); - QRectF widgetRect(0,0, width(), height()); QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect)); qreal scaleX, scaleY; converter->imageScale(&scaleX, &scaleY); - d->displayShader->setUniformValue(d->displayShader->location(Uniform::ViewportScale), (GLfloat) scaleX); - d->displayShader->setUniformValue(d->displayShader->location(Uniform::TexelSize), (GLfloat) d->openGLImageTextures->texelSize()); QRect ir = d->openGLImageTextures->storedImageBounds(); QRect wr = widgetRectInImagePixels.toAlignedRect(); @@ -650,121 +643,208 @@ wr &= ir; } - int firstColumn = d->xToColWithWrapCompensation(wr.left(), ir); - int lastColumn = d->xToColWithWrapCompensation(wr.right(), ir); - int firstRow = d->yToRowWithWrapCompensation(wr.top(), ir); - int lastRow = d->yToRowWithWrapCompensation(wr.bottom(), ir); + const int firstColumn = d->xToColWithWrapCompensation(wr.left(), ir); + const int lastColumn = d->xToColWithWrapCompensation(wr.right(), ir); + const int firstRow = d->yToRowWithWrapCompensation(wr.top(), ir); + const int lastRow = d->yToRowWithWrapCompensation(wr.bottom(), ir); + + const int minColumn = d->openGLImageTextures->xToCol(ir.left()); + const int maxColumn = d->openGLImageTextures->xToCol(ir.right()); + const int minRow = d->openGLImageTextures->yToRow(ir.top()); + const int maxRow = d->openGLImageTextures->yToRow(ir.bottom()); - int minColumn = d->openGLImageTextures->xToCol(ir.left()); - int maxColumn = d->openGLImageTextures->xToCol(ir.right()); - int minRow = d->openGLImageTextures->yToRow(ir.top()); - int maxRow = d->openGLImageTextures->yToRow(ir.bottom()); + const int imageColumns = maxColumn - minColumn + 1; + const int imageRows = maxRow - minRow + 1; - int imageColumns = maxColumn - minColumn + 1; - int imageRows = maxRow - minRow + 1; + KisOpenGLImageTextures const *textures = d->openGLImageTextures; + + typedef QPair Column; + QVector columns; + columns.reserve(lastColumn - firstColumn + 1); for (int col = firstColumn; col <= lastColumn; col++) { - for (int row = firstRow; row <= lastRow; row++) { + int effectiveCol = col; + float rx = 0; + + if (effectiveCol > maxColumn || effectiveCol < minColumn) { + int translationStep = floor(qreal(col) / imageColumns); + int originCol = translationStep * imageColumns; + effectiveCol = col - originCol; + rx = translationStep * ir.width(); + } + + if (d->accumulator && !d->accumulator->updateColumn(effectiveCol, incremental)) { + continue; + } - int effectiveCol = col; - int effectiveRow = row; - QPointF tileWrappingTranslation; + columns.push_back(QPair(effectiveCol, rx)); + } - if (effectiveCol > maxColumn || effectiveCol < minColumn) { - int translationStep = floor(qreal(col) / imageColumns); - int originCol = translationStep * imageColumns; - effectiveCol = col - originCol; - tileWrappingTranslation.rx() = translationStep * ir.width(); + // incremental updates needs two passes because of the tile's alpha channel. if we draw + // a non-opaque tile, we need to make sure that the checkers background has been drawn + // below first; otherwise multiple updates would make multiple tile versions visible due + // to transparency. however, since we only update certain tiles, we do not want to draw + // checkers on the whole screen, but only where tile updates happens (otherwise we'd not + // be incremental). therefore we create a "changed tiles mask" in pass 0, and then draw + // the checkers background using that mask and the changed tiles in pass 1. + + // for non-incremental updates, we skip pass 0 and only do pass 1. + + for (int pass = incremental ? 0 : 1; pass < 2; pass++) { + if (incremental) { + if (pass == 0) { + // in this pass, we produce an alpha mask that has 0 where + // a tile lies that will be redrawn, and 1 everywhere else. + // we will use this mask in pass 1 to only redraw those parts + // of the checker pattern that lie under updated tiles. we do + // not touch the rgb channels in pass 0. + + // only draw into the alpha channel, don't touch rgb. + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); + + // initialize alpha mask with 1 (i.e. "don't draw" for pass 1). + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + + // make all glDrawArrays calls draw untextured 0-alpha polygons + // (i.e. mark "do draw" pixels for pass 1). + glEnable(GL_BLEND); + glBlendFunc(GL_ZERO, GL_ZERO); + + // now bind the display shader, as we need it to draw the correct + // tiles (using the modelRect we calculate below). + if (!bindDisplayShader()) { + return; + } + glBindTexture(GL_TEXTURE_2D, 0); + } else { + // incrementally restore the checkers pattern, but only where tile + // updates happen. use the alpha mask from pass 0 to achieve this. + glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); } + } - if (effectiveRow > maxRow || effectiveRow < minRow) { - int translationStep = floor(qreal(row) / imageRows); - int originRow = translationStep * imageRows; - effectiveRow = row - originRow; - tileWrappingTranslation.ry() = translationStep * ir.height(); - } + if (pass == 1) { + // draw checkers; for incremental updates in pass 1, this will use the + // alpha mask configured just above. note that in incremental mode, + // checkers are drawn with GL_BLEND enabled (in normal mode not). - KisTextureTile *tile = - d->openGLImageTextures->getTextureTileCR(effectiveCol, effectiveRow); + drawCheckers(); - if (!tile) { - warnUI << "OpenGL: Trying to paint texture tile but it has not been created yet."; - continue; + // prepare for drawing the regular tiles. + if (!bindDisplayShader()) { + return; } - /* - * We create a float rect here to workaround Qt's - * "history reasons" in calculation of right() - * and bottom() coordinates of integer rects. - */ - QRectF textureRect(tile->tileRectInTexturePixels()); - QRectF modelRect(tile->tileRectInImagePixels().translated(tileWrappingTranslation.x(), tileWrappingTranslation.y())); - - //Setup the geometry for rendering - if (KisOpenGL::hasOpenGL3()) { - rectToVertices(d->vertices, modelRect); - d->quadBuffers[0].bind(); - d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); - - rectToTexCoords(d->texCoords, textureRect); - d->quadBuffers[1].bind(); - d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); - } - else { - rectToVertices(d->vertices, modelRect); - d->displayShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); - d->displayShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); - - rectToTexCoords(d->texCoords, textureRect); - d->displayShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); - d->displayShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); - } + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } - if (d->displayFilter) { - glActiveTexture(GL_TEXTURE0 + 1); - glBindTexture(GL_TEXTURE_3D, d->displayFilter->lutTexture()); - d->displayShader->setUniformValue(d->displayShader->location(Uniform::Texture1), 1); - } + Q_FOREACH (const Column &col, columns) { + const int effectiveCol = col.first; - int currentLodPlane = tile->currentLodPlane(); - if (d->displayShader->location(Uniform::FixedLodLevel) >= 0) { - d->displayShader->setUniformValue(d->displayShader->location(Uniform::FixedLodLevel), - (GLfloat) currentLodPlane); - } + for (int row = firstRow; row <= lastRow; row++) { + QPointF tileWrappingTranslation(col.second, 0.0); + int effectiveRow = row; - glActiveTexture(GL_TEXTURE0); - tile->bindToActiveTexture(); + if (effectiveRow > maxRow || effectiveRow < minRow) { + int translationStep = floor(qreal(row) / imageRows); + int originRow = translationStep * imageRows; + effectiveRow = row - originRow; + tileWrappingTranslation.setY(translationStep * ir.height()); + } - if (currentLodPlane > 0) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); - } else if (SCALE_MORE_OR_EQUAL_TO(scaleX, scaleY, 2.0)) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - switch(d->filterMode) { - case KisOpenGL::NearestFilterMode: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - break; - case KisOpenGL::BilinearFilterMode: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - break; - case KisOpenGL::TrilinearFilterMode: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - break; - case KisOpenGL::HighQualityFiltering: - if (SCALE_LESS_THAN(scaleX, scaleY, 0.5)) { + if (d->accumulator && !d->accumulator->updateTile(effectiveCol, effectiveRow, incremental)) { + continue; + } + + KisTextureTile *tile = textures->getTextureTileCR(effectiveCol, effectiveRow); + + if (!tile) { + warnUI << "OpenGL: Trying to paint texture tile but it has not been created yet."; + continue; + } + + /* + * We create a float rect here to workaround Qt's + * "history reasons" in calculation of right() + * and bottom() coordinates of integer rects. + */ + QRectF textureRect(tile->tileRectInTexturePixels()); + QRectF modelRect(tile->tileRectInImagePixels().translated(tileWrappingTranslation.x(), tileWrappingTranslation.y())); + + //Setup the geometry for rendering + if (KisOpenGL::hasOpenGL3()) { + rectToVertices(d->vertices, modelRect); + d->quadBuffers[0].bind(); + d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); + + rectToTexCoords(d->texCoords, textureRect); + d->quadBuffers[1].bind(); + d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); + } + else { + rectToVertices(d->vertices, modelRect); + d->displayShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); + d->displayShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); + + rectToTexCoords(d->texCoords, textureRect); + d->displayShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); + d->displayShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); + } + + if (pass > 0) { + // only configure the texture if we're not in pass 0 (which is the incremental update + // alpha mask creation pass). + + if (d->displayFilter) { + glActiveTexture(GL_TEXTURE0 + 1); + glBindTexture(GL_TEXTURE_3D, d->displayFilter->lutTexture()); + d->displayShader->setUniformValue(d->displayShader->location(Uniform::Texture1), 1); + } + + int currentLodPlane = tile->currentLodPlane(); + if (d->displayShader->location(Uniform::FixedLodLevel) >= 0) { + d->displayShader->setUniformValue(d->displayShader->location(Uniform::FixedLodLevel), + (GLfloat) currentLodPlane); + } + + glActiveTexture(GL_TEXTURE0); + tile->bindToActiveTexture(); + + if (currentLodPlane > 0) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + } else if (SCALE_MORE_OR_EQUAL_TO(scaleX, scaleY, 2.0)) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + switch (d->filterMode) { + case KisOpenGL::NearestFilterMode: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + break; + case KisOpenGL::BilinearFilterMode: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + break; + case KisOpenGL::TrilinearFilterMode: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + break; + case KisOpenGL::HighQualityFiltering: + if (SCALE_LESS_THAN(scaleX, scaleY, 0.5)) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + break; + } } - break; } - } - glDrawArrays(GL_TRIANGLES, 0, 6); + glDrawArrays(GL_TRIANGLES, 0, 6); + } } } @@ -774,6 +854,38 @@ glDisable(GL_BLEND); } +bool KisOpenGLCanvas2::bindDisplayShader() +{ + if (!d->displayShader) { + return false; + } + + d->displayShader->bind(); + + KisCoordinatesConverter *converter = coordinatesConverter(); + + QMatrix4x4 projectionMatrix; + projectionMatrix.setToIdentity(); + projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); + + // Set view/projection matrices + QMatrix4x4 modelMatrix(converter->imageToWidgetTransform()); + modelMatrix.optimize(); + modelMatrix = projectionMatrix * modelMatrix; + d->displayShader->setUniformValue(d->displayShader->location(Uniform::ModelViewProjection), modelMatrix); + + QMatrix4x4 textureMatrix; + textureMatrix.setToIdentity(); + d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix); + + qreal scaleX, scaleY; + converter->imageScale(&scaleX, &scaleY); + d->displayShader->setUniformValue(d->displayShader->location(Uniform::ViewportScale), (GLfloat) scaleX); + d->displayShader->setUniformValue(d->displayShader->location(Uniform::TexelSize), (GLfloat) d->openGLImageTextures->texelSize()); + + return true; +} + void KisOpenGLCanvas2::slotConfigChanged() { KisConfig cfg; @@ -804,10 +916,13 @@ void KisOpenGLCanvas2::renderCanvasGL() { - // Draw the border (that is, clear the whole widget to the border color) - QColor widgetBackgroundColor = borderColor(); - glClearColor(widgetBackgroundColor.redF(), widgetBackgroundColor.greenF(), widgetBackgroundColor.blueF(), 1.0); - glClear(GL_COLOR_BUFFER_BIT); + const QColor widgetBackgroundColor = borderColor(); + + if (!d->accumulator) { + // Draw the border (that is, clear the whole widget to the border color) + glClearColor(widgetBackgroundColor.redF(), widgetBackgroundColor.greenF(), widgetBackgroundColor.blueF(), 1.0); + glClear(GL_COLOR_BUFFER_BIT); + } if ((d->displayFilter && d->displayFilter->updateShader()) || (bool(d->displayFilter) != d->displayShaderCompiledWithDisplayFilterSupport)) { @@ -819,16 +934,31 @@ d->canvasInitialized = true; } - if (KisOpenGL::hasOpenGL3()) { + bool incremental = false; + + if (d->accumulator) { + incremental = d->accumulator->bind(widgetBackgroundColor); + } + + if (d->hasQuadVAO) { d->quadVAO.bind(); } - drawCheckers(); - drawImage(); + drawImage(incremental); + + if (d->accumulator) { + d->accumulator->blit(); + + if (d->hasQuadVAO) { + d->quadVAO.bind(); + } + } + if ((coordinatesConverter()->effectiveZoom() > d->pixelGridDrawingThreshold - 0.00001) && d->pixelGridEnabled) { drawGrid(); } - if (KisOpenGL::hasOpenGL3()) { + + if (d->hasQuadVAO) { d->quadVAO.release(); } } diff --git a/libs/ui/opengl/kis_opengl_image_textures.h b/libs/ui/opengl/kis_opengl_image_textures.h --- a/libs/ui/opengl/kis_opengl_image_textures.h +++ b/libs/ui/opengl/kis_opengl_image_textures.h @@ -117,9 +117,35 @@ return y / m_texturesInfo.effectiveHeight; } - inline KisTextureTile* getTextureTileCR(int col, int row) { + inline int getTextureTileIndexCR(int col, int row) const { + return row * m_numCols + col; + } + + inline quint64 getTextureTileVersionCR(int col, int row) const { + if (m_initialized) { + const int tile = getTextureTileIndexCR(col, row); + KIS_ASSERT_RECOVER_RETURN_VALUE(m_tileVersions.size() > tile, 0); + return m_tileVersions[tile]; + } + return 0; + } + + inline quint64 getColumnVersion(int col) const { + KIS_ASSERT_RECOVER_RETURN_VALUE(m_columnVersions.size() > col, 0); + return m_columnVersions[col]; + } + + inline void updateTextureTileVersionCR(int col, int row) { + const int tile = getTextureTileIndexCR(col, row); + KIS_ASSERT_RECOVER_RETURN(m_tileVersions.size() > tile && m_columnVersions.size() > col); + // with 1000 updates per second, this will last > 500 million years. + m_tileVersions[tile]++; + m_columnVersions[col]++; + } + + inline KisTextureTile* getTextureTileCR(int col, int row) const { if (m_initialized) { - int tile = row * m_numCols + col; + const int tile = getTextureTileIndexCR(col, row); KIS_ASSERT_RECOVER_RETURN_VALUE(m_textureTiles.size() > tile, 0); return m_textureTiles[tile]; } @@ -190,6 +216,8 @@ KisGLTexturesInfo m_texturesInfo; int m_numCols; QVector m_textureTiles; + QVector m_tileVersions; + QVector m_columnVersions; QOpenGLFunctions *m_glFuncs; QBitArray m_channelFlags; diff --git a/libs/ui/opengl/kis_opengl_image_textures.cpp b/libs/ui/opengl/kis_opengl_image_textures.cpp --- a/libs/ui/opengl/kis_opengl_image_textures.cpp +++ b/libs/ui/opengl/kis_opengl_image_textures.cpp @@ -221,21 +221,27 @@ m_initialized = true; dbgUI << "OpenGL: creating texture tiles of size" << m_texturesInfo.height << "x" << m_texturesInfo.width; - m_textureTiles.reserve((lastRow+1)*m_numCols); + const int nTiles = (lastRow+1)*m_numCols; + + m_textureTiles.reserve(nTiles); for (int row = 0; row <= lastRow; row++) { for (int col = 0; col <= lastCol; col++) { QRect tileRect = calculateTileRect(col, row); - KisTextureTile *tile = new KisTextureTile(tileRect, + KisTextureTile *tile = new KisTextureTile(QPoint(col, row), + tileRect, &m_texturesInfo, emptyTileData, mode, config.useOpenGLTextureBuffer(), config.numMipmapLevels(), - f); + f, this); m_textureTiles.append(tile); } } + + m_tileVersions.resize(nTiles); + m_columnVersions.resize(m_numCols); } else { dbgUI << "Tried to init texture tiles without a current OpenGL Context."; @@ -250,6 +256,8 @@ delete tile; } m_textureTiles.clear(); + m_tileVersions.clear(); + m_columnVersions.clear(); m_storedImageBounds = QRect(); } diff --git a/libs/ui/opengl/kis_texture_tile.h b/libs/ui/opengl/kis_texture_tile.h --- a/libs/ui/opengl/kis_texture_tile.h +++ b/libs/ui/opengl/kis_texture_tile.h @@ -30,6 +30,7 @@ #endif class KisTextureTileUpdateInfo; +class KisOpenGLImageTextures; class QOpenGLBuffer; @@ -67,9 +68,10 @@ class KisTextureTile { public: - KisTextureTile(const QRect &imageRect, const KisGLTexturesInfo *texturesInfo, + KisTextureTile(const QPoint &index, const QRect &imageRect, const KisGLTexturesInfo *texturesInfo, const QByteArray &fillData, KisOpenGL::FilterMode mode, - bool useBuffer, int numMipmapLevels, QOpenGLFunctions *f); + bool useBuffer, int numMipmapLevels, QOpenGLFunctions *f, + KisOpenGLImageTextures *textures); ~KisTextureTile(); void setUseBuffer(bool useBuffer) { @@ -97,6 +99,10 @@ void bindToActiveTexture(); int currentLodPlane() const; + quint64 version() const { + return m_version; + } + private: inline void setTextureParameters(); @@ -104,6 +110,7 @@ void setCurrentLodPlane(int lod); GLuint m_textureId; + quint64 m_version; #ifdef USE_PIXEL_BUFFERS void createTextureBuffer(const char*data, int size); @@ -120,6 +127,8 @@ bool m_useBuffer; int m_numMipmapLevels; QOpenGLFunctions *f; + KisOpenGLImageTextures *m_textures; + QPoint m_index; Q_DISABLE_COPY(KisTextureTile) }; diff --git a/libs/ui/opengl/kis_texture_tile.cpp b/libs/ui/opengl/kis_texture_tile.cpp --- a/libs/ui/opengl/kis_texture_tile.cpp +++ b/libs/ui/opengl/kis_texture_tile.cpp @@ -19,6 +19,7 @@ #define GL_GLEXT_PROTOTYPES #include "kis_texture_tile.h" #include "kis_texture_tile_update_info.h" +#include "kis_opengl_image_textures.h" #include #if !defined(QT_OPENGL_ES) @@ -62,9 +63,10 @@ } -KisTextureTile::KisTextureTile(const QRect &imageRect, const KisGLTexturesInfo *texturesInfo, +KisTextureTile::KisTextureTile(const QPoint &index, const QRect &imageRect, const KisGLTexturesInfo *texturesInfo, const QByteArray &fillData, KisOpenGL::FilterMode filter, - bool useBuffer, int numMipmapLevels, QOpenGLFunctions *fcn) + bool useBuffer, int numMipmapLevels, QOpenGLFunctions *fcn, + KisOpenGLImageTextures *textures) : m_textureId(0) #ifdef USE_PIXEL_BUFFERS @@ -78,6 +80,8 @@ , m_useBuffer(useBuffer) , m_numMipmapLevels(numMipmapLevels) , f(fcn) + , m_textures(textures) + , m_index(index) { const GLvoid *fd = fillData.constData(); @@ -157,6 +161,8 @@ void KisTextureTile::update(const KisTextureTileUpdateInfo &updateInfo) { + m_textures->updateTextureTileVersionCR(m_index.x(), m_index.y()); + f->initializeOpenGLFunctions(); f->glBindTexture(GL_TEXTURE_2D, m_textureId);