diff --git a/libs/flake/KoCanvasBase.h b/libs/flake/KoCanvasBase.h index f571b3a5ec..915a7f2793 100644 --- a/libs/flake/KoCanvasBase.h +++ b/libs/flake/KoCanvasBase.h @@ -1,251 +1,251 @@ /* This file is part of the KDE project Copyright (C) 2006, 2010 Boudewijn Rempt Copyright (C) 2006, 2010 Thomas Zander Copyright (C) 2006 Thorsten Zachmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KOCANVASBASE_H #define KOCANVASBASE_H #include #include "kritaflake_export.h" class QGraphicsObject; class KUndo2Command; class KoUnit; class KoCanvasResourceManager; class KoShapeManager; class KoToolProxy; class KoViewConverter; class KoShapeController; class KoShapeBasedDocumentBase; class KoCanvasController; class KoShape; class KoSnapGuide; class QWidget; class QCursor; class QObject; class QPointF; class QRectF; class QSizeF; /** * KoCanvasBase is the interface actual application canvas classes * should implement. Flake tools know about the canvas, so they can * do things like scroll, redraw, set a cursor etc. */ class KRITAFLAKE_EXPORT KoCanvasBase { public: /** * The constructor. * @param shapeBasedDocument the implementation of the shapeController that the * application provides to allow shapes to be added in multiple views. */ explicit KoCanvasBase(KoShapeBasedDocumentBase *shapeBasedDocument, KoCanvasResourceManager *sharedResourceManager = 0); virtual ~KoCanvasBase(); public: /** * @return true if opengl can be used directly on the canvas */ - virtual bool canvasIsOpenGL() { return false; } + virtual bool canvasIsOpenGL() const { return false; } /** * retrieve the grid size setting. * The grid spacing will be provided in pt. * @param horizontal a pointer to a qreal that will be filled with the horizontal grid-spacing * @param vertical a pointer to a qreal that will be filled with the vertical grid-spacing */ virtual void gridSize(QPointF *offset, QSizeF *spacing) const = 0; /** * return if snap to grid is enabled. * @return if snap to grid is enabled. */ virtual bool snapToGrid() const = 0; /** * set the specified cursor on this canvas * * @param cursor the new cursor * @return the old cursor */ virtual void setCursor(const QCursor &cursor) = 0; /** * Adds a command to the history. Call this for each @p command you create. * This will also execute the command. * This means, most of the application's code will look like * MyCommand * cmd = new MyCommand( parameters ); * canvas.addCommand( cmd ); * * Note that the command history takes ownership of the command, it will delete * it when the undo limit is reached, or when deleting the command history itself. * @param command the command to add */ virtual void addCommand(KUndo2Command *command) = 0; /** * return the current shapeManager * @return the current shapeManager */ virtual KoShapeManager *shapeManager() const = 0; /** * Tell the canvas to repaint the specified rectangle. The coordinates * are document coordinates, not view coordinates. */ virtual void updateCanvas(const QRectF &rc) = 0; /** * Return the proxy to the active tool (determining which tool * is really, really active is hard when tablets are involved, * so leave that to others. */ virtual KoToolProxy *toolProxy() const = 0; /** * Return the viewConverter for this view. * @return the viewConverter for this view. */ virtual KoViewConverter *viewConverter() const = 0; /** * Convert a coordinate in pixels to pt. * @param viewPoint the point in the coordinate system of the widget, or window. */ virtual QPointF viewToDocument(const QPointF &viewPoint) const; /** * Return the widget that will be added to the scrollArea. */ virtual QWidget *canvasWidget() = 0; /** * Return the widget that will be added to the scrollArea. */ virtual const QWidget *canvasWidget() const = 0; /** * Return the widget that will be added to the scrollArea. */ virtual QGraphicsObject *canvasItem() { return 0; } /** * Return the widget that will be added to the scrollArea. */ virtual const QGraphicsObject *canvasItem() const{ return 0; } /** * Return the unit of the current document for initialization of the widgets created * by the flake framework. * @see KoDocument::unit() */ virtual KoUnit unit() const = 0; /** * Called when the user tries to move the argument shape to allow the application to limit the * users movement to stay within the document bounds. * An implementation can alter the parameter move to make sure that if the distance moved * is applied to the shape it will not become unreachable for the user. * The default implementation does not restrict movement. * @param shape the shape that will be moved soon. * @param move the distance the caller intends to move the shape. */ virtual void clipToDocument(const KoShape *shape, QPointF &move) const; /** * Return the position of the document origin inside the canvas widget, in pixels. * By default the origin of the canvas widget and the position of the * document origin are coincident, thus an empty point is returned. */ virtual QPoint documentOrigin() const { return QPoint(0, 0); } /** * This method should somehow call QWidget::updateMicroFocus() on the canvas widget. */ virtual void updateInputMethodInfo() = 0; /** * disconnect the given QObject completely and utterly from any and all * connections it has to any QObject owned by the canvas. Do this in * the setCanvas of every KoCanvasObserver. */ virtual void disconnectCanvasObserver(QObject *object); /** * Return a pointer to the resource manager associated with this * canvas. The resource manager contains per-canvas settings such * as current foreground and background color. * If instead of per-canvas resources you need per-document resources * you can by going via the shapeController instead; * @code * canvasBase->shapeController()->resourceManager(); * @endcode * @see KoShapeController::resourceManager() */ KoCanvasResourceManager *resourceManager() const; /** * Return the shape controller for this canvas. * A shape controller is used to create or delete shapes and show the relevant dialogs to the user. */ KoShapeController *shapeController() const; /** * Return the canvas controller for this canvas. */ KoCanvasController *canvasController() const; /** * @brief Scrolls the content of the canvas so that the given rect is visible. * * The rect is to be specified in document coordinates. * * @param rect the rectangle to make visible */ virtual void ensureVisible(const QRectF &rect); /** * Returns the snap guide of the canvas */ KoSnapGuide *snapGuide() const; /// called by KoCanvasController to set the controller that handles this canvas. void setCanvasController(KoCanvasController *controller); private: // we need a KoShapeBasedDocumentBase so that it can work KoCanvasBase(); class Private; Private * const d; }; #endif // KOCANVASBASE_H diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp index 4a7991afef..a2be8bd701 100644 --- a/libs/ui/canvas/kis_canvas2.cpp +++ b/libs/ui/canvas/kis_canvas2.cpp @@ -1,915 +1,922 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) Lukáš Tvrdý , (C) 2010 * Copyright (C) 2011 Silvio Heinrich * * 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_canvas2.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include "kis_coordinates_converter.h" #include "kis_prescaled_projection.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "KisDocument.h" #include "flake/kis_shape_layer.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_abstract_canvas_widget.h" #include "kis_qpainter_canvas.h" #include "kis_group_layer.h" #include "flake/kis_shape_controller.h" #include "kis_node_manager.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "flake/kis_shape_selection.h" #include "kis_image_config.h" #include "kis_infinity_manager.h" #include "kis_signal_compressor.h" #include "kis_display_color_converter.h" #include "kis_exposure_gamma_correction_interface.h" #include "KisView.h" #include "kis_canvas_controller.h" #include "kis_grid_config.h" #ifdef HAVE_OPENGL #include "kis_animation_player.h" #include "kis_animation_frame_cache.h" #include "opengl/kis_opengl_canvas2.h" #endif #include "opengl/kis_opengl.h" #include "kis_fps_decoration.h" #include #include #include "input/kis_input_manager.h" #include "kis_painting_assistants_decoration.h" #include "kis_canvas_updates_compressor.h" class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private { public: KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer view, KoCanvasResourceManager* resourceManager) : coordinatesConverter(coordConverter) , view(view) , shapeManager(parent) , toolProxy(parent) , displayColorConverter(resourceManager, view) { } KisCoordinatesConverter *coordinatesConverter; QPointerview; KisAbstractCanvasWidget *canvasWidget = 0; KoShapeManager shapeManager; bool currentCanvasIsOpenGL; #ifdef HAVE_OPENGL int openGLFilterMode; #endif KisToolProxy toolProxy; KisPrescaledProjectionSP prescaledProjection; bool vastScrolling; KisSignalCompressor updateSignalCompressor; QRect savedUpdateRect; QBitArray channelFlags; KisPopupPalette *popupPalette = 0; KisDisplayColorConverter displayColorConverter; KisCanvasUpdatesCompressor projectionUpdatesCompressor; #ifdef HAVE_OPENGL KisAnimationPlayer *animationPlayer; KisAnimationFrameCacheSP frameCache; #endif bool lodAllowedInCanvas; bool bootsrapLodBlocked; bool effectiveLodAllowedInCanvas() { return lodAllowedInCanvas && !bootsrapLodBlocked; } }; KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeBasedDocumentBase *sc) : KoCanvasBase(sc, resourceManager) , m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager)) { /** * While loading LoD should be blocked. Only when GUI has finished * loading and zoom level settled down, LoD is given a green * light. */ m_d->bootsrapLodBlocked = true; connect(view->mainWindow(), SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished())); m_d->updateSignalCompressor.setDelay(10); m_d->updateSignalCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); } void KisCanvas2::setup() { // a bit of duplication from slotConfigChanged() KisConfig cfg; m_d->vastScrolling = cfg.vastScrolling(); m_d->lodAllowedInCanvas = cfg.levelOfDetailEnabled(); createCanvas(cfg.useOpenGL()); setLodAllowedInCanvas(m_d->lodAllowedInCanvas); #ifdef HAVE_OPENGL m_d->animationPlayer = new KisAnimationPlayer(this); #endif connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); /** * We switch the shape manager every time vector layer or * shape selection is activated. Flake does not expect this * and connects all the signals of the global shape manager * to the clients in the constructor. To workaround this we * forward the signals of local shape managers stored in the * vector layers to the signals of global shape manager. So the * sequence of signal deliveries is the following: * * shapeLayer.m_d.canvas.m_shapeManager.selection() -> * shapeLayer -> * shapeController -> * globalShapeManager.selection() */ KisShapeController *kritaShapeController = static_cast(shapeController()->documentBase()); connect(kritaShapeController, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); connect(kritaShapeController, SIGNAL(selectionContentChanged()), globalShapeManager(), SIGNAL(selectionContentChanged())); connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)), globalShapeManager()->selection(), SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate())); } KisCanvas2::~KisCanvas2() { #ifdef HAVE_OPENGL if (m_d->animationPlayer->isPlaying()) { m_d->animationPlayer->forcedStopOnExit(); } #endif delete m_d; } void KisCanvas2::setCanvasWidget(QWidget * widget) { KisAbstractCanvasWidget *tmp = dynamic_cast(widget); Q_ASSERT_X(tmp, "setCanvasWidget", "Cannot cast the widget to a KisAbstractCanvasWidget"); if (m_d->popupPalette) { m_d->popupPalette->setParent(widget); } if(m_d->canvasWidget != 0) { tmp->setDecorations(m_d->canvasWidget->decorations()); // Redundant check for the constructor case, see below if(viewManager() != 0) viewManager()->inputManager()->removeTrackedCanvas(this); } m_d->canvasWidget = tmp; // Either tmp was null or we are being called by KisCanvas2 constructor that is called by KisView // constructor, so the view manager still doesn't exists. if(m_d->canvasWidget != 0 && viewManager() != 0) viewManager()->inputManager()->addTrackedCanvas(this); if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) { KisInfinityManager *manager = new KisInfinityManager(m_d->view, this); manager->setVisible(true); m_d->canvasWidget->addDecoration(manager); } widget->setAutoFillBackground(false); widget->setAttribute(Qt::WA_OpaquePaintEvent); widget->setMouseTracking(true); widget->setAcceptDrops(true); KoCanvasControllerWidget *controller = dynamic_cast(canvasController()); if (controller) { Q_ASSERT(controller->canvas() == this); controller->changeCanvasWidget(widget); } } -bool KisCanvas2::canvasIsOpenGL() +bool KisCanvas2::canvasIsOpenGL() const { return m_d->currentCanvasIsOpenGL; } +KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const +{ + return KisOpenGL::FilterMode(m_d->openGLFilterMode); +} + void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const { QTransform transform = coordinatesConverter()->imageToDocumentTransform(); const QPoint intSpacing = m_d->view->document()->gridConfig().spacing(); const QPoint intOffset = m_d->view->document()->gridConfig().offset(); QPointF size = transform.map(QPointF(intSpacing)); spacing->rwidth() = size.x(); spacing->rheight() = size.y(); *offset = transform.map(QPointF(intOffset)); } bool KisCanvas2::snapToGrid() const { return m_d->view->document()->gridConfig().snapToGrid(); } qreal KisCanvas2::rotationAngle() const { return m_d->coordinatesConverter->rotationAngle(); } bool KisCanvas2::xAxisMirrored() const { return m_d->coordinatesConverter->xAxisMirrored(); } bool KisCanvas2::yAxisMirrored() const { return m_d->coordinatesConverter->yAxisMirrored(); } void KisCanvas2::channelSelectionChanged() { KisImageWSP image = this->image(); m_d->channelFlags = image->rootLayer()->channelFlags(); image->barrierLock(); m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags); startUpdateInPatches(image->bounds()); image->unlock(); } void KisCanvas2::addCommand(KUndo2Command *command) { // This method exists to support flake-related operations m_d->view->document()->addCommand(command); } KoShapeManager* KisCanvas2::shapeManager() const { if (!viewManager()) return globalShapeManager(); if (!viewManager()->nodeManager()) return globalShapeManager(); KisLayerSP activeLayer = viewManager()->nodeManager()->activeLayer(); if (activeLayer && activeLayer->isEditable()) { KisShapeLayer * shapeLayer = dynamic_cast(activeLayer.data()); if (shapeLayer) { return shapeLayer->shapeManager(); } KisSelectionSP selection = activeLayer->selection(); if (selection && !selection.isNull()) { if (selection->hasShapeSelection()) { KoShapeManager* m = dynamic_cast(selection->shapeSelection())->shapeManager(); return m; } } } return globalShapeManager(); } KoShapeManager * KisCanvas2::globalShapeManager() const { return &m_d->shapeManager; } void KisCanvas2::updateInputMethodInfo() { // TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget... } const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const { return m_d->coordinatesConverter; } KoViewConverter* KisCanvas2::viewConverter() const { return m_d->coordinatesConverter; } KisInputManager* KisCanvas2::globalInputManager() const { return m_d->view->globalInputManager(); } QWidget* KisCanvas2::canvasWidget() { return m_d->canvasWidget->widget(); } const QWidget* KisCanvas2::canvasWidget() const { return m_d->canvasWidget->widget(); } KoUnit KisCanvas2::unit() const { KoUnit unit(KoUnit::Pixel); KisImageWSP image = m_d->view->image(); if (image) { if (!qFuzzyCompare(image->xRes(), image->yRes())) { warnKrita << "WARNING: resolution of the image is anisotropic" << ppVar(image->xRes()) << ppVar(image->yRes()); } const qreal resolution = image->xRes(); unit.setFactor(resolution); } return unit; } KoToolProxy * KisCanvas2::toolProxy() const { return &m_d->toolProxy; } void KisCanvas2::createQPainterCanvas() { m_d->currentCanvasIsOpenGL = false; KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view); m_d->prescaledProjection = new KisPrescaledProjection(); m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter); m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(), m_d->displayColorConverter.renderingIntent(), m_d->displayColorConverter.conversionFlags()); m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter()); canvasWidget->setPrescaledProjection(m_d->prescaledProjection); setCanvasWidget(canvasWidget); } void KisCanvas2::createOpenGLCanvas() { #ifdef HAVE_OPENGL KisConfig cfg; m_d->openGLFilterMode = cfg.openGLFilteringMode(); m_d->currentCanvasIsOpenGL = true; KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter); m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures()); setCanvasWidget(canvasWidget); if (canvasWidget->needsFpsDebugging() && !decoration(KisFpsDecoration::idTag)) { addDecoration(new KisFpsDecoration(imageView())); } #else qFatal("Bad use of createOpenGLCanvas(). It shouldn't have happened =("); #endif } void KisCanvas2::createCanvas(bool useOpenGL) { // deinitialize previous canvas structures m_d->prescaledProjection = 0; #ifdef HAVE_OPENGL m_d->frameCache = 0; #endif KisConfig cfg; QDesktopWidget dw; const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView())); m_d->displayColorConverter.setMonitorProfile(profile); if (useOpenGL) { #ifdef HAVE_OPENGL if (KisOpenGL::hasOpenGL()) { createOpenGLCanvas(); if (cfg.canvasState() == "OPENGL_FAILED") { // Creating the opengl canvas failed, fall back warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter."; createQPainterCanvas(); } } else { warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n"; createQPainterCanvas(); } #else warnKrita << "User requested an OpenGL canvas, but Krita was compiled without OpenGL support. Falling back to QPainter."; createQPainterCanvas(); #endif } else { createQPainterCanvas(); } if (m_d->popupPalette) { m_d->popupPalette->setParent(m_d->canvasWidget->widget()); } } void KisCanvas2::initializeImage() { KisImageWSP image = m_d->view->image(); m_d->coordinatesConverter->setImage(image); connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection); connect(this, SIGNAL(sigCanvasCacheUpdated()), SLOT(updateCanvasProjection())); connect(image, SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), SLOT(startResizingImage()), Qt::DirectConnection); connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32))); connectCurrentCanvas(); } void KisCanvas2::connectCurrentCanvas() { KisImageWSP image = m_d->view->image(); if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->setImage(image); } startResizingImage(); emit imageChanged(image); setLodAllowedInCanvas(m_d->lodAllowedInCanvas); } void KisCanvas2::resetCanvas(bool useOpenGL) { // we cannot reset the canvas before it's created, but this method might be called, // for instance when setting the monitor profile. if (!m_d->canvasWidget) { return; } #ifdef HAVE_OPENGL KisConfig cfg; bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) || (m_d->currentCanvasIsOpenGL && m_d->openGLFilterMode != cfg.openGLFilteringMode()); if (needReset) { createCanvas(useOpenGL); connectCurrentCanvas(); notifyZoomChanged(); } #else Q_UNUSED(useOpenGL) #endif updateCanvasWidgetImpl(); } void KisCanvas2::startUpdateInPatches(const QRect &imageRect) { if (m_d->currentCanvasIsOpenGL) { startUpdateCanvasProjection(imageRect); } else { KisImageConfig imageConfig; int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < imageRect.height(); y += patchHeight) { for (int x = 0; x < imageRect.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); startUpdateCanvasProjection(patchRect); } } } } void KisCanvas2::setDisplayFilter(KisDisplayFilter *displayFilter) { m_d->displayColorConverter.setDisplayFilter(displayFilter); KisImageWSP image = this->image(); image->barrierLock(); m_d->canvasWidget->setDisplayFilter(displayFilter); image->unlock(); } KisDisplayFilter *KisCanvas2::displayFilter() const { return m_d->displayColorConverter.displayFilter(); } KisDisplayColorConverter* KisCanvas2::displayColorConverter() const { return &m_d->displayColorConverter; } KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const { KisDisplayFilter *displayFilter = m_d->displayColorConverter.displayFilter(); return displayFilter ? displayFilter->correctionInterface() : KisDumbExposureGammaCorrectionInterface::instance(); } void KisCanvas2::startResizingImage() { KisImageWSP image = this->image(); qint32 w = image->width(); qint32 h = image->height(); emit sigContinueResizeImage(w, h); QRect imageBounds(0, 0, w, h); startUpdateInPatches(imageBounds); } void KisCanvas2::finishResizingImage(qint32 w, qint32 h) { m_d->canvasWidget->finishResizingImage(w, h); } void KisCanvas2::startUpdateCanvasProjection(const QRect & rc) { KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags); if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) { emit sigCanvasCacheUpdated(); } } void KisCanvas2::updateCanvasProjection() { while (KisUpdateInfoSP info = m_d->projectionUpdatesCompressor.takeUpdateInfo()) { QRect vRect = m_d->canvasWidget->updateCanvasProjection(info); if (!vRect.isEmpty()) { updateCanvasWidgetImpl(m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect()); } } // TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas if (m_d->currentCanvasIsOpenGL) { updateCanvasWidgetImpl(); } } void KisCanvas2::slotDoCanvasUpdate() { if (m_d->canvasWidget->isBusy()) { // just restarting the timer updateCanvasWidgetImpl(m_d->savedUpdateRect); return; } if (m_d->savedUpdateRect.isEmpty()) { m_d->canvasWidget->widget()->update(); emit updateCanvasRequested(m_d->canvasWidget->widget()->rect()); } else { emit updateCanvasRequested(m_d->savedUpdateRect); m_d->canvasWidget->widget()->update(m_d->savedUpdateRect); } m_d->savedUpdateRect = QRect(); } void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc) { if (!m_d->updateSignalCompressor.isActive() || !m_d->savedUpdateRect.isEmpty()) { m_d->savedUpdateRect |= rc; } m_d->updateSignalCompressor.start(); } void KisCanvas2::updateCanvas() { updateCanvasWidgetImpl(); } void KisCanvas2::updateCanvas(const QRectF& documentRect) { if (m_d->currentCanvasIsOpenGL && m_d->canvasWidget->decorations().size() > 0) { updateCanvasWidgetImpl(); } else { // updateCanvas is called from tools, never from the projection // updates, so no need to prescale! QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect(); widgetRect.adjust(-2, -2, 2, 2); if (!widgetRect.isEmpty()) { updateCanvasWidgetImpl(widgetRect); } } } void KisCanvas2::disconnectCanvasObserver(QObject *object) { KoCanvasBase::disconnectCanvasObserver(object); m_d->view->disconnect(object); } void KisCanvas2::notifyZoomChanged() { if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->notifyZoomChanged(); } notifyLevelOfDetailChange(); updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction } void KisCanvas2::notifyLevelOfDetailChange() { if (!m_d->effectiveLodAllowedInCanvas()) return; KisImageSP image = this->image(); const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom(); KisConfig cfg; const int maxLod = cfg.numMipmapLevels(); int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod); image->setDesiredLevelOfDetail(lod); } void KisCanvas2::preScale() { if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->preScale(); } } const KoColorProfile * KisCanvas2::monitorProfile() { return m_d->displayColorConverter.monitorProfile(); } KisViewManager* KisCanvas2::viewManager() const { if (m_d->view) { return m_d->view->viewManager(); } return 0; } QPointerKisCanvas2::imageView() const { return m_d->view; } KisImageWSP KisCanvas2::image() const { return m_d->view->image(); } KisImageWSP KisCanvas2::currentImage() const { return m_d->view->image(); } void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset) { QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); m_d->coordinatesConverter->setDocumentOffset(documentOffset); QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); QPointF moveOffset = offsetAfter - offsetBefore; if (!m_d->currentCanvasIsOpenGL) m_d->prescaledProjection->viewportMoved(moveOffset); emit documentOffsetUpdateFinished(); updateCanvas(); } void KisCanvas2::slotConfigChanged() { KisConfig cfg; m_d->vastScrolling = cfg.vastScrolling(); resetCanvas(cfg.useOpenGL()); slotSetDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this->canvasWidget()))); } void KisCanvas2::refetchDataFromImage() { KisImageSP image = this->image(); KisImageBarrierLocker l(image); startUpdateInPatches(image->bounds()); } void KisCanvas2::slotSetDisplayProfile(const KoColorProfile *monitorProfile) { if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return; m_d->displayColorConverter.setMonitorProfile(monitorProfile); KisImageWSP image = this->image(); image->barrierLock(); m_d->canvasWidget->setDisplayProfile(&m_d->displayColorConverter); refetchDataFromImage(); } void KisCanvas2::addDecoration(KisCanvasDecoration* deco) { m_d->canvasWidget->addDecoration(deco); } KisCanvasDecoration* KisCanvas2::decoration(const QString& id) const { return m_d->canvasWidget->decoration(id); } QPoint KisCanvas2::documentOrigin() const { /** * In Krita we don't use document origin anymore. * All the centering when needed (vastScrolling < 0.5) is done * automatically by the KisCoordinatesConverter. */ return QPoint(); } QPoint KisCanvas2::documentOffset() const { return m_d->coordinatesConverter->documentOffset(); } void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager) { m_d->popupPalette = new KisPopupPalette(favoriteResourceManager, displayColorConverter()->displayRendererInterface(), m_d->canvasWidget->widget()); m_d->popupPalette->showPopupPalette(false); } void KisCanvas2::setCursor(const QCursor &cursor) { canvasWidget()->setCursor(cursor); } #ifdef HAVE_OPENGL KisAnimationFrameCacheSP KisCanvas2::frameCache() const { return m_d->frameCache; } KisAnimationPlayer *KisCanvas2::animationPlayer() const { return m_d->animationPlayer; } #endif void KisCanvas2::slotSelectionChanged() { KisShapeLayer* shapeLayer = dynamic_cast(viewManager()->activeLayer().data()); if (!shapeLayer) { return; } m_d->shapeManager.selection()->deselectAll(); Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) { m_d->shapeManager.selection()->select(shape); } } bool KisCanvas2::isPopupPaletteVisible() const { if (!m_d->popupPalette) { return false; } return m_d->popupPalette->isVisible(); } void KisCanvas2::setWrapAroundViewingMode(bool value) { KisCanvasDecoration *infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { infinityDecoration->setVisible(!value); } m_d->canvasWidget->setWrapAroundViewingMode(value); } bool KisCanvas2::wrapAroundViewingMode() const { KisCanvasDecoration *infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { return !(infinityDecoration->visible()); } return false; } void KisCanvas2::bootstrapFinished() { if (!m_d->bootsrapLodBlocked) return; m_d->bootsrapLodBlocked = false; setLodAllowedInCanvas(m_d->lodAllowedInCanvas); } void KisCanvas2::setLodAllowedInCanvas(bool value) { #ifdef HAVE_OPENGL m_d->lodAllowedInCanvas = value && - m_d->currentCanvasIsOpenGL; + m_d->currentCanvasIsOpenGL && + (m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode || + m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering); #else Q_UNUSED(value); m_d->lodAllowedInCanvas = false; #endif KisImageSP image = this->image(); if (m_d->effectiveLodAllowedInCanvas() != !image->levelOfDetailBlocked()) { image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInCanvas()); notifyLevelOfDetailChange(); } KisConfig cfg; cfg.setLevelOfDetailEnabled(m_d->lodAllowedInCanvas); } bool KisCanvas2::lodAllowedInCanvas() const { return m_d->lodAllowedInCanvas; } void KisCanvas2::slotShowPopupPalette(const QPoint &p) { if (!m_d->popupPalette) { return; } m_d->popupPalette->showPopupPalette(p); } KisPaintingAssistantsDecoration* KisCanvas2::paintingAssistantsDecoration() const { KisCanvasDecoration* deco = decoration("paintingAssistantsDecoration"); return qobject_cast(deco); } diff --git a/libs/ui/canvas/kis_canvas2.h b/libs/ui/canvas/kis_canvas2.h index 5d7f93da1c..2097622658 100644 --- a/libs/ui/canvas/kis_canvas2.h +++ b/libs/ui/canvas/kis_canvas2.h @@ -1,273 +1,281 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) 2011 Silvio Heinrich * * 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_CANVAS_H #define KIS_CANVAS_H #include #include #include #include #include #include #include #include #include #include +#ifdef HAVE_OPENGL +#include "opengl/kis_opengl.h" +#endif + #include "kis_ui_types.h" #include "kis_coordinates_converter.h" class KoToolProxy; class KoColorProfile; class KisCanvasDecoration; class KisViewManager; class KisFavoriteResourceManager; class KisDisplayFilter; class KisDisplayColorConverter; struct KisExposureGammaCorrectionInterface; class KisPaintingAssistantsDecoration; class KisView; class KisInputManager; class KisAnimationPlayer; class KisShapeController; class KisCoordinatesConverter; class KoViewConverter; /** * KisCanvas2 is not an actual widget class, but rather an adapter for * the widget it contains, which may be either a QPainter based * canvas, or an OpenGL based canvas: that are the real widgets. */ class KRITAUI_EXPORT KisCanvas2 : public QObject, public KoCanvasBase { Q_OBJECT public: /** * Create a new canvas. The canvas manages a widget that will do * the actual painting: the canvas itself is not a widget. * * @param viewConverter the viewconverter for converting between * window and document coordinates. */ KisCanvas2(KisCoordinatesConverter* coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeBasedDocumentBase* sc); virtual ~KisCanvas2(); void notifyZoomChanged(); virtual void disconnectCanvasObserver(QObject *object); public: // KoCanvasBase implementation - bool canvasIsOpenGL(); + bool canvasIsOpenGL() const; + +#ifdef HAVE_OPENGL + KisOpenGL::FilterMode openGLFilterMode() const; +#endif void gridSize(QPointF *offset, QSizeF *spacing) const; bool snapToGrid() const; // XXX: Why? void addCommand(KUndo2Command *command); virtual QPoint documentOrigin() const; QPoint documentOffset() const; /** * Return the right shape manager for the current layer. That is * to say, if the current layer is a vector layer, return the shape * layer's canvas' shapemanager, else the shapemanager associated * with the global krita canvas. */ KoShapeManager * shapeManager() const; /** * Return the shape manager associated with this canvas */ KoShapeManager * globalShapeManager() const; void updateCanvas(const QRectF& rc); virtual void updateInputMethodInfo(); const KisCoordinatesConverter* coordinatesConverter() const; virtual KoViewConverter *viewConverter() const; virtual QWidget* canvasWidget(); virtual const QWidget* canvasWidget() const; virtual KoUnit unit() const; virtual KoToolProxy* toolProxy() const; const KoColorProfile* monitorProfile(); /** * Prescale the canvas represention of the image (if necessary, it * is for QPainter, not for OpenGL). */ void preScale(); // FIXME: // Temporary! Either get the current layer and image from the // resource provider, or use this, which gets them from the // current shape selection. KisImageWSP currentImage() const; /** * Filters events and sends them to canvas actions. Shared * among all the views/canvases * * NOTE: May be null while initialization! */ KisInputManager* globalInputManager() const; KisPaintingAssistantsDecoration* paintingAssistantsDecoration() const; public: // KisCanvas2 methods KisImageWSP image() const; KisViewManager* viewManager() const; QPointer imageView() const; /// @return true if the canvas image should be displayed in vertically mirrored mode void addDecoration(KisCanvasDecoration* deco); KisCanvasDecoration* decoration(const QString& id) const; void setDisplayFilter(KisDisplayFilter *displayFilter); KisDisplayFilter *displayFilter() const; KisDisplayColorConverter* displayColorConverter() const; KisExposureGammaCorrectionInterface* exposureGammaCorrectionInterface() const; void setCursor(const QCursor &cursor); #ifdef HAVE_OPENGL KisAnimationFrameCacheSP frameCache() const; KisAnimationPlayer *animationPlayer() const; #endif void refetchDataFromImage(); Q_SIGNALS: void imageChanged(KisImageWSP image); void sigCanvasCacheUpdated(); void sigContinueResizeImage(qint32 w, qint32 h); void documentOffsetUpdateFinished(); // emitted whenever the canvas widget thinks sketch should update void updateCanvasRequested(const QRect &rc); public Q_SLOTS: /// Update the entire canvas area void updateCanvas(); void startResizingImage(); void finishResizingImage(qint32 w, qint32 h); /// canvas rotation in degrees qreal rotationAngle() const; /// Bools indicating canvasmirroring. bool xAxisMirrored() const; bool yAxisMirrored() const; void channelSelectionChanged(); /** * Called whenever the display monitor profile resource changes */ void slotSetDisplayProfile(const KoColorProfile *profile); void startUpdateInPatches(const QRect &imageRect); private Q_SLOTS: /// The image projection has changed, now start an update /// of the canvas representation. void startUpdateCanvasProjection(const QRect & rc); void updateCanvasProjection(); /** * Called whenever the view widget needs to show a different part of * the document * * @param documentOffset the offset in widget pixels */ void documentOffsetMoved(const QPoint &documentOffset); /** * Called whenever the configuration settings change. */ void slotConfigChanged(); void slotSelectionChanged(); void slotDoCanvasUpdate(); void bootstrapFinished(); public: bool isPopupPaletteVisible() const; void slotShowPopupPalette(const QPoint& = QPoint(0,0)); // interface for KisCanvasController only void setWrapAroundViewingMode(bool value); bool wrapAroundViewingMode() const; void setLodAllowedInCanvas(bool value); bool lodAllowedInCanvas() const; void initializeImage(); void setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager); private: Q_DISABLE_COPY(KisCanvas2) void connectCurrentCanvas(); void createCanvas(bool useOpenGL); void createQPainterCanvas(); void createOpenGLCanvas(); void updateCanvasWidgetImpl(const QRect &rc = QRect()); void setCanvasWidget(QWidget *widget); void resetCanvas(bool useOpenGL); void notifyLevelOfDetailChange(); // Completes construction of canvas. // To be called by KisView in its constructor, once it has been setup enough // (to be defined what that means) for things KisCanvas2 expects from KisView // TODO: see to avoid that void setup(); private: friend class KisView; // calls setup() class KisCanvas2Private; KisCanvas2Private * const m_d; }; #endif diff --git a/libs/ui/canvas/kis_canvas_controller.cpp b/libs/ui/canvas/kis_canvas_controller.cpp index 0cd016a0a5..2a48c0873f 100644 --- a/libs/ui/canvas/kis_canvas_controller.cpp +++ b/libs/ui/canvas/kis_canvas_controller.cpp @@ -1,275 +1,298 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * 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_canvas_controller.h" #include #include #include #include "kis_canvas_decoration.h" #include "kis_paintop_transformation_connector.h" #include "kis_coordinates_converter.h" #include "kis_canvas2.h" #include "kis_image.h" #include "KisViewManager.h" #include "KisView.h" #include "krita_utils.h" #include "kis_signal_compressor_with_param.h" static const int gRulersUpdateDelay = 80 /* ms */; struct KisCanvasController::Private { Private(KisCanvasController *qq) : q(qq), paintOpTransformationConnector(0) { using namespace std::placeholders; std::function callback( std::bind(&KisCanvasController::Private::emitPointerPositionChangedSignals, this, _1)); mousePositionCompressor.reset( new KisSignalCompressorWithParam( gRulersUpdateDelay, callback, KisSignalCompressor::FIRST_ACTIVE)); } QPointer view; KisCoordinatesConverter *coordinatesConverter; KisCanvasController *q; KisPaintopTransformationConnector *paintOpTransformationConnector; QScopedPointer > mousePositionCompressor; void emitPointerPositionChangedSignals(QPoint pointerPos); void updateDocumentSizeAfterTransform(); void showRotationValueOnCanvas(); void showMirrorStateOnCanvas(); }; void KisCanvasController::Private::emitPointerPositionChangedSignals(QPoint pointerPos) { if (!coordinatesConverter) return; QPointF documentPos = coordinatesConverter->widgetToDocument(pointerPos); q->proxyObject->emitDocumentMousePositionChanged(documentPos); q->proxyObject->emitCanvasMousePositionChanged(pointerPos); } void KisCanvasController::Private::updateDocumentSizeAfterTransform() { // round the size of the area to the nearest integer instead of getting aligned rect QSize widgetSize = coordinatesConverter->imageRectInWidgetPixels().toRect().size(); q->updateDocumentSize(widgetSize, true); KisCanvas2 *kritaCanvas = dynamic_cast(q->canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->notifyZoomChanged(); } KisCanvasController::KisCanvasController(QPointerparent, KActionCollection * actionCollection) : KoCanvasControllerWidget(actionCollection, parent), m_d(new Private(this)) { m_d->view = parent; } KisCanvasController::~KisCanvasController() { delete m_d; } void KisCanvasController::setCanvas(KoCanvasBase *canvas) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas); Q_ASSERT(kritaCanvas); m_d->coordinatesConverter = const_cast(kritaCanvas->coordinatesConverter()); KoCanvasControllerWidget::setCanvas(canvas); m_d->paintOpTransformationConnector = new KisPaintopTransformationConnector(kritaCanvas, this); } void KisCanvasController::changeCanvasWidget(QWidget *widget) { KoCanvasControllerWidget::changeCanvasWidget(widget); } void KisCanvasController::activate() { KoCanvasControllerWidget::activate(); } void KisCanvasController::keyPressEvent(QKeyEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::keyPressEvent() * to avoid activation of Pan and Default tool activation shortcuts */ Q_UNUSED(event); } void KisCanvasController::wheelEvent(QWheelEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::wheelEvent() * to disable the default behavior of KoCanvasControllerWidget and QAbstractScrollArea */ Q_UNUSED(event); } bool KisCanvasController::eventFilter(QObject *watched, QEvent *event) { KoCanvasBase *canvas = this->canvas(); if (!canvas || !canvas->canvasWidget() || canvas->canvasWidget() != watched) return false; if (event->type() == QEvent::MouseMove) { QMouseEvent *mevent = static_cast(event); m_d->mousePositionCompressor->start(mevent->pos()); } else if (event->type() == QEvent::TabletMove) { QTabletEvent *tevent = static_cast(event); m_d->mousePositionCompressor->start(tevent->pos()); } return false; } void KisCanvasController::updateDocumentSize(const QSize &sz, bool recalculateCenter) { KoCanvasControllerWidget::updateDocumentSize(sz, recalculateCenter); emit documentSizeChanged(); } void KisCanvasController::Private::showMirrorStateOnCanvas() { bool isXMirrored = coordinatesConverter->xAxisMirrored(); view->viewManager()-> showFloatingMessage( i18nc("floating message about mirroring", "Horizontal mirroring: %1 ", isXMirrored ? i18n("ON") : i18n("OFF")), QIcon(), 500, KisFloatingMessage::Low); } void KisCanvasController::mirrorCanvas(bool enable) { KisCanvasDecoration *decorator = dynamic_cast(this->canvas())->decoration("mirror_axis"); KIS_ASSERT_RECOVER_RETURN(decorator); decorator->setVisible(enable); QPoint newOffset = m_d->coordinatesConverter->mirror(m_d->coordinatesConverter->widgetCenterPoint(), enable, false); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showMirrorStateOnCanvas(); } void KisCanvasController::Private::showRotationValueOnCanvas() { qreal rotationAngle = coordinatesConverter->rotationAngle(); view->viewManager()-> showFloatingMessage( i18nc("floating message about rotation", "Rotation: %1° ", KritaUtils::prettyFormatReal(rotationAngle)), QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter); } void KisCanvasController::rotateCanvas(qreal angle) { QPoint newOffset = m_d->coordinatesConverter->rotate(m_d->coordinatesConverter->widgetCenterPoint(), angle); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showRotationValueOnCanvas(); } void KisCanvasController::rotateCanvasRight15() { rotateCanvas(15.0); } void KisCanvasController::rotateCanvasLeft15() { rotateCanvas(-15.0); } void KisCanvasController::resetCanvasRotation() { QPoint newOffset = m_d->coordinatesConverter->resetRotation(m_d->coordinatesConverter->widgetCenterPoint()); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->paintOpTransformationConnector->notifyTransformationChanged(); m_d->showRotationValueOnCanvas(); } void KisCanvasController::slotToggleWrapAroundMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); if (!canvas()->canvasIsOpenGL() && value) { m_d->view->viewManager()->showFloatingMessage(i18n("You are activating wrap-around mode, but have not enabled OpenGL.\n" "To visualize wrap-around mode, enable OpenGL."), QIcon()); } kritaCanvas->setWrapAroundViewingMode(value); kritaCanvas->image()->setWrapAroundModePermitted(value); } bool KisCanvasController::wrapAroundMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->wrapAroundViewingMode(); } void KisCanvasController::slotToggleLevelOfDetailMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->setLodAllowedInCanvas(value); - int result = levelOfDetailMode(); + bool result = levelOfDetailMode(); + + if (!value || result) { + m_d->view->viewManager()->showFloatingMessage( + i18n("Instant Preview Mode: %1", result ? + i18n("ON") : i18n("OFF")), + QIcon(), 500, KisFloatingMessage::Low); + } else { + QString reason; + + if (!kritaCanvas->canvasIsOpenGL()) { + reason = i18n("Instant Preview is only supported with OpenGL activated"); + } +#ifdef HAVE_OPENGL + else if (kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode || + kritaCanvas->openGLFilterMode() == KisOpenGL::NearestFilterMode) { + QString filteringMode = + kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ? + i18n("Bilinear") : i18n("Nearest Neighbour"); + reason = i18n("Instant Preview is supported\n in Trilinear of High Quality filtering modes.\nCurrent mode is %1", filteringMode); + } +#endif + + m_d->view->viewManager()->showFloatingMessage( + i18n("Failed activating Instant Preview mode!\n\n%1", reason), + QIcon(), 5000, KisFloatingMessage::Low); + } + - m_d->view->viewManager()->showFloatingMessage( - i18n("Instant Preview Mode: %1", result ? - i18n("ON") : i18n("OFF")), - QIcon(), 500, KisFloatingMessage::Low); } bool KisCanvasController::levelOfDetailMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->lodAllowedInCanvas(); } diff --git a/libs/ui/opengl/kis_opengl.h b/libs/ui/opengl/kis_opengl.h index 216c0f0033..9f855a1f26 100644 --- a/libs/ui/opengl/kis_opengl.h +++ b/libs/ui/opengl/kis_opengl.h @@ -1,72 +1,79 @@ /* * Copyright (c) 2007 Adrian Page * * 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_H_ #define KIS_OPENGL_H_ /** @file */ #include #if defined(_WIN32) || defined(_WIN64) #include #endif #include class QOpenGLContext; #include "kritaui_export.h" /** * This class manages a shared OpenGL context and provides utility * functions for checking capabilities and error reporting. */ class KRITAUI_EXPORT KisOpenGL { +public: + enum FilterMode { + NearestFilterMode, // nearest + BilinearFilterMode, // linear, no mipmap + TrilinearFilterMode, // LINEAR_MIPMAP_LINEAR + HighQualityFiltering // Mipmaps + custom shader + }; public: /// Request OpenGL version 3.2 static void initialize(); /// Initialize shared OpenGL context static int initializeContext(QOpenGLContext* s); /// Check for OpenGL static bool hasOpenGL(); /** * @brief supportsFilter * @return True if OpenGL provides fence sync methods. */ static bool supportsFenceSync(); /** * Returns true if we have a driver that has bugged support to sync objects (a fence) * and false otherwise. */ static bool needsFenceWorkaround(); static QString renderer(); private: KisOpenGL(); }; #endif // KIS_OPENGL_H_ diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp index d3ca898653..1eb05c014a 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -1,806 +1,806 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2006-2013 * Copyright (C) 2015 Michael Abrahams * * 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. */ #define GL_GLEXT_PROTOTYPES #include "opengl/kis_opengl_canvas2.h" #ifdef HAVE_OPENGL #include "opengl/kis_opengl_canvas2_p.h" #include "opengl/kis_opengl_canvas_debugger.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_coordinates_converter.h" #include "canvas/kis_display_filter.h" #include "canvas/kis_display_color_converter.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_TEXCOORD_ATTRIBUTE 1 static bool OPENGL_SUCCESS = false; typedef void (*kis_glLogicOp)(int); static kis_glLogicOp ptr_glLogicOp = 0; struct KisOpenGLCanvas2::Private { public: ~Private() { delete displayShader; delete checkerShader; delete cursorShader; Sync::deleteSync(glSyncObject); } bool canvasInitialized{false}; QVector3D vertices[6]; QVector2D texCoords[6]; KisOpenGLImageTexturesSP openGLImageTextures; QOpenGLShaderProgram *displayShader{0}; int displayUniformLocationModelViewProjection; int displayUniformLocationTextureMatrix; int displayUniformLocationViewPortScale; int displayUniformLocationTexelSize; int displayUniformLocationTexture0; int displayUniformLocationTexture1; int displayUniformLocationFixedLodLevel; QOpenGLShaderProgram *checkerShader{0}; GLfloat checkSizeScale; bool scrollCheckers; int checkerUniformLocationModelViewProjection; int checkerUniformLocationTextureMatrix; QOpenGLShaderProgram *cursorShader{0}; int cursorShaderModelViewProjectionUniform; KisDisplayFilter* displayFilter; - KisTextureTile::FilterMode filterMode; + KisOpenGL::FilterMode filterMode; GLsync glSyncObject{0}; bool wrapAroundMode{false}; int xToColWithWrapCompensation(int x, const QRect &imageRect) { int firstImageColumn = openGLImageTextures->xToCol(imageRect.left()); int lastImageColumn = openGLImageTextures->xToCol(imageRect.right()); int colsPerImage = lastImageColumn - firstImageColumn + 1; int numWraps = floor(qreal(x) / imageRect.width()); int remainder = x - imageRect.width() * numWraps; return colsPerImage * numWraps + openGLImageTextures->xToCol(remainder); } int yToRowWithWrapCompensation(int y, const QRect &imageRect) { int firstImageRow = openGLImageTextures->yToRow(imageRect.top()); int lastImageRow = openGLImageTextures->yToRow(imageRect.bottom()); int rowsPerImage = lastImageRow - firstImageRow + 1; int numWraps = floor(qreal(y) / imageRect.height()); int remainder = y - imageRect.height() * numWraps; return rowsPerImage * numWraps + openGLImageTextures->yToRow(remainder); } }; KisOpenGLCanvas2::KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter) : QOpenGLWidget(parent) , KisCanvasWidgetBase(canvas, coordinatesConverter) , d(new Private()) { QSurfaceFormat format; format.setDepthBufferSize(24); setFormat(format); KisConfig cfg; cfg.writeEntry("canvasState", "OPENGL_STARTED"); d->openGLImageTextures = KisOpenGLImageTextures::getImageTextures(image, colorConverter->monitorProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); setAcceptDrops(true); setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_NoSystemBackground); setAttribute(Qt::WA_AcceptTouchEvents); setAutoFillBackground(false); setAttribute(Qt::WA_InputMethodEnabled, true); setAttribute(Qt::WA_DontCreateNativeAncestors, true); setDisplayFilterImpl(colorConverter->displayFilter(), true); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); slotConfigChanged(); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); } KisOpenGLCanvas2::~KisOpenGLCanvas2() { delete d; } bool KisOpenGLCanvas2::needsFpsDebugging() const { return KisOpenglCanvasDebugger::instance()->showFpsOnCanvas(); } void KisOpenGLCanvas2::setDisplayFilter(KisDisplayFilter* displayFilter) { setDisplayFilterImpl(displayFilter, false); } void KisOpenGLCanvas2::setDisplayFilterImpl(KisDisplayFilter* displayFilter, bool initializing) { bool needsInternalColorManagement = !displayFilter || displayFilter->useInternalColorManagement(); bool needsFullRefresh = d->openGLImageTextures-> setInternalColorManagementActive(needsInternalColorManagement); d->displayFilter = displayFilter; if (d->canvasInitialized) { d->canvasInitialized = false; initializeDisplayShader(); initializeCheckerShader(); d->canvasInitialized = true; } if (!initializing && needsFullRefresh) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } else if (!initializing) { canvas()->updateCanvas(); } } void KisOpenGLCanvas2::setWrapAroundViewingMode(bool value) { d->wrapAroundMode = value; update(); } void KisOpenGLCanvas2::initializeGL() { // KisConfig cfg; // if (cfg.disableVSync()) { // if (!VSyncWorkaround::tryDisableVSync(this)) { // warnUI; // warnUI << "WARNING: We didn't manage to switch off VSync on your graphics adapter."; // warnUI << "WARNING: It means either your hardware or driver doesn't support it,"; // warnUI << "WARNING: or we just don't know about this hardware. Please report us a bug"; // warnUI << "WARNING: with the output of \'glxinfo\' for your card."; // warnUI; // warnUI << "WARNING: Trying to workaround it by disabling Double Buffering."; // warnUI << "WARNING: You may see some flickering when painting with some tools. It doesn't"; // warnUI << "WARNING: affect the quality of the final image, though."; // warnUI; // if (cfg.disableDoubleBuffering() && QOpenGLContext::currentContext()->format().swapBehavior() == QSurfaceFormat::DoubleBuffer) { // errUI << "CRITICAL: Failed to disable Double Buffering. Lines may look \"bended\" on your image."; // errUI << "CRITICAL: Your graphics card or driver does not fully support Krita's OpenGL canvas."; // errUI << "CRITICAL: For an optimal experience, please disable OpenGL"; // errUI; // } // } // } KisConfig cfg; dbgUI << "OpenGL: Preparing to initialize OpenGL for KisCanvas"; int glVersion = KisOpenGL::initializeContext(context()); dbgUI << "OpenGL: Version found" << glVersion; initializeOpenGLFunctions(); VSyncWorkaround::tryDisableVSync(context()); d->openGLImageTextures->initGL(context()->functions()); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); initializeCheckerShader(); initializeDisplayShader(); ptr_glLogicOp = (kis_glLogicOp)(context()->getProcAddress("glLogicOp")); Sync::init(context()); d->canvasInitialized = true; } void KisOpenGLCanvas2::resizeGL(int width, int height) { coordinatesConverter()->setCanvasWidgetSize(QSize(width, height)); paintGL(); } void KisOpenGLCanvas2::paintGL() { if (!OPENGL_SUCCESS) { KisConfig cfg; cfg.writeEntry("canvasState", "OPENGL_PAINT_STARTED"); } KisOpenglCanvasDebugger::instance()->nofityPaintRequested(); QPainter gc(this); gc.beginNativePainting(); renderCanvasGL(); if (d->glSyncObject) { Sync::deleteSync(d->glSyncObject); } d->glSyncObject = Sync::getSync(); gc.endNativePainting(); renderDecorations(&gc); gc.end(); if (!OPENGL_SUCCESS) { KisConfig cfg; cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); OPENGL_SUCCESS = true; } } QOpenGLShaderProgram *KisOpenGLCanvas2::getCursorShader() { if (d->cursorShader == 0) { d->cursorShader = new QOpenGLShaderProgram(); d->cursorShader->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/cursor.vert"); d->cursorShader->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/cursor.frag"); d->cursorShader->bindAttributeLocation("a_vertexPosition", PROGRAM_VERTEX_ATTRIBUTE); if (! d->cursorShader->link()) { dbgUI << "OpenGL error" << glGetError(); qFatal("Failed linking cursor shader"); } Q_ASSERT(d->cursorShader->isLinked()); d->cursorShaderModelViewProjectionUniform = d->cursorShader->uniformLocation("modelViewProjection"); } return d->cursorShader; } void KisOpenGLCanvas2::paintToolOutline(const QPainterPath &path) { QOpenGLShaderProgram *cursorShader = getCursorShader(); cursorShader->bind(); // setup the mvp transformation KisCoordinatesConverter *converter = coordinatesConverter(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(converter->flakeToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; cursorShader->setUniformValue(d->cursorShaderModelViewProjectionUniform, modelMatrix); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); // XXX: glLogicOp not in ES 2.0 -- it would be better to use another method. // It is defined in 3.1 core profile onward. glEnable(GL_COLOR_LOGIC_OP); if (ptr_glLogicOp) { ptr_glLogicOp(GL_XOR); } // setup the array of vertices QVector vertices; QList subPathPolygons = path.toSubpathPolygons(); for (int i=0; ienableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); cursorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices.constData()); glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); vertices.clear(); } glDisable(GL_COLOR_LOGIC_OP); cursorShader->release(); } bool KisOpenGLCanvas2::isBusy() const { const bool isBusyStatus = Sync::syncStatus(d->glSyncObject) == Sync::Unsignaled; KisOpenglCanvasDebugger::instance()->nofitySyncStatus(isBusyStatus); return isBusyStatus; } inline void rectToVertices(QVector3D* vertices, const QRectF &rc) { vertices[0] = QVector3D(rc.left(), rc.bottom(), 0.f); vertices[1] = QVector3D(rc.left(), rc.top(), 0.f); vertices[2] = QVector3D(rc.right(), rc.bottom(), 0.f); vertices[3] = QVector3D(rc.left(), rc.top(), 0.f); vertices[4] = QVector3D(rc.right(), rc.top(), 0.f); vertices[5] = QVector3D(rc.right(), rc.bottom(), 0.f); } inline void rectToTexCoords(QVector2D* texCoords, const QRectF &rc) { texCoords[0] = QVector2D(rc.left(), rc.bottom()); texCoords[1] = QVector2D(rc.left(), rc.top()); texCoords[2] = QVector2D(rc.right(), rc.bottom()); texCoords[3] = QVector2D(rc.left(), rc.top()); texCoords[4] = QVector2D(rc.right(), rc.top()); texCoords[5] = QVector2D(rc.right(), rc.bottom()); } void KisOpenGLCanvas2::drawCheckers() { if (!d->checkerShader) { return; } KisCoordinatesConverter *converter = coordinatesConverter(); QTransform textureTransform; QTransform modelTransform; QRectF textureRect; QRectF modelRect; QRectF viewportRect = !d->wrapAroundMode ? converter->imageRectInViewportPixels() : converter->widgetToViewport(this->rect()); converter->getOpenGLCheckersInfo(viewportRect, &textureTransform, &modelTransform, &textureRect, &modelRect, d->scrollCheckers); textureTransform *= QTransform::fromScale(d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE, d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE); d->checkerShader->bind(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(modelTransform); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->checkerShader->setUniformValue(d->checkerUniformLocationModelViewProjection, modelMatrix); QMatrix4x4 textureMatrix(textureTransform); d->checkerShader->setUniformValue(d->checkerUniformLocationTextureMatrix, textureMatrix); //Setup the geometry for rendering rectToVertices(d->vertices, modelRect); d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); // render checkers glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, d->openGLImageTextures->checkerTexture()); glDrawArrays(GL_TRIANGLES, 0, 6); glBindTexture(GL_TEXTURE_2D, 0); d->checkerShader->release(); } void KisOpenGLCanvas2::drawImage() { 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(coordinatesConverter()->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->displayShader->setUniformValue(d->displayUniformLocationModelViewProjection, modelMatrix); QMatrix4x4 textureMatrix; textureMatrix.setToIdentity(); d->displayShader->setUniformValue(d->displayUniformLocationTextureMatrix, 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->displayUniformLocationViewPortScale, (GLfloat) scaleX); d->displayShader->setUniformValue(d->displayUniformLocationTexelSize, (GLfloat) d->openGLImageTextures->texelSize()); QRect ir = d->openGLImageTextures->storedImageBounds(); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { // if we don't want to paint wrapping images, just limit the // processing area, and the code will handle all the rest 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); 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()); int imageColumns = maxColumn - minColumn + 1; int imageRows = maxRow - minRow + 1; for (int col = firstColumn; col <= lastColumn; col++) { for (int row = firstRow; row <= lastRow; row++) { int effectiveCol = col; int effectiveRow = row; QPointF tileWrappingTranslation; if (effectiveCol > maxColumn || effectiveCol < minColumn) { int translationStep = floor(qreal(col) / imageColumns); int originCol = translationStep * imageColumns; effectiveCol = col - originCol; tileWrappingTranslation.rx() = translationStep * ir.width(); } if (effectiveRow > maxRow || effectiveRow < minRow) { int translationStep = floor(qreal(row) / imageRows); int originRow = translationStep * imageRows; effectiveRow = row - originRow; tileWrappingTranslation.ry() = translationStep * ir.height(); } KisTextureTile *tile = d->openGLImageTextures->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 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 (d->displayFilter) { glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_3D, d->displayFilter->lutTexture()); d->displayShader->setUniformValue(d->displayUniformLocationTexture1, 1); } int currentLodPlane = tile->currentLodPlane(); if (d->displayUniformLocationFixedLodLevel >= 0) { d->displayShader->setUniformValue(d->displayUniformLocationFixedLodLevel, (GLfloat) currentLodPlane); } glActiveTexture(GL_TEXTURE0); tile->bindToActiveTexture(); if (currentLodPlane) { 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 KisTextureTile::NearestFilterMode: + case KisOpenGL::NearestFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); break; - case KisTextureTile::BilinearFilterMode: + case KisOpenGL::BilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); break; - case KisTextureTile::TrilinearFilterMode: + case KisOpenGL::TrilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); break; - case KisTextureTile::HighQualityFiltering: + 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; } } glDrawArrays(GL_TRIANGLES, 0, 6); } } glBindTexture(GL_TEXTURE_2D, 0); d->displayShader->release(); } void KisOpenGLCanvas2::reportShaderLinkFailedAndExit(bool result, const QString &context, const QString &log) { KisConfig cfg; if (cfg.useVerboseOpenGLDebugOutput()) { dbgUI << "GL-log:" << context << log; } if (result) return; QMessageBox::critical(this, i18nc("@title:window", "Krita"), QString(i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n%2\n\n Krita will disable OpenGL and close now.")).arg(context).arg(log), QMessageBox::Close); cfg.setUseOpenGL(false); cfg.setCanvasState("OPENGL_FAILED"); } void KisOpenGLCanvas2::initializeCheckerShader() { if (d->canvasInitialized) return; delete d->checkerShader; d->checkerShader = new QOpenGLShaderProgram(); bool result; result = d->checkerShader->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/matrix_transform.vert"); reportShaderLinkFailedAndExit(result, "Checker vertex shader", d->checkerShader->log()); result = d->checkerShader->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/simple_texture.frag"); reportShaderLinkFailedAndExit(result, "Checker fragment shader", d->checkerShader->log()); d->checkerShader->bindAttributeLocation("a_vertexPosition", PROGRAM_VERTEX_ATTRIBUTE); d->checkerShader->bindAttributeLocation("a_textureCoordinate", PROGRAM_TEXCOORD_ATTRIBUTE); result = d->checkerShader->link(); reportShaderLinkFailedAndExit(result, "Checker shader (link)", d->checkerShader->log()); Q_ASSERT(d->checkerShader->isLinked()); d->checkerUniformLocationModelViewProjection = d->checkerShader->uniformLocation("modelViewProjection"); d->checkerUniformLocationTextureMatrix = d->checkerShader->uniformLocation("textureMatrix"); } QByteArray KisOpenGLCanvas2::buildFragmentShader() { QByteArray shaderText; bool haveDisplayFilter = d->displayFilter && !d->displayFilter->program().isEmpty(); - bool useHiQualityFiltering = d->filterMode == KisTextureTile::HighQualityFiltering; + bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering; QString filename = "highq_downscale.frag"; // FIXME is this necessary? shaderText.append("#version 130\n"); if (haveDisplayFilter) { shaderText.append("#define USE_OCIO\n"); shaderText.append(d->displayFilter->program().toLatin1()); } if (useHiQualityFiltering) { shaderText.append("#define HIGHQ_SCALING\n"); } shaderText.append("#define DIRECT_LOD_FETCH\n"); { QFile prefaceFile(":/" + filename); prefaceFile.open(QIODevice::ReadOnly); shaderText.append(prefaceFile.readAll()); } return shaderText; } void KisOpenGLCanvas2::initializeDisplayShader() { if (d->canvasInitialized) return; delete d->displayShader; d->displayShader = new QOpenGLShaderProgram(); bool result = d->displayShader->addShaderFromSourceCode(QOpenGLShader::Fragment, buildFragmentShader()); reportShaderLinkFailedAndExit(result, "Display fragment shader", d->displayShader->log()); result = d->displayShader->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/matrix_transform.vert"); reportShaderLinkFailedAndExit(result, "Display vertex shader", d->displayShader->log()); d->displayShader->bindAttributeLocation("a_vertexPosition", PROGRAM_VERTEX_ATTRIBUTE); d->displayShader->bindAttributeLocation("a_textureCoordinate", PROGRAM_TEXCOORD_ATTRIBUTE); result = d->displayShader->link(); reportShaderLinkFailedAndExit(result, "Display shader (link)", d->displayShader->log()); Q_ASSERT(d->displayShader->isLinked()); d->displayUniformLocationModelViewProjection = d->displayShader->uniformLocation("modelViewProjection"); d->displayUniformLocationTextureMatrix = d->displayShader->uniformLocation("textureMatrix"); d->displayUniformLocationTexture0 = d->displayShader->uniformLocation("texture0"); // ocio d->displayUniformLocationTexture1 = d->displayShader->uniformLocation("texture1"); // highq || lod d->displayUniformLocationViewPortScale = d->displayShader->uniformLocation("viewportScale"); // highq d->displayUniformLocationTexelSize = d->displayShader->uniformLocation("texelSize"); // TODO: The trilinear filtering mode is having issues when that is set in the application. It sometimes causes Krita to crash // I cannot tell where that is at in here... Scott P (11/8/2015) // lod d->displayUniformLocationFixedLodLevel = d->displayShader->uniformLocation("fixedLodLevel"); } void KisOpenGLCanvas2::slotConfigChanged() { KisConfig cfg; d->checkSizeScale = KisOpenGLImageTextures::BACKGROUND_TEXTURE_CHECK_SIZE / static_cast(cfg.checkSize()); d->scrollCheckers = cfg.scrollCheckers(); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); d->openGLImageTextures->updateConfig(cfg.useOpenGLTextureBuffer(), cfg.numMipmapLevels()); - d->filterMode = (KisTextureTile::FilterMode) cfg.openGLFilteringMode(); + d->filterMode = (KisOpenGL::FilterMode) cfg.openGLFilteringMode(); notifyConfigChanged(); } QVariant KisOpenGLCanvas2::inputMethodQuery(Qt::InputMethodQuery query) const { return processInputMethodQuery(query); } void KisOpenGLCanvas2::inputMethodEvent(QInputMethodEvent *event) { processInputMethodEvent(event); } 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); drawCheckers(); drawImage(); } void KisOpenGLCanvas2::renderDecorations(QPainter *painter) { QRect boundingRect = coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect(); drawDecorations(*painter, boundingRect); } void KisOpenGLCanvas2::setDisplayProfile(KisDisplayColorConverter *colorConverter) { d->openGLImageTextures->setMonitorProfile(colorConverter->monitorProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); } void KisOpenGLCanvas2::channelSelectionChanged(const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); } void KisOpenGLCanvas2::finishResizingImage(qint32 w, qint32 h) { if (d->canvasInitialized) { d->openGLImageTextures->slotImageSizeChanged(w, h); } } KisUpdateInfoSP KisOpenGLCanvas2::startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); return d->openGLImageTextures->updateCache(rc); } QRect KisOpenGLCanvas2::updateCanvasProjection(KisUpdateInfoSP info) { // See KisQPainterCanvas::updateCanvasProjection for more info bool isOpenGLUpdateInfo = dynamic_cast(info.data()); if (isOpenGLUpdateInfo) { d->openGLImageTextures->recalculateCache(info); } return QRect(); // FIXME: Implement dirty rect for OpenGL } bool KisOpenGLCanvas2::callFocusNextPrevChild(bool next) { return focusNextPrevChild(next); } KisOpenGLImageTexturesSP KisOpenGLCanvas2::openGLImageTextures() const { return d->openGLImageTextures; } #endif // HAVE_OPENGL diff --git a/libs/ui/opengl/kis_opengl_image_textures.cpp b/libs/ui/opengl/kis_opengl_image_textures.cpp index 5a6118c86e..f2ae7d7c62 100644 --- a/libs/ui/opengl/kis_opengl_image_textures.cpp +++ b/libs/ui/opengl/kis_opengl_image_textures.cpp @@ -1,610 +1,610 @@ /* * Copyright (c) 2005-2007 Adrian Page * * 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 "opengl/kis_opengl_image_textures.h" #ifdef HAVE_OPENGL #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_config.h" #include "KisPart.h" #ifdef HAVE_OPENEXR #include #endif #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif #ifndef GL_BGRA #define GL_BGRA 0x80E1 #endif KisOpenGLImageTextures::ImageTexturesMap KisOpenGLImageTextures::imageTexturesMap; KisOpenGLImageTextures::KisOpenGLImageTextures() : m_image(0) , m_monitorProfile(0) , m_tilesDestinationColorSpace(0) , m_internalColorManagementActive(true) , m_checkerTexture(0) , m_glFuncs(0) , m_allChannelsSelected(true) , m_useOcio(false) , m_initialized(false) { KisConfig cfg; m_renderingIntent = (KoColorConversionTransformation::Intent)cfg.monitorRenderIntent(); m_conversionFlags = KoColorConversionTransformation::HighQuality; if (cfg.useBlackPointCompensation()) m_conversionFlags |= KoColorConversionTransformation::BlackpointCompensation; if (!cfg.allowLCMSOptimization()) m_conversionFlags |= KoColorConversionTransformation::NoOptimization; m_useOcio = cfg.useOcio(); } KisOpenGLImageTextures::KisOpenGLImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) : m_image(image) , m_monitorProfile(monitorProfile) , m_renderingIntent(renderingIntent) , m_conversionFlags(conversionFlags) , m_tilesDestinationColorSpace(0) , m_internalColorManagementActive(true) , m_checkerTexture(0) , m_glFuncs(0) , m_allChannelsSelected(true) , m_useOcio(false) , m_initialized(false) { Q_ASSERT(renderingIntent < 4); } void KisOpenGLImageTextures::initGL(QOpenGLFunctions *f) { if (f) { m_glFuncs = f; } else { errUI << "Tried to create OpenGLImageTextures with uninitialized QOpenGLFunctions"; } getTextureSize(&m_texturesInfo); m_glFuncs->glGenTextures(1, &m_checkerTexture); createImageTextureTiles(); KisOpenGLUpdateInfoSP info = updateCache(m_image->bounds()); recalculateCache(info); } KisOpenGLImageTextures::~KisOpenGLImageTextures() { ImageTexturesMap::iterator it = imageTexturesMap.find(m_image); if (it != imageTexturesMap.end()) { KisOpenGLImageTextures *textures = it.value(); if (textures == this) { dbgUI << "Removing shared image context from map"; imageTexturesMap.erase(it); } } destroyImageTextureTiles(); m_glFuncs->glDeleteTextures(1, &m_checkerTexture); } KisImageSP KisOpenGLImageTextures::image() const { return m_image; } bool KisOpenGLImageTextures::imageCanShareTextures() { KisConfig cfg; if (cfg.useOcio()) return false; if (KisPart::instance()->mainwindowCount() == 1) return true; if (qApp->desktop()->screenCount() == 1) return true; for (int i = 1; i < qApp->desktop()->screenCount(); i++) { if (cfg.displayProfile(i) != cfg.displayProfile(i - 1)) { return false; } } return true; } KisOpenGLImageTexturesSP KisOpenGLImageTextures::getImageTextures(KisImageWSP image, const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (imageCanShareTextures()) { ImageTexturesMap::iterator it = imageTexturesMap.find(image); if (it != imageTexturesMap.end()) { KisOpenGLImageTexturesSP textures = it.value(); textures->setMonitorProfile(monitorProfile, renderingIntent, conversionFlags); return textures; } else { KisOpenGLImageTextures *imageTextures = new KisOpenGLImageTextures(image, monitorProfile, renderingIntent, conversionFlags); imageTexturesMap[image] = imageTextures; dbgUI << "Added shareable textures to map"; return imageTextures; } } else { return new KisOpenGLImageTextures(image, monitorProfile, renderingIntent, conversionFlags); } } QRect KisOpenGLImageTextures::calculateTileRect(int col, int row) const { return m_image->bounds() & QRect(col * m_texturesInfo.effectiveWidth, row * m_texturesInfo.effectiveHeight, m_texturesInfo.effectiveWidth, m_texturesInfo.effectiveHeight); } void KisOpenGLImageTextures::createImageTextureTiles() { KisConfig cfg; destroyImageTextureTiles(); updateTextureFormat(); m_storedImageBounds = m_image->bounds(); const int lastCol = xToCol(m_image->width()); const int lastRow = yToRow(m_image->height()); m_numCols = lastCol + 1; // Default color is transparent black const int pixelSize = m_tilesDestinationColorSpace->pixelSize(); QByteArray emptyTileData((m_texturesInfo.width) * (m_texturesInfo.height) * pixelSize, 0); KisConfig config; - KisTextureTile::FilterMode mode = (KisTextureTile::FilterMode)config.openGLFilteringMode(); + KisOpenGL::FilterMode mode = (KisOpenGL::FilterMode)config.openGLFilteringMode(); QOpenGLContext *ctx = QOpenGLContext::currentContext(); if (ctx) { QOpenGLFunctions *f = ctx->functions(); m_initialized = true; dbgUI << "OpenGL: creating texture tiles of size" << m_texturesInfo.height << "x" << m_texturesInfo.width; m_textureTiles.reserve((lastRow+1)*m_numCols); for (int row = 0; row <= lastRow; row++) { for (int col = 0; col <= lastCol; col++) { QRect tileRect = calculateTileRect(col, row); KisTextureTile *tile = new KisTextureTile(tileRect, &m_texturesInfo, emptyTileData, mode, cfg.useOpenGLTextureBuffer(), cfg.numMipmapLevels(), f); m_textureTiles.append(tile); } } } else { dbgUI << "Tried to init texture tiles without a current OpenGL Context."; } } void KisOpenGLImageTextures::destroyImageTextureTiles() { if(m_textureTiles.isEmpty()) return; Q_FOREACH (KisTextureTile *tile, m_textureTiles) { delete tile; } m_textureTiles.clear(); m_storedImageBounds = QRect(); } KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCache(const QRect& rect) { return updateCacheImpl(rect, true); } KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCacheNoConversion(const QRect& rect) { return updateCacheImpl(rect, false); } KisOpenGLUpdateInfoSP KisOpenGLImageTextures::updateCacheImpl(const QRect& rect, bool convertColorSpace) { const KoColorSpace *dstCS = m_tilesDestinationColorSpace; ConversionOptions options; if (convertColorSpace) { options = ConversionOptions(dstCS, m_renderingIntent, m_conversionFlags); } KisOpenGLUpdateInfoSP info = new KisOpenGLUpdateInfo(options); QRect updateRect = rect & m_image->bounds(); if (updateRect.isEmpty() || !(m_initialized)) return info; /** * Why the rect is artificial? That's easy! * It does not represent any real piece of the image. It is * intentionally stretched to get through the overlappping * stripes of neutrality and poke neighbouring tiles. * Thanks to the rect we get the coordinates of all the tiles * involved into update process */ QRect artificialRect = stretchRect(updateRect, m_texturesInfo.border); artificialRect &= m_image->bounds(); int firstColumn = xToCol(artificialRect.left()); int lastColumn = xToCol(artificialRect.right()); int firstRow = yToRow(artificialRect.top()); int lastRow = yToRow(artificialRect.bottom()); QBitArray channelFlags; // empty by default if (m_channelFlags.size() != m_image->projection()->colorSpace()->channels().size()) { setChannelFlags(QBitArray()); } if (!m_useOcio) { // Ocio does its own channel flipping if (!m_allChannelsSelected) { // and we do it only if necessary channelFlags = m_channelFlags; } } qint32 numItems = (lastColumn - firstColumn + 1) * (lastRow - firstRow + 1); info->tileList.reserve(numItems); const QRect bounds = m_image->bounds(); const int levelOfDetail = m_image->currentLevelOfDetail(); QRect alignedUpdateRect = updateRect; QRect alignedBounds = bounds; if (levelOfDetail) { alignedUpdateRect = KisLodTransform::alignedRect(alignedUpdateRect, levelOfDetail); alignedBounds = KisLodTransform::alignedRect(alignedBounds, levelOfDetail); } for (int col = firstColumn; col <= lastColumn; col++) { for (int row = firstRow; row <= lastRow; row++) { const QRect tileRect = calculateTileRect(col, row); const QRect tileTextureRect = stretchRect(tileRect, m_texturesInfo.border); QRect alignedTileTextureRect = levelOfDetail ? KisLodTransform::alignedRect(tileTextureRect, levelOfDetail) : tileTextureRect; KisTextureTileUpdateInfoSP tileInfo( new KisTextureTileUpdateInfo(col, row, alignedTileTextureRect, alignedUpdateRect, alignedBounds, levelOfDetail)); // Don't update empty tiles if (tileInfo->valid()) { tileInfo->retrieveData(m_image, channelFlags, m_onlyOneChannelSelected, m_selectedChannelIndex); if (convertColorSpace) { tileInfo->convertTo(dstCS, m_renderingIntent, m_conversionFlags); } info->tileList.append(tileInfo); } else { dbgUI << "Trying to create an empty tileinfo record" << col << row << tileTextureRect << updateRect << m_image->bounds(); } } } info->assignDirtyImageRect(rect); return info; } void KisOpenGLImageTextures::recalculateCache(KisUpdateInfoSP info) { if (!m_initialized) { dbgUI << "OpenGL: Tried to edit image texture cache before it was initialized."; return; } KisOpenGLUpdateInfoSP glInfo = dynamic_cast(info.data()); if(!glInfo) return; KisTextureTileUpdateInfoSP tileInfo; Q_FOREACH (tileInfo, glInfo->tileList) { KisTextureTile *tile = getTextureTileCR(tileInfo->tileCol(), tileInfo->tileRow()); KIS_ASSERT_RECOVER_RETURN(tile); tile->update(*tileInfo); } } void KisOpenGLImageTextures::generateCheckerTexture(const QImage &checkImage) { QOpenGLContext *ctx = QOpenGLContext::currentContext(); if (ctx) { QOpenGLFunctions *f = ctx->functions(); dbgUI << "Attaching checker texture" << checkerTexture(); f->glBindTexture(GL_TEXTURE_2D, checkerTexture()); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1); QImage img = checkImage; if (checkImage.width() != BACKGROUND_TEXTURE_SIZE || checkImage.height() != BACKGROUND_TEXTURE_SIZE) { img = checkImage.scaled(BACKGROUND_TEXTURE_SIZE, BACKGROUND_TEXTURE_SIZE); } f->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, BACKGROUND_TEXTURE_SIZE, BACKGROUND_TEXTURE_SIZE, 0, GL_BGRA, GL_UNSIGNED_BYTE, img.constBits()); } else { dbgUI << "OpenGL: Tried to generate checker texture before OpenGL was initialized."; } } GLuint KisOpenGLImageTextures::checkerTexture() { if (m_glFuncs) { if (m_checkerTexture == 0) { m_glFuncs->glGenTextures(1, &m_checkerTexture); } return m_checkerTexture; } else { dbgUI << "Tried to access checker texture before OpenGL was initialized"; return 0; } } void KisOpenGLImageTextures::updateConfig(bool useBuffer, int NumMipmapLevels) { if(m_textureTiles.isEmpty()) return; Q_FOREACH (KisTextureTile *tile, m_textureTiles) { tile->setUseBuffer(useBuffer); tile->setNumMipmapLevels(NumMipmapLevels); } } void KisOpenGLImageTextures::slotImageSizeChanged(qint32 /*w*/, qint32 /*h*/) { createImageTextureTiles(); } void KisOpenGLImageTextures::setMonitorProfile(const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { //dbgUI << "Setting monitor profile to" << monitorProfile->name() << renderingIntent << conversionFlags; m_monitorProfile = monitorProfile; m_renderingIntent = renderingIntent; m_conversionFlags = conversionFlags; createImageTextureTiles(); } void KisOpenGLImageTextures::setChannelFlags(const QBitArray &channelFlags) { m_channelFlags = channelFlags; int selectedChannels = 0; const KoColorSpace *projectionCs = m_image->projection()->colorSpace(); QList channelInfo = projectionCs->channels(); if (m_channelFlags.size() != channelInfo.size()) { m_channelFlags = QBitArray(); } for (int i = 0; i < m_channelFlags.size(); ++i) { if (m_channelFlags.testBit(i) && channelInfo[i]->channelType() == KoChannelInfo::COLOR) { selectedChannels++; m_selectedChannelIndex = i; } } m_allChannelsSelected = (selectedChannels == m_channelFlags.size()); m_onlyOneChannelSelected = (selectedChannels == 1); } void KisOpenGLImageTextures::getTextureSize(KisGLTexturesInfo *texturesInfo) { KisConfig cfg; const GLint preferredTextureSize = cfg.openGLTextureSize(); GLint maxTextureSize; if (m_glFuncs) { m_glFuncs->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); } else { dbgUI << "OpenGL: Tried to read texture size before OpenGL was initialized."; maxTextureSize = GL_MAX_TEXTURE_SIZE; } texturesInfo->width = qMin(preferredTextureSize, maxTextureSize); texturesInfo->height = qMin(preferredTextureSize, maxTextureSize); texturesInfo->border = cfg.textureOverlapBorder(); texturesInfo->effectiveWidth = texturesInfo->width - 2 * texturesInfo->border; texturesInfo->effectiveHeight = texturesInfo->height - 2 * texturesInfo->border; } bool KisOpenGLImageTextures::internalColorManagementActive() const { return m_internalColorManagementActive; } bool KisOpenGLImageTextures::setInternalColorManagementActive(bool value) { bool needsFinalRegeneration = m_internalColorManagementActive != value; if (needsFinalRegeneration) { m_internalColorManagementActive = value; createImageTextureTiles(); // at this point the value of m_internalColorManagementActive might // have been forcely reverted to 'false' in case of some problems } return needsFinalRegeneration; } void KisOpenGLImageTextures::updateTextureFormat() { QOpenGLContext *ctx = QOpenGLContext::currentContext(); if (!(m_image && ctx)) return; m_texturesInfo.internalFormat = GL_RGBA8; m_texturesInfo.type = GL_UNSIGNED_BYTE; m_texturesInfo.format = GL_BGRA; KoID colorModelId = m_image->colorSpace()->colorModelId(); KoID colorDepthId = m_image->colorSpace()->colorDepthId(); KoID destinationColorModelId = RGBAColorModelID; KoID destinationColorDepthId = Integer8BitsColorDepthID; dbgUI << "Choosing texture format:"; if (colorModelId == RGBAColorModelID) { if (colorDepthId == Float16BitsColorDepthID) { if (ctx->hasExtension("GL_ARB_texture_float")) { m_texturesInfo.internalFormat = GL_RGBA16F_ARB; dbgUI << "Using ARB half"; } else if (ctx->hasExtension("GL_ATI_texture_float")) { m_texturesInfo.internalFormat = GL_RGBA_FLOAT16_ATI; dbgUI << "Using ATI half"; } bool haveBuiltInOpenExr = false; #ifdef HAVE_OPENEXR haveBuiltInOpenExr = true; #endif if (haveBuiltInOpenExr && ctx->hasExtension("GL_ARB_half_float_pixel")) { m_texturesInfo.type = GL_HALF_FLOAT_ARB; destinationColorDepthId = Float16BitsColorDepthID; dbgUI << "Pixel type half"; } else { m_texturesInfo.type = GL_FLOAT; destinationColorDepthId = Float32BitsColorDepthID; dbgUI << "Pixel type float"; } m_texturesInfo.format = GL_RGBA; } else if (colorDepthId == Float32BitsColorDepthID) { if (ctx->hasExtension("GL_ARB_texture_float")) { m_texturesInfo.internalFormat = GL_RGBA32F_ARB; dbgUI << "Using ARB float"; } else if (ctx->hasExtension("GL_ATI_texture_float")) { m_texturesInfo.internalFormat = GL_RGBA_FLOAT32_ATI; dbgUI << "Using ATI float"; } m_texturesInfo.type = GL_FLOAT; m_texturesInfo.format = GL_RGBA; destinationColorDepthId = Float32BitsColorDepthID; } else if (colorDepthId == Integer16BitsColorDepthID) { m_texturesInfo.internalFormat = GL_RGBA16; m_texturesInfo.type = GL_UNSIGNED_SHORT; m_texturesInfo.format = GL_BGRA; destinationColorDepthId = Integer16BitsColorDepthID; dbgUI << "Using 16 bits rgba"; } } else { // We will convert the colorspace to 16 bits rgba, instead of 8 bits if (colorDepthId == Integer16BitsColorDepthID) { m_texturesInfo.internalFormat = GL_RGBA16; m_texturesInfo.type = GL_UNSIGNED_SHORT; m_texturesInfo.format = GL_BGRA; destinationColorDepthId = Integer16BitsColorDepthID; dbgUI << "Using conversion to 16 bits rgba"; } } if (!m_internalColorManagementActive && colorModelId != destinationColorModelId) { KisConfig cfg; KisConfig::OcioColorManagementMode cm = cfg.ocioColorManagementMode(); if (cm != KisConfig::INTERNAL) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You enabled OpenColorIO based color management, but your image is not an RGB image.\n" "OpenColorIO-based color management only works with RGB images.\n" "Please check the settings in the LUT docker.\n" "OpenColorIO will now be deactivated.")); } warnUI << "WARNING: Internal color management was forcely enabled"; warnUI << "Color Management Mode: " << cm; warnUI << ppVar(m_image->colorSpace()); warnUI << ppVar(destinationColorModelId); warnUI << ppVar(destinationColorDepthId); cfg.setOcioColorManagementMode(KisConfig::INTERNAL); m_internalColorManagementActive = true; } const KoColorProfile *profile = m_internalColorManagementActive || colorModelId != destinationColorModelId ? m_monitorProfile : m_image->colorSpace()->profile(); /** * TODO: add an optimization so that the tile->convertTo() method * would not be called when not needed (DK) */ m_tilesDestinationColorSpace = KoColorSpaceRegistry::instance()->colorSpace(destinationColorModelId.id(), destinationColorDepthId.id(), profile); } #endif // HAVE_OPENGL diff --git a/libs/ui/opengl/kis_texture_tile.cpp b/libs/ui/opengl/kis_texture_tile.cpp index 31613ecfe1..e18e8341bf 100644 --- a/libs/ui/opengl/kis_texture_tile.cpp +++ b/libs/ui/opengl/kis_texture_tile.cpp @@ -1,360 +1,357 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * 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. */ #define GL_GLEXT_PROTOTYPES #include "kis_texture_tile.h" #include "kis_texture_tile_update_info.h" #ifdef HAVE_OPENGL #include #if !defined(QT_OPENGL_ES) #include #endif #ifndef GL_BGRA #define GL_BGRA 0x814F #endif void KisTextureTile::setTextureParameters() { f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, m_numMipmapLevels); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, m_numMipmapLevels); f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1); } inline QRectF relativeRect(const QRect &br /* baseRect */, const QRect &cr /* childRect */, const KisGLTexturesInfo *texturesInfo) { const qreal x = qreal(cr.x() - br.x()) / texturesInfo->width; const qreal y = qreal(cr.y() - br.y()) / texturesInfo->height; const qreal w = qreal(cr.width()) / texturesInfo->width; const qreal h = qreal(cr.height()) / texturesInfo->height; return QRectF(x, y, w, h); } KisTextureTile::KisTextureTile(const QRect &imageRect, const KisGLTexturesInfo *texturesInfo, - const QByteArray &fillData, FilterMode filter, + const QByteArray &fillData, KisOpenGL::FilterMode filter, bool useBuffer, int numMipmapLevels, QOpenGLFunctions *fcn) : m_textureId(0) #ifdef USE_PIXEL_BUFFERS , m_glBuffer(0) #endif , m_tileRectInImagePixels(imageRect) , m_filter(filter) , m_texturesInfo(texturesInfo) , m_needsMipmapRegeneration(false) , m_currentLodPlane(0) , m_useBuffer(useBuffer) , m_numMipmapLevels(numMipmapLevels) , f(fcn) { const GLvoid *fd = fillData.constData(); m_textureRectInImagePixels = stretchRect(m_tileRectInImagePixels, texturesInfo->border); m_tileRectInTexturePixels = relativeRect(m_textureRectInImagePixels, m_tileRectInImagePixels, m_texturesInfo); f->glGenTextures(1, &m_textureId); f->glBindTexture(GL_TEXTURE_2D, m_textureId); setTextureParameters(); #ifdef USE_PIXEL_BUFFERS createTextureBuffer(fillData.constData(), fillData.size()); // we set fill data to 0 so the next glTexImage2D call uses our buffer fd = 0; #endif f->glTexImage2D(GL_TEXTURE_2D, 0, m_texturesInfo->internalFormat, m_texturesInfo->width, m_texturesInfo->height, 0, m_texturesInfo->format, m_texturesInfo->type, fd); #ifdef USE_PIXEL_BUFFERS if (m_useBuffer) { m_glBuffer->release(); } #endif setNeedsMipmapRegeneration(); } KisTextureTile::~KisTextureTile() { #ifdef USE_PIXEL_BUFFERS delete m_glBuffer; #endif f->glDeleteTextures(1, &m_textureId); } void KisTextureTile::bindToActiveTexture() { f->glBindTexture(GL_TEXTURE_2D, m_textureId); if (m_needsMipmapRegeneration) { f->glGenerateMipmap(GL_TEXTURE_2D); m_needsMipmapRegeneration = false; } } void KisTextureTile::setNeedsMipmapRegeneration() { - // TODO: when a switch for LoD is implemented, put it there to - // allow mipmapping in that case - - if (m_filter == TrilinearFilterMode || - m_filter == HighQualityFiltering) { + if (m_filter == KisOpenGL::TrilinearFilterMode || + m_filter == KisOpenGL::HighQualityFiltering) { m_needsMipmapRegeneration = true; } m_currentLodPlane = 0; } int KisTextureTile::currentLodPlane() const { return m_currentLodPlane; } void KisTextureTile::setCurrentLodPlane(int lod) { m_currentLodPlane = lod; m_needsMipmapRegeneration = false; } void KisTextureTile::update(const KisTextureTileUpdateInfo &updateInfo) { f->initializeOpenGLFunctions(); f->glBindTexture(GL_TEXTURE_2D, m_textureId); setTextureParameters(); const int patchLevelOfDetail = updateInfo.patchLevelOfDetail(); const QSize patchSize = updateInfo.realPatchSize(); const QPoint patchOffset = updateInfo.realPatchOffset(); const GLvoid *fd = updateInfo.data(); #ifdef USE_PIXEL_BUFFERS if (!m_glBuffer) { createTextureBuffer((const char*)updateInfo.data(), updateInfo.patchPixelsLength()); } #endif if (updateInfo.isEntireTileUpdated()) { #ifdef USE_PIXEL_BUFFERS if (m_useBuffer) { m_glBuffer->bind(); m_glBuffer->allocate(updateInfo.patchPixelsLength()); void *vid = m_glBuffer->map(QOpenGLBuffer::WriteOnly); memcpy(vid, fd, updateInfo.patchPixelsLength()); m_glBuffer->unmap(); // we set fill data to 0 so the next glTexImage2D call uses our buffer fd = 0; } #endif f->glTexImage2D(GL_TEXTURE_2D, patchLevelOfDetail, m_texturesInfo->internalFormat, patchSize.width(), patchSize.height(), 0, m_texturesInfo->format, m_texturesInfo->type, fd); #ifdef USE_PIXEL_BUFFERS if (m_useBuffer) { m_glBuffer->release(); } #endif } else { #ifdef USE_PIXEL_BUFFERS if (m_useBuffer) { m_glBuffer->bind(); quint32 size = patchSize.width() * patchSize.height() * updateInfo.pixelSize(); m_glBuffer->allocate(size); void *vid = m_glBuffer->map(QOpenGLBuffer::WriteOnly); memcpy(vid, fd, size); m_glBuffer->unmap(); // we set fill data to 0 so the next glTexImage2D call uses our buffer fd = 0; } #endif f->glTexSubImage2D(GL_TEXTURE_2D, patchLevelOfDetail, patchOffset.x(), patchOffset.y(), patchSize.width(), patchSize.height(), m_texturesInfo->format, m_texturesInfo->type, fd); #ifdef USE_PIXEL_BUFFERS if (m_useBuffer) { m_glBuffer->release(); } #endif } /** * On the boundaries of KisImage, there is a border-effect as well. * So we just repeat the bounding pixels of the image to make * bilinear interpolator happy. */ /** * WARN: The width of the stripes will be equal to the broder * width of the tiles. */ const int pixelSize = updateInfo.pixelSize(); const QSize tileSize = updateInfo.realTileSize(); if(updateInfo.isTopmost()) { int start = 0; int end = patchOffset.y() - 1; for (int i = start; i <= end; i++) { f->glTexSubImage2D(GL_TEXTURE_2D, patchLevelOfDetail, patchOffset.x(), i, patchSize.width(), 1, m_texturesInfo->format, m_texturesInfo->type, updateInfo.data()); } } if (updateInfo.isBottommost()) { int shift = patchSize.width() * (patchSize.height() - 1) * pixelSize; int start = patchOffset.y() + patchSize.height(); int end = tileSize.height() - 1; for (int i = start; i < end; i++) { f->glTexSubImage2D(GL_TEXTURE_2D, patchLevelOfDetail, patchOffset.x(), i, patchSize.width(), 1, m_texturesInfo->format, m_texturesInfo->type, updateInfo.data() + shift); } } if (updateInfo.isLeftmost()) { QByteArray columnBuffer(patchSize.height() * pixelSize, 0); quint8 *srcPtr = updateInfo.data(); quint8 *dstPtr = (quint8*) columnBuffer.data(); for(int i = 0; i < patchSize.height(); i++) { memcpy(dstPtr, srcPtr, pixelSize); srcPtr += patchSize.width() * pixelSize; dstPtr += pixelSize; } int start = 0; int end = patchOffset.x() - 1; for (int i = start; i <= end; i++) { f->glTexSubImage2D(GL_TEXTURE_2D, patchLevelOfDetail, i, patchOffset.y(), 1, patchSize.height(), m_texturesInfo->format, m_texturesInfo->type, columnBuffer.constData()); } } if (updateInfo.isRightmost()) { QByteArray columnBuffer(patchSize.height() * pixelSize, 0); quint8 *srcPtr = updateInfo.data() + (patchSize.width() - 1) * pixelSize; quint8 *dstPtr = (quint8*) columnBuffer.data(); for(int i = 0; i < patchSize.height(); i++) { memcpy(dstPtr, srcPtr, pixelSize); srcPtr += patchSize.width() * pixelSize; dstPtr += pixelSize; } int start = patchOffset.x() + patchSize.width(); int end = tileSize.width() - 1; for (int i = start; i <= end; i++) { f->glTexSubImage2D(GL_TEXTURE_2D, patchLevelOfDetail, i, patchOffset.y(), 1, patchSize.height(), m_texturesInfo->format, m_texturesInfo->type, columnBuffer.constData()); } } if (!patchLevelOfDetail) { setNeedsMipmapRegeneration(); } else { setCurrentLodPlane(patchLevelOfDetail); } } #ifdef USE_PIXEL_BUFFERS void KisTextureTile::createTextureBuffer(const char *data, int size) { if (m_useBuffer) { if (!m_glBuffer) { m_glBuffer = new QOpenGLBuffer(QOpenGLBuffer::PixelUnpackBuffer); m_glBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw); m_glBuffer->create(); m_glBuffer->bind(); m_glBuffer->allocate(size); } void *vid = m_glBuffer->map(QOpenGLBuffer::WriteOnly); memcpy(vid, data, size); m_glBuffer->unmap(); } else { delete m_glBuffer; m_glBuffer = 0; } } #endif #endif /* HAVE_OPENGL */ diff --git a/libs/ui/opengl/kis_texture_tile.h b/libs/ui/opengl/kis_texture_tile.h index d8a00f944a..b62a8521b7 100644 --- a/libs/ui/opengl/kis_texture_tile.h +++ b/libs/ui/opengl/kis_texture_tile.h @@ -1,131 +1,126 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * 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_TEXTURE_TILE_H_ #define KIS_TEXTURE_TILE_H_ #include // for HAVE_OPENGL #ifdef HAVE_OPENGL #include #include // no forward-declaration, used to get GL* primitive types defined #include +#include "kis_opengl.h" + #if !defined(QT_OPENGL_ES) #define USE_PIXEL_BUFFERS #endif class KisTextureTileUpdateInfo; class QOpenGLBuffer; struct KisGLTexturesInfo { // real width and height int width; int height; // width and height minus border padding? int effectiveWidth; int effectiveHeight; // size of the border padding int border; GLint internalFormat; GLint format; GLint type; }; inline QRect stretchRect(const QRect &rc, int delta) { return rc.adjusted(-delta, -delta, delta, delta); } class KisTextureTile { public: - enum FilterMode { - NearestFilterMode, // nearest - BilinearFilterMode, // linear, no mipmap - TrilinearFilterMode, // LINEAR_MIPMAP_LINEAR - HighQualityFiltering // Mipmaps + custom shader - }; - KisTextureTile(const QRect &imageRect, const KisGLTexturesInfo *texturesInfo, - const QByteArray &fillData, FilterMode mode, + const QByteArray &fillData, KisOpenGL::FilterMode mode, bool useBuffer, int numMipmapLevels, QOpenGLFunctions *f); ~KisTextureTile(); void setUseBuffer(bool useBuffer) { m_useBuffer = useBuffer; } void setNumMipmapLevels(int num) { m_numMipmapLevels = num; } void update(const KisTextureTileUpdateInfo &updateInfo); inline QRect tileRectInImagePixels() { return m_tileRectInImagePixels; } inline QRect textureRectInImagePixels() { return m_textureRectInImagePixels; } inline QRectF tileRectInTexturePixels() { return m_tileRectInTexturePixels; } void bindToActiveTexture(); int currentLodPlane() const; private: inline void setTextureParameters(); void setNeedsMipmapRegeneration(); void setCurrentLodPlane(int lod); GLuint m_textureId; #ifdef USE_PIXEL_BUFFERS void createTextureBuffer(const char*data, int size); QOpenGLBuffer *m_glBuffer; #endif QRect m_tileRectInImagePixels; QRectF m_tileRectInTexturePixels; QRect m_textureRectInImagePixels; - FilterMode m_filter; + KisOpenGL::FilterMode m_filter; const KisGLTexturesInfo *m_texturesInfo; bool m_needsMipmapRegeneration; int m_currentLodPlane; bool m_useBuffer; int m_numMipmapLevels; QOpenGLFunctions *f; Q_DISABLE_COPY(KisTextureTile) }; #endif /* HAVE_OPENGL */ #endif /* KIS_TEXTURE_TILE_H_ */