File indexing completed on 2024-12-22 04:12:47

0001 /*
0002  *  SPDX-FileCopyrightText: 2010 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #define GL_GLEXT_PROTOTYPES
0008 #include "kis_texture_tile.h"
0009 #include "kis_texture_tile_update_info.h"
0010 #include "KisOpenGLBufferCircularStorage.h"
0011 
0012 #include <kis_debug.h>
0013 #if !defined(QT_OPENGL_ES)
0014 #include <QOpenGLBuffer>
0015 #endif
0016 
0017 #ifndef GL_BGRA
0018 #define GL_BGRA 0x814F
0019 #endif
0020 
0021 #ifndef GL_RGBA16_EXT
0022 #define GL_RGBA16_EXT 0x805B
0023 #endif
0024 
0025 
0026 void KisTextureTile::setTextureParameters()
0027 {
0028 
0029     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
0030     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
0031     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0);
0032     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, m_numMipmapLevels);
0033     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
0034     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, m_numMipmapLevels);
0035 
0036     if ((m_texturesInfo->internalFormat == GL_RGBA8 && m_texturesInfo->format == GL_RGBA)
0037 #ifndef QT_OPENGL_ES_2
0038         || (m_texturesInfo->internalFormat == GL_RGBA16 && m_texturesInfo->format == GL_RGBA)
0039 #endif
0040         || (m_texturesInfo->internalFormat == GL_RGBA16_EXT && m_texturesInfo->format == GL_RGBA)
0041     ) {
0042         // If image format is RGBA8, swap the red and blue channels for the proper color
0043         // This is for OpenGL ES support and only used if lacking GL_EXT_texture_format_BGRA8888
0044         f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
0045         f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
0046     }
0047 
0048     f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
0049 }
0050 
0051 void KisTextureTile::restoreTextureParameters()
0052 {
0053     // QPainter::drawText relies on this.
0054     // Ref: https://bugreports.qt.io/browse/QTBUG-65496
0055     f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
0056 }
0057 
0058 inline QRectF relativeRect(const QRect &br /* baseRect */,
0059                            const QRect &cr /* childRect */,
0060                            const KisGLTexturesInfo *texturesInfo)
0061 {
0062     const qreal x = qreal(cr.x() - br.x()) / texturesInfo->width;
0063     const qreal y = qreal(cr.y() - br.y()) / texturesInfo->height;
0064     const qreal w = qreal(cr.width()) / texturesInfo->width;
0065     const qreal h = qreal(cr.height()) / texturesInfo->height;
0066 
0067     return QRectF(x, y, w, h);
0068 }
0069 
0070 #include "kis_debug.h"
0071 
0072 KisTextureTile::KisTextureTile(const QRect &imageRect, const KisGLTexturesInfo *texturesInfo,
0073                                const QByteArray &fillData, KisOpenGL::FilterMode filter,
0074                                KisOpenGLBufferCircularStorage *bufferStorage, int numMipmapLevels, QOpenGLFunctions *fcn)
0075 
0076     : m_textureId(0)
0077     , m_tileRectInImagePixels(imageRect)
0078     , m_filter(filter)
0079     , m_texturesInfo(texturesInfo)
0080     , m_needsMipmapRegeneration(false)
0081     , m_preparedLodPlane(0)
0082     , m_numMipmapLevels(numMipmapLevels)
0083     , f(fcn)
0084     , m_bufferStorage(bufferStorage)
0085 {
0086     const GLvoid *fd = fillData.constData();
0087 
0088     m_textureRectInImagePixels =
0089             kisGrowRect(m_tileRectInImagePixels, texturesInfo->border);
0090 
0091     m_tileRectInTexturePixels = relativeRect(m_textureRectInImagePixels,
0092                                              m_tileRectInImagePixels,
0093                                              m_texturesInfo);
0094 
0095     f->glGenTextures(1, &m_textureId);
0096     f->glBindTexture(GL_TEXTURE_2D, m_textureId);
0097 
0098     setTextureParameters();
0099 
0100     KisOpenGLBufferCircularStorage::BufferBinder binder(
0101         m_bufferStorage, &fd, fillData.size());
0102 
0103     f->glTexImage2D(GL_TEXTURE_2D, 0,
0104                  m_texturesInfo->internalFormat,
0105                  m_texturesInfo->width,
0106                  m_texturesInfo->height, 0,
0107                  m_texturesInfo->format,
0108                  m_texturesInfo->type, fd);
0109 
0110     restoreTextureParameters();
0111 
0112     setNeedsMipmapRegeneration();
0113 }
0114 
0115 KisTextureTile::~KisTextureTile()
0116 {
0117     f->glDeleteTextures(1, &m_textureId);
0118 }
0119 
0120 int KisTextureTile::bindToActiveTexture(bool blockMipmapRegeneration)
0121 {
0122     f->glBindTexture(GL_TEXTURE_2D, m_textureId);
0123 
0124     if (m_needsMipmapRegeneration && !blockMipmapRegeneration) {
0125         f->glGenerateMipmap(GL_TEXTURE_2D);
0126         setPreparedLodPlane(0);
0127     }
0128 
0129     return m_preparedLodPlane;
0130 }
0131 
0132 void KisTextureTile::setNeedsMipmapRegeneration()
0133 {
0134     if (m_filter == KisOpenGL::TrilinearFilterMode ||
0135         m_filter == KisOpenGL::HighQualityFiltering) {
0136 
0137         m_needsMipmapRegeneration = true;
0138     }
0139 }
0140 
0141 void KisTextureTile::setPreparedLodPlane(int lod)
0142 {
0143     m_preparedLodPlane = lod;
0144     m_needsMipmapRegeneration = false;
0145 }
0146 
0147 void KisTextureTile::update(const KisTextureTileUpdateInfo &updateInfo, bool blockMipmapRegeneration)
0148 {
0149     f->initializeOpenGLFunctions();
0150     f->glBindTexture(GL_TEXTURE_2D, m_textureId);
0151 
0152     setTextureParameters();
0153 
0154     const int patchLevelOfDetail = updateInfo.patchLevelOfDetail();
0155     const QSize patchSize = updateInfo.realPatchSize();
0156     const QPoint patchOffset = updateInfo.realPatchOffset();
0157 
0158     const GLvoid *fd = updateInfo.data();
0159 
0160     /**
0161      * In some special case, when the Lod0 stroke is cancelled the
0162      * following situation is possible:
0163      *
0164      * 1)  The stroke  is  cancelled,  Lod0 update  is  issued by  the
0165      *     image. LodN level of the openGL times is still dirty.
0166      *
0167      * 2) [here, ideally, the canvas should be re-rendered, so that
0168      *     the mipmap would be regenerated in bindToActiveTexture()
0169      *     call, by in some cases (if you cancel and paint to quickly),
0170      *     that doesn't have time to happen]
0171      *
0172      * 3) The new LodN stroke issues a *partial* update of a LodN
0173      *    plane of the tile. But the plane is still *dirty*! We update
0174      *    a part of it, but we cannot regenerate the mipmap anymore,
0175      *    because the Lod0 level is not known yet!
0176      *
0177      * To avoid this issue, we should regenerate the dirty mipmap
0178      * *before* doing anything with the low-resolution plane.
0179      */
0180     if (!blockMipmapRegeneration &&
0181         patchLevelOfDetail > 0 &&
0182         m_needsMipmapRegeneration &&
0183         !updateInfo.isEntireTileUpdated()) {
0184 
0185         f->glGenerateMipmap(GL_TEXTURE_2D);
0186         m_needsMipmapRegeneration = false;
0187     }
0188 
0189 
0190     if (updateInfo.isEntireTileUpdated()) {
0191         KisOpenGLBufferCircularStorage::BufferBinder b(
0192             m_bufferStorage, &fd, updateInfo.patchPixelsLength());
0193 
0194         f->glTexImage2D(GL_TEXTURE_2D, patchLevelOfDetail,
0195                      m_texturesInfo->internalFormat,
0196                      patchSize.width(),
0197                      patchSize.height(), 0,
0198                      m_texturesInfo->format,
0199                      m_texturesInfo->type,
0200                      fd);
0201     }
0202     else {
0203         const int size = patchSize.width() * patchSize.height() * updateInfo.pixelSize();
0204         KisOpenGLBufferCircularStorage::BufferBinder b(
0205             m_bufferStorage, &fd, size);
0206 
0207         f->glTexSubImage2D(GL_TEXTURE_2D, patchLevelOfDetail,
0208                         patchOffset.x(), patchOffset.y(),
0209                         patchSize.width(), patchSize.height(),
0210                         m_texturesInfo->format,
0211                         m_texturesInfo->type,
0212                         fd);
0213 
0214     }
0215 
0216     /**
0217      * On the boundaries of KisImage, there is a border-effect as well.
0218      * So we just repeat the bounding pixels of the image to make
0219      * bilinear interpolator happy.
0220      */
0221 
0222     /**
0223      * WARN: The width of the stripes will be equal to the broader
0224      *       width of the tiles.
0225      */
0226 
0227     const int pixelSize = updateInfo.pixelSize();
0228     const QSize tileSize = updateInfo.realTileSize();
0229 
0230     if(updateInfo.isTopmost()) {
0231         int start = 0;
0232         int end = patchOffset.y() - 1;
0233 
0234         const GLvoid *fd = updateInfo.data();
0235         const int size = patchSize.width() * pixelSize;
0236         KisOpenGLBufferCircularStorage::BufferBinder g(
0237             m_bufferStorage, &fd, size);
0238 
0239         for (int i = start; i <= end; i++) {
0240             f->glTexSubImage2D(GL_TEXTURE_2D, patchLevelOfDetail,
0241                                patchOffset.x(), i,
0242                                patchSize.width(), 1,
0243                                m_texturesInfo->format,
0244                                m_texturesInfo->type,
0245                                fd);
0246         }
0247     }
0248 
0249     if (updateInfo.isBottommost()) {
0250         int shift = patchSize.width() * (patchSize.height() - 1) *
0251                 pixelSize;
0252 
0253         int start = patchOffset.y() + patchSize.height();
0254         int end = tileSize.height() - 1;
0255 
0256         const GLvoid *fd = updateInfo.data() + shift;
0257         const int size = patchSize.width() * pixelSize;
0258         KisOpenGLBufferCircularStorage::BufferBinder g(
0259             m_bufferStorage, &fd, size);
0260 
0261         for (int i = start; i < end; i++) {
0262             f->glTexSubImage2D(GL_TEXTURE_2D, patchLevelOfDetail,
0263                             patchOffset.x(), i,
0264                             patchSize.width(), 1,
0265                             m_texturesInfo->format,
0266                             m_texturesInfo->type,
0267                             fd);
0268         }
0269     }
0270 
0271     if (updateInfo.isLeftmost()) {
0272 
0273         QByteArray columnBuffer(patchSize.height() * pixelSize, 0);
0274 
0275         quint8 *srcPtr = updateInfo.data();
0276         quint8 *dstPtr = (quint8*) columnBuffer.data();
0277         for(int i = 0; i < patchSize.height(); i++) {
0278             memcpy(dstPtr, srcPtr, pixelSize);
0279 
0280             srcPtr += patchSize.width() * pixelSize;
0281             dstPtr += pixelSize;
0282         }
0283 
0284         int start = 0;
0285         int end = patchOffset.x() - 1;
0286 
0287         const GLvoid *fd = columnBuffer.constData();
0288         const int size = columnBuffer.size();
0289         KisOpenGLBufferCircularStorage::BufferBinder g(
0290             m_bufferStorage, &fd, size);
0291 
0292         for (int i = start; i <= end; i++) {
0293             f->glTexSubImage2D(GL_TEXTURE_2D, patchLevelOfDetail,
0294                             i, patchOffset.y(),
0295                             1, patchSize.height(),
0296                             m_texturesInfo->format,
0297                             m_texturesInfo->type,
0298                             fd);
0299         }
0300     }
0301 
0302     if (updateInfo.isRightmost()) {
0303 
0304         QByteArray columnBuffer(patchSize.height() * pixelSize, 0);
0305 
0306         quint8 *srcPtr = updateInfo.data() + (patchSize.width() - 1) * pixelSize;
0307         quint8 *dstPtr = (quint8*) columnBuffer.data();
0308         for(int i = 0; i < patchSize.height(); i++) {
0309             memcpy(dstPtr, srcPtr, pixelSize);
0310 
0311             srcPtr += patchSize.width() * pixelSize;
0312             dstPtr += pixelSize;
0313         }
0314 
0315         int start = patchOffset.x() + patchSize.width();
0316         int end = tileSize.width() - 1;
0317 
0318         const GLvoid *fd = columnBuffer.constData();
0319         const int size = columnBuffer.size();
0320         KisOpenGLBufferCircularStorage::BufferBinder g(
0321             m_bufferStorage, &fd, size);
0322 
0323         for (int i = start; i <= end; i++) {
0324             f->glTexSubImage2D(GL_TEXTURE_2D, patchLevelOfDetail,
0325                             i, patchOffset.y(),
0326                             1, patchSize.height(),
0327                             m_texturesInfo->format,
0328                             m_texturesInfo->type,
0329                             fd);
0330         }
0331     }
0332 
0333     //// Uncomment this warning if you see any weird flickering when
0334     //// Instant Preview updates
0335     // if (!updateInfo.isEntireTileUpdated() &&
0336     //     !(!patchLevelOfDetail || !m_preparedLodPlane || patchLevelOfDetail == m_preparedLodPlane)) {
0337     //     qDebug() << "WARNING: LodN switch is requested for the partial tile update!. Flickering is possible..." << ppVar(patchSize);
0338     //     qDebug() << "    " << ppVar(m_preparedLodPlane);
0339     //     qDebug() << "    " << ppVar(patchLevelOfDetail);
0340     // }
0341 
0342     restoreTextureParameters();
0343 
0344     if (!patchLevelOfDetail) {
0345         setNeedsMipmapRegeneration();
0346     } else {
0347         setPreparedLodPlane(patchLevelOfDetail);
0348     }
0349 }
0350 
0351 QRectF KisTextureTile::imageRectInTexturePixels(const QRect &imageRect) const
0352 {
0353     return relativeRect(m_textureRectInImagePixels,
0354                         imageRect,
0355                         m_texturesInfo);
0356 
0357 }