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

0001 /*
0002  *  SPDX-FileCopyrightText: 2005-2007 Adrian Page <adrian@pagenet.plus.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "opengl/kis_opengl_image_textures.h"
0008 
0009 #ifdef QT_OPENGL_ES_2
0010 #include <qopengl.h>
0011 #endif
0012 
0013 #ifndef QT_OPENGL_ES_2
0014 #include <QOpenGLFunctions>
0015 #endif
0016 #include <QOpenGLContext>
0017 
0018 #include <QMessageBox>
0019 #include <QApplication>
0020 #include <QDesktopWidget>
0021 
0022 #include <KoColorSpaceRegistry.h>
0023 #include <KoColorProfile.h>
0024 #include <KoColorModelStandardIds.h>
0025 
0026 #include "kis_image.h"
0027 #include "kis_config.h"
0028 #include "KisPart.h"
0029 #include "KisOpenGLModeProber.h"
0030 #include "kis_fixed_paint_device.h"
0031 #include "KisOpenGLSync.h"
0032 #include <QVector3D>
0033 #include "kis_painting_tweaks.h"
0034 #include "KisOpenGLBufferCreationGuard.h"
0035 
0036 #ifdef HAVE_OPENEXR
0037 #include <half.h>
0038 #endif
0039 
0040 #ifndef GL_CLAMP_TO_EDGE
0041 #define GL_CLAMP_TO_EDGE 0x812F
0042 #endif
0043 
0044 #ifndef GL_BGRA
0045 #define GL_BGRA 0x80E1
0046 #endif
0047 
0048 // GL_EXT_texture_format_BGRA8888
0049 #ifndef GL_BGRA_EXT
0050 #define GL_BGRA_EXT 0x80E1
0051 #endif
0052 #ifndef GL_BGRA8_EXT
0053 #define GL_BGRA8_EXT 0x93A1
0054 #endif
0055 
0056 #ifndef GL_RGBA16_EXT
0057 #define GL_RGBA16_EXT 0x805B
0058 #endif
0059 
0060 //#define DEBUG_BUFFER_REALLOCATION
0061 
0062 
0063 KisOpenGLImageTextures::ImageTexturesMap KisOpenGLImageTextures::imageTexturesMap;
0064 
0065 KisOpenGLImageTextures::KisOpenGLImageTextures()
0066     : m_image(0)
0067     , m_monitorProfile(0)
0068     , m_internalColorManagementActive(true)
0069     , m_bufferStorage(QOpenGLBuffer::PixelUnpackBuffer)
0070     , m_glFuncs(0)
0071     , m_useOcio(false)
0072     , m_initialized(false)
0073 {
0074     KisConfig cfg(true);
0075     m_renderingIntent = (KoColorConversionTransformation::Intent)cfg.monitorRenderIntent();
0076 
0077     m_conversionFlags = KoColorConversionTransformation::HighQuality;
0078     if (cfg.useBlackPointCompensation()) m_conversionFlags |= KoColorConversionTransformation::BlackpointCompensation;
0079     if (!cfg.allowLCMSOptimization()) m_conversionFlags |= KoColorConversionTransformation::NoOptimization;
0080     m_useOcio = cfg.useOcio();
0081 }
0082 
0083 KisOpenGLImageTextures::KisOpenGLImageTextures(KisImageWSP image,
0084                                                const KoColorProfile *monitorProfile,
0085                                                KoColorConversionTransformation::Intent renderingIntent,
0086                                                KoColorConversionTransformation::ConversionFlags conversionFlags)
0087     : m_image(image)
0088     , m_monitorProfile(monitorProfile)
0089     , m_renderingIntent(renderingIntent)
0090     , m_conversionFlags(conversionFlags)
0091     , m_internalColorManagementActive(true)
0092     , m_bufferStorage(QOpenGLBuffer::PixelUnpackBuffer)
0093     , m_glFuncs(0)
0094     , m_useOcio(false)
0095     , m_initialized(false)
0096 {
0097     Q_ASSERT(renderingIntent < 4);
0098 }
0099 
0100 void KisOpenGLImageTextures::initGL(QOpenGLFunctions *f)
0101 {
0102     if (f) {
0103         m_glFuncs = f;
0104     } else {
0105         errUI << "Tried to create OpenGLImageTextures with uninitialized QOpenGLFunctions";
0106     }
0107 
0108     getTextureSize(&m_texturesInfo);
0109 
0110     // we use local static object for creating pools shared among
0111     // different images
0112     static KisTextureTileInfoPoolRegistry s_poolRegistry;
0113     m_updateInfoBuilder.setTextureInfoPool(s_poolRegistry.getPool(m_texturesInfo.width, m_texturesInfo.height));
0114 
0115     m_checkerTexture = GLuint();
0116     m_glFuncs->glGenTextures(1, &(*m_checkerTexture));
0117     recreateImageTextureTiles();
0118 
0119     KisOpenGLUpdateInfoSP info = updateCache(m_image->bounds(), m_image);
0120     recalculateCache(info, false);
0121 }
0122 
0123 KisOpenGLImageTextures::~KisOpenGLImageTextures()
0124 {
0125     ImageTexturesMap::iterator it = imageTexturesMap.find(m_image);
0126     if (it != imageTexturesMap.end()) {
0127         KisOpenGLImageTextures *textures = it.value();
0128         if (textures == this) {
0129             dbgUI << "Removing shared image context from map";
0130             imageTexturesMap.erase(it);
0131         }
0132     }
0133 
0134     destroyImageTextureTiles();
0135     if (m_checkerTexture) {
0136         m_glFuncs->glDeleteTextures(1, &(*m_checkerTexture));
0137     }
0138 }
0139 
0140 KisImageSP KisOpenGLImageTextures::image() const
0141 {
0142     return m_image;
0143 }
0144 
0145 bool KisOpenGLImageTextures::imageCanShareTextures()
0146 {
0147     KisConfig cfg(true);
0148     if (cfg.useOcio()) return false;
0149     if (KisPart::instance()->mainwindowCount() == 1) return true;
0150     if (QGuiApplication::screens().count() == 1) return true;
0151     for (int i = 1; i < QGuiApplication::screens().count(); i++) {
0152         if (cfg.displayProfile(i) != cfg.displayProfile(i - 1)) {
0153             return false;
0154         }
0155     }
0156     return true;
0157 }
0158 
0159 void KisOpenGLImageTextures::initBufferStorage(bool useBuffer)
0160 {
0161     if (useBuffer) {
0162         const int numTextureBuffers = 16;
0163 
0164         const KoColorSpace *tilesDestinationColorSpace =
0165             m_updateInfoBuilder.destinationColorSpace();
0166         const int pixelSize = tilesDestinationColorSpace->pixelSize();
0167         const int tileSize = m_texturesInfo.width * m_texturesInfo.height * pixelSize;
0168 
0169         m_bufferStorage.allocate(numTextureBuffers, tileSize);
0170     } else {
0171         m_bufferStorage.reset();
0172     }
0173 }
0174 
0175 KisOpenGLImageTexturesSP KisOpenGLImageTextures::getImageTextures(KisImageWSP image,
0176                                                                   const KoColorProfile *monitorProfile,
0177                                                                   KoColorConversionTransformation::Intent renderingIntent,
0178                                                                   KoColorConversionTransformation::ConversionFlags conversionFlags)
0179 {
0180     // Disabled until we figure out why we're deleting the shared textures on closing the second view on a single image
0181     if (false && imageCanShareTextures()) {
0182         ImageTexturesMap::iterator it = imageTexturesMap.find(image);
0183 
0184         if (it != imageTexturesMap.end()) {
0185             KisOpenGLImageTexturesSP textures = it.value();
0186             textures->setMonitorProfile(monitorProfile, renderingIntent, conversionFlags);
0187 
0188             return textures;
0189         } else {
0190             KisOpenGLImageTextures *imageTextures = new KisOpenGLImageTextures(image, monitorProfile, renderingIntent, conversionFlags);
0191             imageTexturesMap[image] = imageTextures;
0192             dbgUI << "Added shareable textures to map";
0193 
0194             return imageTextures;
0195         }
0196     } else {
0197         return new KisOpenGLImageTextures(image, monitorProfile, renderingIntent, conversionFlags);
0198     }
0199 }
0200 
0201 void KisOpenGLImageTextures::recreateImageTextureTiles()
0202 {
0203 
0204     destroyImageTextureTiles();
0205     updateTextureFormat();
0206 
0207     const KoColorSpace *tilesDestinationColorSpace =
0208         m_updateInfoBuilder.destinationColorSpace();
0209 
0210     if (!tilesDestinationColorSpace) {
0211         qDebug() << "No destination colorspace!!!!";
0212         return;
0213     }
0214 
0215 
0216     m_storedImageBounds = m_image->bounds();
0217     const int lastCol = xToCol(m_image->width());
0218     const int lastRow = yToRow(m_image->height());
0219 
0220     m_numCols = lastCol + 1;
0221 
0222     // Default color is transparent black
0223     const int pixelSize = tilesDestinationColorSpace->pixelSize();
0224     QByteArray emptyTileData((m_texturesInfo.width) * (m_texturesInfo.height) * pixelSize, 0);
0225 
0226     KisConfig config(true);
0227     KisOpenGL::FilterMode mode = (KisOpenGL::FilterMode)config.openGLFilteringMode();
0228 
0229     initBufferStorage(KisOpenGL::shouldUseTextureBuffers(config.useOpenGLTextureBuffer()));
0230 
0231     QOpenGLContext *ctx = QOpenGLContext::currentContext();
0232     if (ctx) {
0233         QOpenGLFunctions *f = ctx->functions();
0234 
0235         m_initialized = true;
0236         dbgUI  << "OpenGL: creating texture tiles of size" << m_texturesInfo.height << "x" << m_texturesInfo.width;
0237 
0238         QVector<QRectF> tileImageRect;
0239         QVector<QRectF> tileTextureRect;
0240 
0241         m_textureTiles.reserve((lastRow+1)*m_numCols);
0242 
0243         tileImageRect.reserve(m_textureTiles.size());
0244         tileTextureRect.reserve(m_textureTiles.size());
0245 
0246         for (int row = 0; row <= lastRow; row++) {
0247             for (int col = 0; col <= lastCol; col++) {
0248                 QRect tileRect = m_updateInfoBuilder.calculateEffectiveTileRect(col, row, m_image->bounds());
0249 
0250                 KisTextureTile *tile = new KisTextureTile(tileRect,
0251                                                           &m_texturesInfo,
0252                                                           emptyTileData,
0253                                                           mode,
0254                                                           m_bufferStorage.isValid() ? &m_bufferStorage : 0,
0255                                                           config.numMipmapLevels(),
0256                                                           f);
0257                 m_textureTiles.append(tile);
0258                 tileImageRect.append(tile->tileRectInImagePixels());
0259                 tileTextureRect.append(tile->tileRectInTexturePixels());
0260             }
0261         }
0262 
0263 
0264 
0265         {
0266             KisOpenGLBufferCreationGuard bufferGuard(&m_tileVertexBuffer,
0267                                                      tileImageRect.size() * 6 * 3 * sizeof(float),
0268                                                      QOpenGLBuffer::StaticDraw);
0269 
0270             QVector3D* mappedPtr = reinterpret_cast<QVector3D*>(bufferGuard.data());
0271 
0272             Q_FOREACH (const QRectF &rc, tileImageRect) {
0273                 KisPaintingTweaks::rectToVertices(mappedPtr, rc);
0274                 mappedPtr += 6;
0275             }
0276         }
0277 
0278         {
0279             KisOpenGLBufferCreationGuard bufferGuard(&m_tileTexCoordBuffer,
0280                                                      tileImageRect.size() * 6 * 2 * sizeof(float),
0281                                                      QOpenGLBuffer::StaticDraw);
0282 
0283             QVector2D* mappedPtr = reinterpret_cast<QVector2D*>(bufferGuard.data());
0284 
0285             Q_FOREACH (const QRectF &rc, tileTextureRect) {
0286                 KisPaintingTweaks::rectToTexCoords(mappedPtr, rc);
0287                 mappedPtr += 6;
0288             }
0289         }
0290     }
0291     else {
0292         dbgUI << "Tried to init texture tiles without a current OpenGL Context.";
0293     }
0294 }
0295 
0296 void KisOpenGLImageTextures::destroyImageTextureTiles()
0297 {
0298     if (m_textureTiles.isEmpty()) return;
0299 
0300     Q_FOREACH (KisTextureTile *tile, m_textureTiles) {
0301         delete tile;
0302     }
0303     m_textureTiles.clear();
0304     m_tileVertexBuffer.destroy();
0305     m_tileTexCoordBuffer.destroy();
0306     m_storedImageBounds = QRect();
0307 }
0308 
0309 KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCache(const QRect& rect, KisImageSP srcImage)
0310 {
0311     return updateCacheImpl(rect, srcImage, true);
0312 }
0313 
0314 KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCacheNoConversion(const QRect& rect)
0315 {
0316     return updateCacheImpl(rect, m_image, false);
0317 }
0318 
0319 // TODO: add sanity checks about the conformance of the passed srcImage!
0320 KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCacheImpl(const QRect& rect, KisImageSP srcImage, bool convertColorSpace)
0321 {
0322     if (!m_initialized) return new KisOpenGLUpdateInfo();
0323     return m_updateInfoBuilder.buildUpdateInfo(rect, srcImage, convertColorSpace);
0324 }
0325 
0326 void KisOpenGLImageTextures::recalculateCache(KisUpdateInfoSP info, bool blockMipmapRegeneration)
0327 {
0328     if (!m_initialized) {
0329         dbgUI << "OpenGL: Tried to edit image texture cache before it was initialized.";
0330         return;
0331     }
0332 
0333     KisOpenGLUpdateInfoSP glInfo = dynamic_cast<KisOpenGLUpdateInfo*>(info.data());
0334     if(!glInfo) return;
0335 
0336     QScopedPointer<KisOpenGLSync> sync;
0337     int numProcessedTiles = 0;
0338 
0339     KisTextureTileUpdateInfoSP tileInfo;
0340     Q_FOREACH (tileInfo, glInfo->tileList) {
0341         KisTextureTile *tile = getTextureTileCR(tileInfo->tileCol(), tileInfo->tileRow());
0342         KIS_ASSERT_RECOVER_RETURN(tile);
0343 
0344         if (m_bufferStorage.isValid() && numProcessedTiles > m_bufferStorage.size() &&
0345             sync && !sync->isSignaled()) {
0346 
0347 #ifdef DEBUG_BUFFER_REALLOCATION
0348             qDebug() << "Still unsignalled after processed" << numProcessedTiles << "tiles";
0349 #endif
0350 
0351             m_bufferStorage.allocateMoreBuffers();
0352 
0353 #ifdef DEBUG_BUFFER_REALLOCATION
0354             qDebug() << "    increased number of buffers to" << m_bufferStorage.size();
0355 #endif
0356         }
0357 
0358 
0359         tile->update(*tileInfo, blockMipmapRegeneration);
0360 
0361         if (m_bufferStorage.isValid()) {
0362             if (!sync) {
0363                 sync.reset(new KisOpenGLSync());
0364                 numProcessedTiles = 0;
0365             } else if (sync && sync->isSignaled()) {
0366                 sync.reset();
0367             }
0368             numProcessedTiles++;
0369         }
0370     }
0371 }
0372 
0373 void KisOpenGLImageTextures::generateCheckerTexture(const QImage &checkImage)
0374 {
0375     if (!m_initialized) {
0376         return;
0377     }
0378 
0379     QOpenGLContext *ctx = QOpenGLContext::currentContext();
0380     if (ctx) {
0381         QOpenGLFunctions *f = ctx->functions();
0382         dbgUI << "Attaching checker texture" << checkerTexture();
0383         f->glBindTexture(GL_TEXTURE_2D, checkerTexture());
0384 
0385         f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
0386         f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
0387         f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
0388         f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
0389 
0390         f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
0391 
0392         QImage img = checkImage;
0393         if (checkImage.width() != BACKGROUND_TEXTURE_SIZE || checkImage.height() != BACKGROUND_TEXTURE_SIZE) {
0394             img = checkImage.scaled(BACKGROUND_TEXTURE_SIZE, BACKGROUND_TEXTURE_SIZE);
0395         }
0396 
0397         // convert from sRGB to display format, potentially HDR
0398         const KoColorSpace *temporaryColorSpace = KoColorSpaceRegistry::instance()->rgb8();
0399         const KoColorSpace *finalColorSpace =
0400                KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(),
0401                                                             m_updateInfoBuilder.destinationColorSpace()->colorDepthId().id(),
0402                                                             m_monitorProfile);
0403 
0404         KisFixedPaintDevice checkers(temporaryColorSpace);
0405         checkers.convertFromQImage(img, temporaryColorSpace->profile()->name());
0406         checkers.convertTo(finalColorSpace);
0407 
0408         KIS_ASSERT(checkers.bounds().width() == BACKGROUND_TEXTURE_SIZE);
0409         KIS_ASSERT(checkers.bounds().height() == BACKGROUND_TEXTURE_SIZE);
0410 
0411         GLint format = m_texturesInfo.format;
0412         GLint internalFormat = m_texturesInfo.internalFormat;
0413         GLint type = m_texturesInfo.type;
0414 
0415         f->glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, BACKGROUND_TEXTURE_SIZE, BACKGROUND_TEXTURE_SIZE,
0416                         0, format, type, checkers.data());
0417 
0418         // QPainter::drawText relies on this.
0419         // Ref: https://bugreports.qt.io/browse/QTBUG-65496
0420         f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
0421     }
0422     else {
0423         dbgUI << "OpenGL: Tried to generate checker texture before OpenGL was initialized.";
0424     }
0425 
0426 }
0427 
0428 GLuint KisOpenGLImageTextures::checkerTexture()
0429 {
0430     if (m_glFuncs) {
0431         if (!m_checkerTexture) {
0432             m_checkerTexture = GLuint();
0433             m_glFuncs->glGenTextures(1, &(*m_checkerTexture));
0434         }
0435         return *m_checkerTexture;
0436     }
0437     else {
0438         dbgUI << "Tried to access checker texture before OpenGL was initialized";
0439         return 0;
0440     }
0441 }
0442 
0443 void KisOpenGLImageTextures::updateConfig(bool useBuffer, int NumMipmapLevels)
0444 {
0445     if(m_textureTiles.isEmpty()) return;
0446 
0447     const bool effectiveUseBuffer = KisOpenGL::shouldUseTextureBuffers(useBuffer);
0448     initBufferStorage(effectiveUseBuffer);
0449 
0450     Q_FOREACH (KisTextureTile *tile, m_textureTiles) {
0451         tile->setBufferStorage(effectiveUseBuffer ? &m_bufferStorage : 0);
0452         tile->setNumMipmapLevels(NumMipmapLevels);
0453     }
0454 }
0455 
0456 void KisOpenGLImageTextures::testingForceInitialized()
0457 {
0458     m_initialized = true;
0459     m_updateInfoBuilder.setTextureInfoPool(toQShared(new KisTextureTileInfoPool(256, 256)));
0460 
0461     ConversionOptions options;
0462     options.m_destinationColorSpace = KoColorSpaceRegistry::instance()->rgb8();
0463     options.m_conversionFlags = KoColorConversionTransformation::internalConversionFlags();
0464     options.m_renderingIntent = KoColorConversionTransformation::internalRenderingIntent();
0465     options.m_needsConversion = false;
0466     m_updateInfoBuilder.setConversionOptions(options);
0467 
0468     m_updateInfoBuilder.setTextureBorder(4);
0469     m_updateInfoBuilder.setEffectiveTextureSize(QSize(248, 248));
0470 }
0471 
0472 void KisOpenGLImageTextures::slotImageSizeChanged(qint32 /*w*/, qint32 /*h*/)
0473 {
0474     recreateImageTextureTiles();
0475 }
0476 
0477 KisOpenGLUpdateInfoBuilder &KisOpenGLImageTextures::updateInfoBuilder()
0478 {
0479     return m_updateInfoBuilder;
0480 }
0481 
0482 const KoColorProfile *KisOpenGLImageTextures::monitorProfile()
0483 {
0484     return m_monitorProfile;
0485 }
0486 
0487 void KisOpenGLImageTextures::setMonitorProfile(const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
0488 {
0489     //dbgUI << "Setting monitor profile to" << monitorProfile->name() << renderingIntent << conversionFlags;
0490     m_monitorProfile = monitorProfile;
0491     m_renderingIntent = renderingIntent;
0492     m_conversionFlags = conversionFlags;
0493 
0494     recreateImageTextureTiles();
0495 }
0496 
0497 bool KisOpenGLImageTextures::setImageColorSpace(const KoColorSpace *cs)
0498 {
0499     Q_UNUSED(cs);
0500     // TODO: implement lazy update: do not re-upload textures when not needed
0501 
0502     recreateImageTextureTiles();
0503     return true;
0504 }
0505 
0506 void KisOpenGLImageTextures::setChannelFlags(const QBitArray &channelFlags)
0507 {
0508     QBitArray targetChannelFlags = channelFlags;
0509     int selectedChannels = 0;
0510     const KoColorSpace *projectionCs = m_image->projection()->colorSpace();
0511     const QList<KoChannelInfo*> channelInfo = projectionCs->channels();
0512 
0513     if (targetChannelFlags.size() != channelInfo.size()) {
0514         targetChannelFlags = QBitArray();
0515     }
0516 
0517     int selectedChannelIndex = -1;
0518 
0519     for (int i = 0; i < targetChannelFlags.size(); ++i) {
0520         if (targetChannelFlags.testBit(i) && channelInfo[i]->channelType() == KoChannelInfo::COLOR) {
0521             selectedChannels++;
0522             selectedChannelIndex = i;
0523         }
0524     }
0525     const bool allChannelsSelected = (selectedChannels == targetChannelFlags.size());
0526     const bool onlyOneChannelSelected = (selectedChannels == 1);
0527 
0528     // OCIO has its own channel swizzling
0529     if (allChannelsSelected || m_useOcio) {
0530         m_updateInfoBuilder.setChannelFlags(QBitArray(), false, -1);
0531     } else {
0532         m_updateInfoBuilder.setChannelFlags(targetChannelFlags, onlyOneChannelSelected, selectedChannelIndex);
0533     }
0534 }
0535 
0536 void KisOpenGLImageTextures::setProofingConfig(KisProofingConfigurationSP proofingConfig)
0537 {
0538     m_updateInfoBuilder.setProofingConfig(proofingConfig);
0539 }
0540 
0541 void KisOpenGLImageTextures::getTextureSize(KisGLTexturesInfo *texturesInfo)
0542 {
0543     KisConfig cfg(true);
0544 
0545     const GLint preferredTextureSize = cfg.openGLTextureSize();
0546 
0547     GLint maxTextureSize;
0548     if (m_glFuncs) {
0549         m_glFuncs->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
0550     }
0551     else {
0552         dbgUI << "OpenGL: Tried to read texture size before OpenGL was initialized.";
0553         maxTextureSize = GL_MAX_TEXTURE_SIZE;
0554     }
0555 
0556     texturesInfo->width = qMin(preferredTextureSize, maxTextureSize);
0557     texturesInfo->height = qMin(preferredTextureSize, maxTextureSize);
0558 
0559     texturesInfo->border = cfg.textureOverlapBorder();
0560 
0561     texturesInfo->effectiveWidth = texturesInfo->width - 2 * texturesInfo->border;
0562     texturesInfo->effectiveHeight = texturesInfo->height - 2 * texturesInfo->border;
0563 
0564     m_updateInfoBuilder.setTextureBorder(texturesInfo->border);
0565     m_updateInfoBuilder.setEffectiveTextureSize(
0566         QSize(texturesInfo->effectiveWidth, texturesInfo->effectiveHeight));
0567 }
0568 
0569 bool KisOpenGLImageTextures::internalColorManagementActive() const
0570 {
0571     return m_internalColorManagementActive;
0572 }
0573 
0574 bool KisOpenGLImageTextures::setInternalColorManagementActive(bool value)
0575 {
0576     bool needsFinalRegeneration = m_internalColorManagementActive != value;
0577 
0578     if (needsFinalRegeneration) {
0579         m_internalColorManagementActive = value;
0580         recreateImageTextureTiles();
0581 
0582         // at this point the value of m_internalColorManagementActive might
0583         // have been forcefully reverted to 'false' in case of some problems
0584     }
0585 
0586     return needsFinalRegeneration;
0587 }
0588 
0589 namespace {
0590 void initializeRGBA16FTextures(QOpenGLContext *ctx, KisGLTexturesInfo &texturesInfo, KoID &destinationColorDepthId)
0591 {
0592     bool haveBuiltInOpenExr = false;
0593 #ifdef HAVE_OPENEXR
0594     haveBuiltInOpenExr = true;
0595 #endif
0596 
0597     if (haveBuiltInOpenExr && (KisOpenGL::hasOpenGLES() || KisOpenGL::hasOpenGL3())) {
0598 #ifndef QT_OPENGL_ES_2
0599         texturesInfo.internalFormat = GL_RGBA16F;
0600         dbgUI << "Using half (GLES or GL3)";
0601         texturesInfo.type = GL_HALF_FLOAT;
0602         destinationColorDepthId = Float16BitsColorDepthID;
0603         dbgUI << "Pixel type half (GLES or GL3)";
0604         texturesInfo.format = GL_RGBA;
0605     } else if (haveBuiltInOpenExr && ctx->hasExtension("GL_ARB_half_float_pixel")) {
0606         texturesInfo.internalFormat = GL_RGBA16F_ARB;
0607         dbgUI << "Using ARB half";
0608         texturesInfo.type = GL_HALF_FLOAT_ARB;
0609         destinationColorDepthId = Float16BitsColorDepthID;
0610         texturesInfo.format = GL_RGBA;
0611         dbgUI << "Pixel type half";
0612     } else if (haveBuiltInOpenExr && ctx->hasExtension("GL_ATI_texture_float")) {
0613         texturesInfo.internalFormat = GL_RGBA_FLOAT16_ATI;
0614         dbgUI << "Using ATI half";
0615         texturesInfo.type = GL_HALF_FLOAT;
0616         destinationColorDepthId = Float16BitsColorDepthID;
0617         dbgUI << "Using half (GLES or GL3)";
0618         texturesInfo.format = GL_RGBA;
0619     } else {
0620         texturesInfo.internalFormat = GL_RGBA32F;
0621         texturesInfo.type = GL_FLOAT;
0622         destinationColorDepthId = Float32BitsColorDepthID;
0623         dbgUI << "Pixel type float";
0624         texturesInfo.format = GL_RGBA;
0625 #else
0626         if (ctx->format().majorVersion() >= 3) {
0627             texturesInfo.internalFormat = GL_RGBA16F;
0628             dbgUI << "Using half (GLES 3 non-Angle)";
0629             texturesInfo.type = GL_HALF_FLOAT;
0630             destinationColorDepthId = Float16BitsColorDepthID;
0631             dbgUI << "Pixel type half (GLES 3 non-Angle)";
0632             texturesInfo.format = GL_RGBA;
0633         } else if (ctx->hasExtension("GL_OES_texture_half_float") && ctx->hasExtension("GL_EXT_color_buffer_half_float")
0634             && ctx->hasExtension("GL_OES_texture_half_float_linear")) {
0635             texturesInfo.internalFormat = GL_RGBA16F_EXT;
0636             dbgUI << "Using half (GLES v2)";
0637             texturesInfo.type = GL_HALF_FLOAT_OES;
0638             destinationColorDepthId = Float16BitsColorDepthID;
0639             dbgUI << "Pixel type half (GLES v2)";
0640             texturesInfo.format = GL_RGBA;
0641         } else if (ctx->hasExtension("GL_OES_texture_float") && (ctx->hasExtension("GL_EXT_texture_storage") || ctx->hasExtension("EXT_color_buffer_float"))
0642                    && ctx->hasExtension("GL_OES_texture_float_linear")) {
0643             texturesInfo.internalFormat = GL_RGBA32F_EXT;
0644             dbgUI << "Using float (GLES v2)";
0645             texturesInfo.type = GL_FLOAT;
0646             destinationColorDepthId = Float32BitsColorDepthID;
0647             dbgUI << "Pixel type float (GLES v2)";
0648             texturesInfo.format = GL_RGBA;
0649         }
0650     } else if (ctx->format().majorVersion() >= 3) {
0651         texturesInfo.internalFormat = GL_RGBA32F;
0652         dbgUI << "Using float (GLES 3 non-Angle)";
0653         texturesInfo.type = GL_FLOAT;
0654         destinationColorDepthId = Float32BitsColorDepthID;
0655         dbgUI << "Pixel type float (GLES 3 non-Angle)";
0656         texturesInfo.format = GL_RGBA;
0657     } else if (ctx->hasExtension("GL_OES_texture_float") && (ctx->hasExtension("GL_EXT_texture_storage") || ctx->hasExtension("EXT_color_buffer_float"))
0658             && ctx->hasExtension("GL_OES_texture_float_linear")) {
0659         texturesInfo.internalFormat = GL_RGBA32F_EXT;
0660         dbgUI << "Using float (GLES v2)";
0661         texturesInfo.type = GL_FLOAT;
0662         destinationColorDepthId = Float32BitsColorDepthID;
0663         dbgUI << "Pixel type float (GLES v2)";
0664         texturesInfo.format = GL_RGBA;
0665 #endif
0666     }
0667 }
0668 }
0669 
0670 void KisOpenGLImageTextures::updateTextureFormat()
0671 {
0672     QOpenGLContext *ctx = QOpenGLContext::currentContext();
0673     if (!(m_image && ctx)) return;
0674 
0675     if (!KisOpenGL::hasOpenGLES()) {
0676 #ifndef QT_OPENGL_ES_2
0677         m_texturesInfo.internalFormat = GL_RGBA8;
0678         m_texturesInfo.type = GL_UNSIGNED_BYTE;
0679         m_texturesInfo.format = GL_BGRA;
0680 #else
0681         KIS_ASSERT_X(false, "KisOpenGLImageTextures::updateTextureFormat",
0682                 "Unexpected KisOpenGL::hasOpenGLES returned false");
0683 #endif
0684     } else {
0685 #ifdef QT_OPENGL_ES_2
0686         m_texturesInfo.internalFormat = GL_RGBA8;
0687         m_texturesInfo.type = GL_UNSIGNED_BYTE;
0688         m_texturesInfo.format = GL_RGBA;
0689 #else
0690         m_texturesInfo.internalFormat = GL_BGRA8_EXT;
0691         m_texturesInfo.type = GL_UNSIGNED_BYTE;
0692         m_texturesInfo.format = GL_BGRA_EXT;
0693         if(!ctx->hasExtension(QByteArrayLiteral("GL_EXT_texture_format_BGRA8888"))) {
0694             // The red and blue channels are swapped, but it will be re-swapped
0695             // by texture swizzle mask set in KisTextureTile::setTextureParameters
0696             m_texturesInfo.internalFormat = GL_RGBA8;
0697             m_texturesInfo.type = GL_UNSIGNED_BYTE;
0698             m_texturesInfo.format = GL_RGBA;
0699         }
0700 #endif
0701     }
0702 
0703     const bool useHDRMode = KisOpenGLModeProber::instance()->useHDRMode();
0704     const KoID colorModelId = m_image->colorSpace()->colorModelId();
0705     const KoID colorDepthId = useHDRMode ? Float16BitsColorDepthID : m_image->colorSpace()->colorDepthId();
0706 
0707     KoID destinationColorModelId = RGBAColorModelID;
0708     KoID destinationColorDepthId = Integer8BitsColorDepthID;
0709 
0710     dbgUI << "Choosing texture format:";
0711 
0712     if (colorModelId == RGBAColorModelID) {
0713         if (colorDepthId == Float16BitsColorDepthID) {
0714             initializeRGBA16FTextures(ctx, m_texturesInfo, destinationColorDepthId);
0715         }
0716         else if (colorDepthId == Float32BitsColorDepthID) {
0717             if (KisOpenGL::hasOpenGLES() || KisOpenGL::hasOpenGL3()) {
0718 #ifndef QT_OPENGL_ES_2
0719                 m_texturesInfo.internalFormat = GL_RGBA32F;
0720                 dbgUI << "Using float (GLES or GL3)";
0721                 m_texturesInfo.type = GL_FLOAT;
0722                 m_texturesInfo.format = GL_RGBA;
0723                 destinationColorDepthId = Float32BitsColorDepthID;
0724             } else if (ctx->hasExtension("GL_ARB_texture_float")) {
0725                 m_texturesInfo.internalFormat = GL_RGBA32F_ARB;
0726                 dbgUI << "Using ARB float";
0727                 m_texturesInfo.type = GL_FLOAT;
0728                 m_texturesInfo.format = GL_RGBA;
0729                 destinationColorDepthId = Float32BitsColorDepthID;
0730             } else if (ctx->hasExtension("GL_ATI_texture_float")) {
0731                 m_texturesInfo.internalFormat = GL_RGBA_FLOAT32_ATI;
0732                 dbgUI << "Using ATI float";
0733                 m_texturesInfo.type = GL_FLOAT;
0734                 m_texturesInfo.format = GL_RGBA;
0735                 destinationColorDepthId = Float32BitsColorDepthID;
0736 #else
0737                 if (ctx->format().majorVersion() >= 3) {
0738                     m_texturesInfo.internalFormat = GL_RGBA32F;
0739                     dbgUI << "Using float (GLES 3 non-Angle)";
0740                     m_texturesInfo.type = GL_FLOAT;
0741                     m_texturesInfo.format = GL_RGBA;
0742                     destinationColorDepthId = Float32BitsColorDepthID;
0743                     /* GL_EXT_texture_storage is GLES 2*/
0744                     /* GL_EXT_color_buffer_float is GLES 3*/
0745                 } else if (ctx->hasExtension("GL_OES_texture_float")
0746                            && (ctx->hasExtension("GL_EXT_texture_storage") || ctx->hasExtension("EXT_color_buffer_float"))
0747                            && ctx->hasExtension("GL_OES_texture_float_linear")) {
0748                     m_texturesInfo.internalFormat = GL_RGBA32F_EXT;
0749                     dbgUI << "Using float (GLES v2)";
0750                     m_texturesInfo.type = GL_FLOAT;
0751                     m_texturesInfo.format = GL_RGBA;
0752                     destinationColorDepthId = Float32BitsColorDepthID;
0753                 }
0754 #endif
0755             }
0756         }
0757         else if (colorDepthId == Integer16BitsColorDepthID) {
0758             if (!KisOpenGL::hasOpenGLES()) {
0759 #ifndef QT_OPENGL_ES_2
0760                 m_texturesInfo.internalFormat = GL_RGBA16;
0761                 m_texturesInfo.type = GL_UNSIGNED_SHORT;
0762 
0763                 // On arm M1, GL_BGRA format is not aligned properly at the driver
0764                 // changing the pixel format fixes the rendering problem when using
0765                 // texture buffers
0766                 // BUG: 445561
0767 #ifdef Q_OS_MACOS
0768                 m_texturesInfo.format = GL_RGBA;
0769 #else
0770                 m_texturesInfo.format = GL_BGRA;
0771 #endif
0772                 destinationColorDepthId = Integer16BitsColorDepthID;
0773                 dbgUI << "Using 16 bits rgba";
0774 #else
0775                 KIS_ASSERT_X(false,
0776                              "KisOpenGLCanvas2::updateTextureFormat",
0777                              "Unexpected KisOpenGL::hasOpenGLES returned false");
0778 #endif
0779             } else {
0780 #ifdef QT_OPENGL_ES_2_ANGLE
0781                 // If OpenGL ES, fall back to 16-bit float -- supports HDR
0782                 // Angle does ship GL_EXT_texture_norm16 but it doesn't seem
0783                 // to be renderable by DXGI - it returns a pixel size of 0
0784                 initializeRGBA16FTextures(ctx, m_texturesInfo, destinationColorDepthId);
0785 #else
0786                 if (ctx->hasExtension("GL_EXT_texture_norm16")) {
0787                     m_texturesInfo.internalFormat = GL_RGBA16_EXT;
0788                     m_texturesInfo.type = GL_UNSIGNED_SHORT;
0789                     m_texturesInfo.format = GL_RGBA;
0790                     destinationColorDepthId = Integer16BitsColorDepthID;
0791                     dbgUI << "Using 16 bits rgba (GLES v2)";
0792                 }
0793 #endif
0794             }
0795         }
0796     }
0797     else {
0798         // We will convert the colorspace to 16 bits rgba, instead of 8 bits
0799         if (colorDepthId == Integer16BitsColorDepthID) {
0800             if (!KisOpenGL::hasOpenGLES()) {
0801 #ifndef QT_OPENGL_ES_2
0802                 m_texturesInfo.internalFormat = GL_RGBA16;
0803                 m_texturesInfo.type = GL_UNSIGNED_SHORT;
0804                 // On arm M1, GL_BGRA format is not aligned properly at the driver
0805                 // changing the pixel format fixes the rendering problem when using
0806                 // texture buffers
0807                 // BUG: 445561
0808 #ifdef Q_OS_MACOS
0809                 m_texturesInfo.format = GL_RGBA;
0810 #else
0811                 m_texturesInfo.format = GL_BGRA;
0812 #endif
0813                 destinationColorDepthId = Integer16BitsColorDepthID;
0814                 dbgUI << "Using conversion to 16 bits rgba";
0815 #else
0816                 KIS_ASSERT_X(false, "KisOpenGLCanvas2::updateTextureFormat",
0817                     "Unexpected KisOpenGL::hasOpenGLES returned false");
0818 #endif
0819             } else {
0820 #ifdef QT_OPENGL_ES_2_ANGLE
0821                 // If OpenGL ES, fall back to 16-bit float -- supports HDR
0822                 // Angle does ship GL_EXT_texture_norm16 but it doesn't seem
0823                 // to be renderable by DXGI - it returns a pixel size of 0
0824                 initializeRGBA16FTextures(ctx, m_texturesInfo, destinationColorDepthId);
0825 #else
0826                 if (ctx->hasExtension("GL_EXT_texture_norm16")) {
0827                     m_texturesInfo.internalFormat = GL_RGBA16_EXT;
0828                     m_texturesInfo.type = GL_UNSIGNED_SHORT;
0829                     m_texturesInfo.format = GL_RGBA;
0830                     destinationColorDepthId = Integer16BitsColorDepthID;
0831                     dbgUI << "Using conversion to 16 bits rgba (GLES v2)";
0832                 }
0833 #endif
0834             }
0835         } else if (colorDepthId == Float16BitsColorDepthID) {
0836             initializeRGBA16FTextures(ctx, m_texturesInfo, destinationColorDepthId);
0837         }
0838     }
0839 
0840     if (!m_internalColorManagementActive &&
0841             colorModelId != destinationColorModelId) {
0842 
0843         KisConfig cfg(false);
0844         KisConfig::OcioColorManagementMode cm = cfg.ocioColorManagementMode();
0845 
0846         if (cm != KisConfig::INTERNAL) {
0847             emit sigShowFloatingMessage(
0848                 i18n("OpenColorIO is disabled: image color space is not supported"), 5000, true);
0849         }
0850 
0851         warnUI << "WARNING: Internal color management was forcibly enabled";
0852         warnUI << "Color Management Mode: " << cm;
0853         warnUI << ppVar(m_image->colorSpace());
0854         warnUI << ppVar(destinationColorModelId);
0855         warnUI << ppVar(destinationColorDepthId);
0856 
0857         cfg.setOcioColorManagementMode(KisConfig::INTERNAL);
0858         m_internalColorManagementActive = true;
0859     }
0860 
0861     const KoColorProfile *profile =
0862             m_internalColorManagementActive ||
0863             colorModelId != destinationColorModelId ?
0864                 m_monitorProfile : m_image->colorSpace()->profile();
0865 
0866     /**
0867      * TODO: add an optimization so that the tile->convertTo() method
0868      *       would not be called when not needed (DK)
0869      */
0870 
0871     const KoColorSpace *tilesDestinationColorSpace =
0872             KoColorSpaceRegistry::instance()->colorSpace(destinationColorModelId.id(),
0873                                                          destinationColorDepthId.id(),
0874                                                          profile);
0875 
0876     m_updateInfoBuilder.setConversionOptions(
0877         ConversionOptions(tilesDestinationColorSpace,
0878                           m_renderingIntent,
0879                           m_conversionFlags));
0880 }
0881