diff options
author | Liang Qi <liang.qi@qt.io> | 2020-02-13 09:14:09 +0100 |
---|---|---|
committer | Alexandru Croitor <alexandru.croitor@qt.io> | 2020-02-13 18:31:40 +0100 |
commit | 6b2535ea15cdbdb2355416b604f072fc13ff36b2 (patch) | |
tree | 4bf1560bab77c8b315850c5337ba31a0ea87b5f0 /src/gui | |
parent | 54c2cebabdda0280b8443c6947b6fee02445e138 (diff) | |
parent | 67491e2df5357706dbf88ddaf1f030ff095b4528 (diff) |
Merge remote-tracking branch 'origin/5.15' into dev
Conflicts:
examples/widgets/graphicsview/boxes/scene.h
src/corelib/Qt5CoreMacros.cmake
src/corelib/Qt6CoreMacros.cmake
src/network/ssl/qsslsocket.cpp
src/network/ssl/qsslsocket.h
src/platformsupport/fontdatabases/windows/qwindowsfontenginedirectwrite.cpp
src/testlib/CMakeLists.txt
src/testlib/.prev_CMakeLists.txt
tests/auto/corelib/tools/qscopeguard/tst_qscopeguard.cpp
Disabled building manual tests with CMake for now, because qmake
doesn't do it, and it confuses people.
Done-With: Alexandru Croitor <alexandru.croitor@qt.io>
Done-With: Volker Hilsheimer <volker.hilsheimer@qt.io>
Change-Id: I865ae347bd01f4e59f16d007b66d175a52f1f152
Diffstat (limited to 'src/gui')
39 files changed, 466 insertions, 169 deletions
diff --git a/src/gui/.prev_CMakeLists.txt b/src/gui/.prev_CMakeLists.txt index f8529ba650..933a8eae6d 100644 --- a/src/gui/.prev_CMakeLists.txt +++ b/src/gui/.prev_CMakeLists.txt @@ -456,7 +456,7 @@ if(NOT ANDROID) ) endif() -qt_extend_target(Gui CONDITION ANDROID AND TEST_architecture_arch STREQUAL arm64 +qt_extend_target(Gui CONDITION (NOT (NOT ANDROID)) AND (TEST_architecture_arch STREQUAL arm64 ORTEST_architecture_arch STREQUAL arm) SOURCES image/qimage_neon.cpp painting/qdrawhelper_neon.cpp painting/qdrawhelper_neon_p.h @@ -502,6 +502,8 @@ qt_extend_target(Gui CONDITION QT_FEATURE_system_textmarkdownreader AND QT_FEATU qt_extend_target(Gui CONDITION QT_FEATURE_textmarkdownreader AND NOT QT_FEATURE_system_textmarkdownreader SOURCES ../3rdparty/md4c/md4c.c ../3rdparty/md4c/md4c.h + DEFINES + MD4C_USE_UTF8 INCLUDE_DIRECTORIES ../3rdparty/md4c ) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index ab1b3884cb..3e93b1ddea 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -538,7 +538,7 @@ if(NOT ANDROID) ) endif() -qt_extend_target(Gui CONDITION ANDROID AND TEST_architecture_arch STREQUAL arm64 +qt_extend_target(Gui CONDITION ANDROID AND (TEST_architecture_arch STREQUAL arm64 OR TEST_architecture_arch STREQUAL arm) # special case SOURCES image/qimage_neon.cpp painting/qdrawhelper_neon.cpp painting/qdrawhelper_neon_p.h @@ -603,6 +603,8 @@ qt_extend_target(Gui CONDITION QT_FEATURE_system_textmarkdownreader AND QT_FEATU qt_extend_target(Gui CONDITION QT_FEATURE_textmarkdownreader AND NOT QT_FEATURE_system_textmarkdownreader SOURCES ../3rdparty/md4c/md4c.c ../3rdparty/md4c/md4c.h + DEFINES + MD4C_USE_UTF8 INCLUDE_DIRECTORIES ../3rdparty/md4c ) diff --git a/src/gui/accessible/qaccessible.h b/src/gui/accessible/qaccessible.h index 2220efd5cb..f7564a3076 100644 --- a/src/gui/accessible/qaccessible.h +++ b/src/gui/accessible/qaccessible.h @@ -280,7 +280,7 @@ public: HotkeyField = 0x00000032, Slider = 0x00000033, SpinBox = 0x00000034, - Canvas = 0x00000035, // Diagram for MSAA + Canvas = 0x00000035, // MSAA: ROLE_SYSTEM_DIAGRAM - The object represents a graphical image that is used to diagram data. Animation = 0x00000036, Equation = 0x00000037, ButtonDropDown = 0x00000038, // The object represents a button that expands a grid. @@ -302,7 +302,7 @@ public: Notification = 0x00000086, // IAccessible2 roles - // IA2_ROLE_CANVAS = 0x401, ### Qt 6 use this one instead of Canvas above + // IA2_ROLE_CANVAS = 0x401, // An object that can be drawn into and to manage events from the objects drawn into it // IA2_ROLE_CAPTION = 0x402, // IA2_ROLE_CHECK_MENU_ITEM = 0x403, ColorChooser = 0x404, diff --git a/src/gui/image/image.pri b/src/gui/image/image.pri index 760b737ec3..0c033609c4 100644 --- a/src/gui/image/image.pri +++ b/src/gui/image/image.pri @@ -92,7 +92,7 @@ qtConfig(png) { MIPS_DSPR2_ASM += image/qimage_mips_dspr2_asm.S } else { # see https://developer.android.com/ndk/guides/abis - arm64-v8a { + arm64-v8a | armeabi-v7a { SOURCES += image/qimage_neon.cpp } x86 | x86_64 { diff --git a/src/gui/image/qicon.cpp b/src/gui/image/qicon.cpp index 38285dd827..41fe649fc5 100644 --- a/src/gui/image/qicon.cpp +++ b/src/gui/image/qicon.cpp @@ -1527,7 +1527,7 @@ QDebug operator<<(QDebug dbg, const QIcon &i) \internal \since 5.6 Attempts to find a suitable @Nx file for the given \a targetDevicePixelRatio - Returns the the \a baseFileName if no such file was found. + Returns the \a baseFileName if no such file was found. Given base foo.png and a target dpr of 2.5, this function will look for foo@3x.png, then foo@2x, then fall back to foo.png if not found. diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp index 738aa29794..1319d9dffc 100644 --- a/src/gui/image/qimage.cpp +++ b/src/gui/image/qimage.cpp @@ -1587,7 +1587,9 @@ const uchar *QImage::scanLine(int i) const Returns a pointer to the pixel data at the scanline with index \a i. The first scanline is at index 0. - The scanline data is aligned on a 32-bit boundary. + The scanline data is as minimum 32-bit aligned. For 64-bit formats + it follows the native alignment of 64-bit integers (64-bit for most + platforms, but notably 32-bit on i386). Note that QImage uses \l{Implicit Data Sharing} {implicit data sharing}, but this function does \e not perform a deep copy of the diff --git a/src/gui/image/qimagereader.cpp b/src/gui/image/qimagereader.cpp index 6139cf99c9..3eb1e01863 100644 --- a/src/gui/image/qimagereader.cpp +++ b/src/gui/image/qimagereader.cpp @@ -1149,6 +1149,7 @@ bool QImageReader::autoTransform() const return false; } +#if QT_DEPRECATED_SINCE(5, 15) /*! \since 5.6 @@ -1181,6 +1182,7 @@ float QImageReader::gamma() const return d->handler->option(QImageIOHandler::Gamma).toFloat(); return 0.0; } +#endif /*! Returns \c true if an image can be read for the device (i.e., the diff --git a/src/gui/image/qimagereader.h b/src/gui/image/qimagereader.h index 4e9a08b6e6..7cd3b81a7d 100644 --- a/src/gui/image/qimagereader.h +++ b/src/gui/image/qimagereader.h @@ -117,8 +117,12 @@ public: void setAutoTransform(bool enabled); bool autoTransform() const; +#if QT_DEPRECATED_SINCE(5, 15) + QT_DEPRECATED_VERSION_X_5_15("Use QColorSpace instead") void setGamma(float gamma); + QT_DEPRECATED_VERSION_X_5_15("Use QColorSpace instead") float gamma() const; +#endif QByteArray subType() const; QList<QByteArray> supportedSubTypes() const; diff --git a/src/gui/image/qimagewriter.cpp b/src/gui/image/qimagewriter.cpp index 512da5c432..6e74b23f76 100644 --- a/src/gui/image/qimagewriter.cpp +++ b/src/gui/image/qimagewriter.cpp @@ -498,6 +498,7 @@ int QImageWriter::compression() const return d->compression; } +#if QT_DEPRECATED_SINCE(5, 15) /*! This is an image format specific function that sets the gamma level of the image to \a gamma. For image formats that do not @@ -522,6 +523,7 @@ float QImageWriter::gamma() const { return d->gamma; } +#endif /*! \since 5.4 diff --git a/src/gui/image/qimagewriter.h b/src/gui/image/qimagewriter.h index ef84a59b7c..5c8ed7e211 100644 --- a/src/gui/image/qimagewriter.h +++ b/src/gui/image/qimagewriter.h @@ -84,8 +84,12 @@ public: void setCompression(int compression); int compression() const; +#if QT_DEPRECATED_SINCE(5, 15) + QT_DEPRECATED_VERSION_X_5_15("Use QColorSpace instead") void setGamma(float gamma); + QT_DEPRECATED_VERSION_X_5_15("Use QColorSpace instead") float gamma() const; +#endif void setSubType(const QByteArray &type); QByteArray subType() const; diff --git a/src/gui/image/qpnghandler.cpp b/src/gui/image/qpnghandler.cpp index 49f9550519..8435e5a0fe 100644 --- a/src/gui/image/qpnghandler.cpp +++ b/src/gui/image/qpnghandler.cpp @@ -606,7 +606,7 @@ bool QPngHandlerPrivate::readPngHeader() #endif png_uint_32 profLen; png_get_iCCP(png_ptr, info_ptr, &name, &compressionType, &profileData, &profLen); - colorSpace = QColorSpace::fromIccProfile(QByteArray::fromRawData((const char *)profileData, profLen)); + colorSpace = QColorSpace::fromIccProfile(QByteArray((const char *)profileData, profLen)); if (!colorSpace.isValid()) { qWarning() << "QPngHandler: Failed to parse ICC profile"; } else { diff --git a/src/gui/kernel/qcursor.cpp b/src/gui/kernel/qcursor.cpp index 7f6fdafbd0..1efa28a5af 100644 --- a/src/gui/kernel/qcursor.cpp +++ b/src/gui/kernel/qcursor.cpp @@ -621,7 +621,7 @@ const QBitmap *QCursor::mask() const QBitmap bmpVal = cursor->bitmap(); \endcode */ -QBitmap QCursor::bitmap(Qt::ReturnByValue_t) const +QBitmap QCursor::bitmap(Qt::ReturnByValueConstant) const { if (!QCursorData::initialized) QCursorData::initialize(); @@ -653,7 +653,7 @@ QBitmap QCursor::bitmap(Qt::ReturnByValue_t) const QBitmap bmpVal = cursor->mask(); \endcode */ -QBitmap QCursor::mask(Qt::ReturnByValue_t) const +QBitmap QCursor::mask(Qt::ReturnByValueConstant) const { if (!QCursorData::initialized) QCursorData::initialize(); diff --git a/src/gui/kernel/qcursor.h b/src/gui/kernel/qcursor.h index 7a11fe59ee..3ae6b98ced 100644 --- a/src/gui/kernel/qcursor.h +++ b/src/gui/kernel/qcursor.h @@ -104,11 +104,11 @@ public: QT_DEPRECATED_VERSION_X(5, 15, "Use the other overload which returns QBitmap by-value") const QBitmap *mask() const; // ### Qt 7: Remove function - QBitmap bitmap(Qt::ReturnByValue_t) const; - QBitmap mask(Qt::ReturnByValue_t) const; + QBitmap bitmap(Qt::ReturnByValueConstant) const; + QBitmap mask(Qt::ReturnByValueConstant) const; #else - QBitmap bitmap(Qt::ReturnByValue_t = Qt::ReturnByValue) const; // ### Qt 7: Remove arg - QBitmap mask(Qt::ReturnByValue_t = Qt::ReturnByValue) const; // ### Qt 7: Remove arg + QBitmap bitmap(Qt::ReturnByValueConstant = Qt::ReturnByValue) const; // ### Qt 7: Remove arg + QBitmap mask(Qt::ReturnByValueConstant = Qt::ReturnByValue) const; // ### Qt 7: Remove arg #endif // QT_DEPRECATED_SINCE(5, 15) QPixmap pixmap() const; QPoint hotSpot() const; diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp index fde6bb0180..671c2d93ef 100644 --- a/src/gui/kernel/qhighdpiscaling.cpp +++ b/src/gui/kernel/qhighdpiscaling.cpp @@ -535,7 +535,7 @@ void QHighDpiScaling::updateHighDpiScaling() ++i; } } - m_active = m_globalScalingActive || m_screenFactorSet || m_usePixelDensity; + m_active = m_globalScalingActive || m_screenFactorSet || m_pixelDensityScalingActive; } /* diff --git a/src/gui/kernel/qpixelformat.cpp b/src/gui/kernel/qpixelformat.cpp index c28fe7ac63..3100d13398 100644 --- a/src/gui/kernel/qpixelformat.cpp +++ b/src/gui/kernel/qpixelformat.cpp @@ -485,7 +485,7 @@ QT_BEGIN_NAMESPACE /*! \fn ByteOrder QPixelFormat::byteOrder() const - The byte order is almost always set the the byte order of the current + The byte order is almost always set the byte order of the current system. However, it can be useful to describe some YUV formats. This function should never return QPixelFormat::CurrentSystemEndian as this value is translated to a endian value in the constructor. diff --git a/src/gui/kernel/qplatformwindow.cpp b/src/gui/kernel/qplatformwindow.cpp index 65accc9f68..15d7505133 100644 --- a/src/gui/kernel/qplatformwindow.cpp +++ b/src/gui/kernel/qplatformwindow.cpp @@ -482,19 +482,17 @@ bool QPlatformWindow::windowEvent(QEvent *event) } /*! - Reimplement this method to start a system size grip drag - operation if the system supports it and return true to indicate - success. - It is called from the mouse press event handler of the size grip. + Reimplement this method to start a system resize operation if + the system supports it and return true to indicate success. + + The default implementation is empty and does nothing with \a edges. - The default implementation is empty and does nothing with \a pos - and \a corner. + \since 5.15 */ -bool QPlatformWindow::startSystemResize(const QPoint &pos, Qt::Corner corner) +bool QPlatformWindow::startSystemResize(Qt::Edges edges) { - Q_UNUSED(pos) - Q_UNUSED(corner) + Q_UNUSED(edges) return false; } @@ -502,18 +500,13 @@ bool QPlatformWindow::startSystemResize(const QPoint &pos, Qt::Corner corner) Reimplement this method to start a system move operation if the system supports it and return true to indicate success. - The \a pos is a position of MouseButtonPress event or TouchBegin - event from a sequence of mouse events that triggered the movement. - It must be specified in window coordinates. - - The default implementation is empty and does nothing with \a pos. + The default implementation is empty and does nothing. - \since 5.11 + \since 5.15 */ -bool QPlatformWindow::startSystemMove(const QPoint &pos) +bool QPlatformWindow::startSystemMove() { - Q_UNUSED(pos) return false; } @@ -747,7 +740,7 @@ QRect QPlatformWindow::initialGeometry(const QWindow *w, const QRect &initialGeo QPlatformWindow subclasses can re-implement this function to provide display refresh synchronized updates. The event should be delivered using QPlatformWindow::deliverUpdateRequest() - to not get out of sync with the the internal state of QWindow. + to not get out of sync with the internal state of QWindow. The default implementation posts an UpdateRequest event to the window after 5 ms. The additional time is there to give the event diff --git a/src/gui/kernel/qplatformwindow.h b/src/gui/kernel/qplatformwindow.h index b6aeb3a86a..7b85090cc0 100644 --- a/src/gui/kernel/qplatformwindow.h +++ b/src/gui/kernel/qplatformwindow.h @@ -132,8 +132,8 @@ public: virtual bool windowEvent(QEvent *event); - virtual bool startSystemResize(const QPoint &pos, Qt::Corner corner); - virtual bool startSystemMove(const QPoint &pos); + virtual bool startSystemResize(Qt::Edges edges); + virtual bool startSystemMove(); virtual void setFrameStrutEventsEnabled(bool enabled); virtual bool frameStrutEventsEnabled() const; diff --git a/src/gui/kernel/qscreen.cpp b/src/gui/kernel/qscreen.cpp index 9de59f8c7e..fd25becbca 100644 --- a/src/gui/kernel/qscreen.cpp +++ b/src/gui/kernel/qscreen.cpp @@ -149,6 +149,8 @@ QScreen::~QScreen() /*! Get the platform screen handle. + + \sa {Qt Platform Abstraction}{Qt Platform Abstraction (QPA)} */ QPlatformScreen *QScreen::handle() const { diff --git a/src/gui/kernel/qwindow.cpp b/src/gui/kernel/qwindow.cpp index dad1b6ba2d..c099ec9692 100644 --- a/src/gui/kernel/qwindow.cpp +++ b/src/gui/kernel/qwindow.cpp @@ -1051,6 +1051,71 @@ void QWindow::lower() } /*! + \brief Start a system-specific resize operation + \since 5.15 + + Calling this will start an interactive resize operation on the window by platforms + that support it. The actual behavior may vary depending on the platform. Usually, + it will make the window resize so that its edge follows the mouse cursor. + + On platforms that support it, this method of resizing windows is preferred over + \c setGeometry, because it allows a more native look-and-feel of resizing windows, e.g. + letting the window manager snap this window against other windows, or special resizing + behavior with animations when dragged to the edge of the screen. + + \a edges should either be a single edge, or two adjacent edges (a corner). Other values + are not allowed. + + Returns true if the operation was supported by the system. +*/ +bool QWindow::startSystemResize(Qt::Edges edges) +{ + Q_D(QWindow); + if (Q_UNLIKELY(!isVisible() || !d->platformWindow || d->maximumSize == d->minimumSize)) + return false; + + const bool isSingleEdge = edges == Qt::TopEdge || edges == Qt::RightEdge || edges == Qt::BottomEdge || edges == Qt::LeftEdge; + const bool isCorner = + edges == (Qt::TopEdge | Qt::LeftEdge) || + edges == (Qt::TopEdge | Qt::RightEdge) || + edges == (Qt::BottomEdge | Qt::RightEdge) || + edges == (Qt::BottomEdge | Qt::LeftEdge); + + if (Q_UNLIKELY(!isSingleEdge && !isCorner)) { + qWarning() << "Invalid edges" << edges << "passed to QWindow::startSystemResize, ignoring."; + return false; + } + + return d->platformWindow->startSystemResize(edges); +} + +/*! + \brief Start a system-specific move operation + \since 5.15 + + Calling this will start an interactive move operation on the window by platforms + that support it. The actual behavior may vary depending on the platform. Usually, + it will make the window follow the mouse cursor until a mouse button is released. + + On platforms that support it, this method of moving windows is preferred over + \c setPosition, because it allows a more native look-and-feel of moving windows, e.g. + letting the window manager snap this window against other windows, or special tiling + or resizing behavior with animations when dragged to the edge of the screen. + Furthermore, on some platforms such as Wayland, \c setPosition is not supported, so + this is the only way the application can influence its position. + + Returns true if the operation was supported by the system. +*/ +bool QWindow::startSystemMove() +{ + Q_D(QWindow); + if (Q_UNLIKELY(!isVisible() || !d->platformWindow)) + return false; + + return d->platformWindow->startSystemMove(); +} + +/*! \property QWindow::opacity \brief The opacity of the window in the windowing system. \since 5.1 @@ -1793,7 +1858,10 @@ void QWindow::setFramePosition(const QPoint &point) The position is in relation to the virtualGeometry() of its screen. - \sa position() + For interactively moving windows, see startSystemMove(). For interactively + resizing windows, see startSystemResize(). + + \sa position(), startSystemMove() */ void QWindow::setPosition(const QPoint &pt) { @@ -1830,6 +1898,8 @@ void QWindow::setPosition(int posx, int posy) set the size of the window, excluding any window frame, to a QSize constructed from width \a w and height \a h + For interactively resizing windows, see startSystemResize(). + \sa size(), geometry() */ void QWindow::resize(int w, int h) diff --git a/src/gui/kernel/qwindow.h b/src/gui/kernel/qwindow.h index 5ee1d00f5b..7aae7ffffa 100644 --- a/src/gui/kernel/qwindow.h +++ b/src/gui/kernel/qwindow.h @@ -292,6 +292,8 @@ public Q_SLOTS: bool close(); void raise(); void lower(); + bool startSystemResize(Qt::Edges edges); + bool startSystemMove(); void setTitle(const QString &); diff --git a/src/gui/painting/painting.pri b/src/gui/painting/painting.pri index 1a0f4f11e4..94a88f55dc 100644 --- a/src/gui/painting/painting.pri +++ b/src/gui/painting/painting.pri @@ -161,7 +161,7 @@ gcc:equals(QT_GCC_MAJOR_VERSION, 5) { DEFINES += QT_COMPILER_SUPPORTS_SSE4_1 QT_COMPILER_SUPPORTS_SSE4_2 SOURCES += painting/qdrawhelper_sse4.cpp painting/qimagescale_sse4.cpp } - arm64-v8a { + arm64-v8a | armeabi-v7a { SOURCES += painting/qdrawhelper_neon.cpp painting/qimagescale_neon.cpp HEADERS += painting/qdrawhelper_neon_p.h } diff --git a/src/gui/painting/qbackingstore.cpp b/src/gui/painting/qbackingstore.cpp index 0a49269c36..2147d9d61d 100644 --- a/src/gui/painting/qbackingstore.cpp +++ b/src/gui/painting/qbackingstore.cpp @@ -281,7 +281,7 @@ QSize QBackingStore::size() const bool QBackingStore::scroll(const QRegion &area, int dx, int dy) { // Disable scrolling for non-integer scroll deltas. For this case - // the the existing rendered pixels can't be re-used, and we return + // the existing rendered pixels can't be re-used, and we return // false to signal that a repaint is needed. const qreal nativeDx = QHighDpi::toNativePixels(qreal(dx), d_ptr->window); const qreal nativeDy = QHighDpi::toNativePixels(qreal(dy), d_ptr->window); diff --git a/src/gui/painting/qcolorspace.cpp b/src/gui/painting/qcolorspace.cpp index 7ebd5f1bf4..7e7bbec870 100644 --- a/src/gui/painting/qcolorspace.cpp +++ b/src/gui/painting/qcolorspace.cpp @@ -549,8 +549,12 @@ float QColorSpace::gamma() const noexcept */ void QColorSpace::setTransferFunction(QColorSpace::TransferFunction transferFunction, float gamma) { - if (!isValid() || transferFunction == QColorSpace::TransferFunction::Custom) + if (transferFunction == TransferFunction::Custom) + return; + if (!d_ptr) { + d_ptr = new QColorSpacePrivate(Primaries::Custom, transferFunction, gamma); return; + } if (d_ptr->transferFunction == transferFunction && d_ptr->gamma == gamma) return; QColorSpacePrivate::getWritable(*this); // detach @@ -585,8 +589,12 @@ QColorSpace QColorSpace::withTransferFunction(QColorSpace::TransferFunction tran */ void QColorSpace::setPrimaries(QColorSpace::Primaries primariesId) { - if (!isValid() || primariesId == QColorSpace::Primaries::Custom) + if (primariesId == Primaries::Custom) return; + if (!d_ptr) { + d_ptr = new QColorSpacePrivate(primariesId, TransferFunction::Custom, 0.0f); + return; + } if (d_ptr->primaries == primariesId) return; QColorSpacePrivate::getWritable(*this); // detach @@ -605,11 +613,13 @@ void QColorSpace::setPrimaries(QColorSpace::Primaries primariesId) void QColorSpace::setPrimaries(const QPointF &whitePoint, const QPointF &redPoint, const QPointF &greenPoint, const QPointF &bluePoint) { - if (!isValid()) - return; QColorSpacePrimaries primaries(whitePoint, redPoint, greenPoint, bluePoint); if (!primaries.areValid()) return; + if (!d_ptr) { + d_ptr = new QColorSpacePrivate(primaries, TransferFunction::Custom, 0.0f); + return; + } QColorMatrix toXyz = primaries.toXyzMatrix(); if (QColorVector(primaries.whitePoint) == d_ptr->whitePoint && toXyz == d_ptr->toXyz) return; @@ -692,12 +702,14 @@ bool operator==(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2) const bool valid1 = colorSpace1.isValid(); const bool valid2 = colorSpace2.isValid(); - if (!valid1 && !valid2) - return colorSpace1.d_ptr->iccProfile == colorSpace2.d_ptr->iccProfile; - else if (!valid1 || !valid2) + if (valid1 != valid2) return false; + if (!valid1 && !valid2) { + if (!colorSpace1.d_ptr->iccProfile.isEmpty() || !colorSpace2.d_ptr->iccProfile.isEmpty()) + return colorSpace1.d_ptr->iccProfile == colorSpace2.d_ptr->iccProfile; + } - // At this point one or both color spaces are unknown but valid, and must be compared in detail instead + // At this point one or both color spaces are unknown, and must be compared in detail instead if (colorSpace1.primaries() != QColorSpace::Primaries::Custom && colorSpace2.primaries() != QColorSpace::Primaries::Custom) { if (colorSpace1.primaries() != colorSpace2.primaries()) diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index a9c6835687..f0bf19bd91 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -1652,7 +1652,7 @@ QRhiTextureUploadDescription::QRhiTextureUploadDescription(const QRhiTextureUplo Constructs a texture upload description with the specified \a list of entries. \note \a list can also contain multiple QRhiTextureUploadEntry elements - with the the same layer and level. This makes sense when those uploads are + with the same layer and level. This makes sense when those uploads are partial, meaning their subresource description has a source size or image smaller than the subresource dimensions, and can be more efficient than issuing separate uploadTexture()'s. @@ -2297,11 +2297,10 @@ bool QRhiTexture::buildFrom(QRhiTexture::NativeTexture src) */ QRhiSampler::QRhiSampler(QRhiImplementation *rhi, Filter magFilter_, Filter minFilter_, Filter mipmapMode_, - AddressMode u_, AddressMode v_) + AddressMode u_, AddressMode v_, AddressMode w_) : QRhiResource(rhi), m_magFilter(magFilter_), m_minFilter(minFilter_), m_mipmapMode(mipmapMode_), - m_addressU(u_), m_addressV(v_), - m_addressW(QRhiSampler::ClampToEdge), + m_addressU(u_), m_addressV(v_), m_addressW(w_), m_compareOp(QRhiSampler::Never) { } @@ -3506,7 +3505,7 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const \l{QSurfaceFormat::sRGBColorSpace}{sRGBColorSpace} on the QSurfaceFormat of the QWindow in addition. - \value UsedAsTransferSource Indicates the the swapchain will be used as the + \value UsedAsTransferSource Indicates the swapchain will be used as the source of a readback in QRhiResourceUpdateBatch::readBackTexture(). \value NoVSync Requests disabling waiting for vertical sync, also avoiding @@ -3623,7 +3622,7 @@ QRhiResource::Type QRhiSwapChain::resourceType() const \fn QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget() \return a render target that can used with beginPass() in order to render - the the swapchain's current backbuffer. Only valid within a + the swapchain's current backbuffer. Only valid within a QRhi::beginFrame() - QRhi::endFrame() block where beginFrame() was called with this swapchain. @@ -4449,7 +4448,7 @@ void QRhiResourceUpdateBatch::generateMips(QRhiTexture *tex, int layer) recorded. \note the return value is not owned by the caller and must never be - destroyed. Instead, the batch is returned the the pool for reuse by passing + destroyed. Instead, the batch is returned the pool for reuse by passing it to QRhiCommandBuffer::beginPass(), QRhiCommandBuffer::endPass(), or QRhiCommandBuffer::resourceUpdate(), or by calling QRhiResourceUpdateBatch::release() on it. @@ -5059,13 +5058,24 @@ bool QRhi::isYUpInNDC() const } /*! - \return \c true if the underlying graphics API uses depth 0 - 1 in clip - space. + \return \c true if the underlying graphics API uses depth range [0, 1] in + clip space. - In practice this is \c false for OpenGL only. + In practice this is \c false for OpenGL only, because OpenGL uses a + post-projection depth range of [-1, 1]. (not to be confused with the + NDC-to-window mapping controlled by glDepthRange(), which uses a range of + [0, 1], unless overridden by the QRhiViewport) In some OpenGL versions + glClipControl() could be used to change this, but the OpenGL backend of + QRhi does not use that function as it is not available in OpenGL ES or + OpenGL versions lower than 4.5. \note clipSpaceCorrMatrix() includes the corresponding adjustment in its - returned matrix. + returned matrix. Therefore, many users of QRhi do not need to take any + further measures apart from pre-multiplying their projection matrices with + clipSpaceCorrMatrix(). However, some graphics techniques, such as, some + types of shadow mapping, involve working with and outputting depth values + in the shaders. These will need to query and take the value of this + function into account as appropriate. */ bool QRhi::isClipDepthZeroToOne() const { @@ -5075,11 +5085,15 @@ bool QRhi::isClipDepthZeroToOne() const /*! \return a matrix that can be used to allow applications keep using OpenGL-targeted vertex data and perspective projection matrices (such as, - the ones generated by QMatrix4x4::perspective()), regardless of the - backend. Once \c{this_matrix * mvp} is used instead of just \c mvp, vertex - data with Y up and viewports with depth range 0 - 1 can be used without - considering what backend and so graphics API is going to be used at run - time. + the ones generated by QMatrix4x4::perspective()), regardless of the active + QRhi backend. + + In a typical renderer, once \c{this_matrix * mvp} is used instead of just + \c mvp, vertex data with Y up and viewports with depth range 0 - 1 can be + used without considering what backend (and so graphics API) is going to be + used at run time. This way branching based on isYUpInNDC() and + isClipDepthZeroToOne() can be avoided (although such logic may still become + required when implementing certain advanced graphics techniques). See \l{https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/}{this @@ -5321,16 +5335,19 @@ QRhiTexture *QRhi::newTexture(QRhiTexture::Format format, /*! \return a new sampler with the specified magnification filter \a magFilter, - minification filter \a minFilter, mipmapping mode \a mipmapMpde, and S/T - addressing modes \a u and \a v. + minification filter \a minFilter, mipmapping mode \a mipmapMode, and the + addressing (wrap) modes \a addressU, \a addressV, and \a addressW. \sa QRhiResource::release() */ -QRhiSampler *QRhi::newSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, +QRhiSampler *QRhi::newSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) + QRhiSampler::AddressMode addressU, + QRhiSampler::AddressMode addressV, + QRhiSampler::AddressMode addressW) { - return d->createSampler(magFilter, minFilter, mipmapMode, u, v); + return d->createSampler(magFilter, minFilter, mipmapMode, addressU, addressV, addressW); } /*! diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index 3a64835c22..d17112a241 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -850,7 +850,7 @@ public: protected: QRhiSampler(QRhiImplementation *rhi, Filter magFilter_, Filter minFilter_, Filter mipmapMode_, - AddressMode u_, AddressMode v_); + AddressMode u_, AddressMode v_, AddressMode w_); Filter m_magFilter; Filter m_minFilter; Filter m_mipmapMode; @@ -1155,6 +1155,12 @@ public: float lineWidth() const { return m_lineWidth; } void setLineWidth(float width) { m_lineWidth = width; } + int depthBias() const { return m_depthBias; } + void setDepthBias(int bias) { m_depthBias = bias; } + + float slopeScaledDepthBias() const { return m_slopeScaledDepthBias; } + void setSlopeScaledDepthBias(float bias) { m_slopeScaledDepthBias = bias; } + void setShaderStages(std::initializer_list<QRhiShaderStage> list) { m_shaderStages = list; } template<typename InputIterator> void setShaderStages(InputIterator first, InputIterator last) @@ -1193,6 +1199,8 @@ protected: quint32 m_stencilWriteMask = 0xFF; int m_sampleCount = 1; float m_lineWidth = 1.0f; + int m_depthBias = 0; + float m_slopeScaledDepthBias = 0.0f; QVarLengthArray<QRhiShaderStage, 4> m_shaderStages; QRhiVertexInputLayout m_vertexInputLayout; QRhiShaderResourceBindings *m_shaderResourceBindings = nullptr; @@ -1474,9 +1482,12 @@ public: int sampleCount = 1, QRhiTexture::Flags flags = QRhiTexture::Flags()); - QRhiSampler *newSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, + QRhiSampler *newSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler::AddressMode u, QRhiSampler::AddressMode v); + QRhiSampler::AddressMode addressU, + QRhiSampler::AddressMode addressV, + QRhiSampler::AddressMode addressW = QRhiSampler::Repeat); QRhiTextureRenderTarget *newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags = QRhiTextureRenderTarget::Flags()); diff --git a/src/gui/rhi/qrhi_p_p.h b/src/gui/rhi/qrhi_p_p.h index baffe28202..4a4c044a29 100644 --- a/src/gui/rhi/qrhi_p_p.h +++ b/src/gui/rhi/qrhi_p_p.h @@ -85,9 +85,12 @@ public: const QSize &pixelSize, int sampleCount, QRhiTexture::Flags flags) = 0; - virtual QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, + virtual QRhiSampler *createSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) = 0; + QRhiSampler:: AddressMode u, + QRhiSampler::AddressMode v, + QRhiSampler::AddressMode w) = 0; virtual QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) = 0; diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index 445d162595..75b90b6995 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -527,9 +527,9 @@ QRhiTexture *QRhiD3D11::createTexture(QRhiTexture::Format format, const QSize &p QRhiSampler *QRhiD3D11::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler::AddressMode u, QRhiSampler::AddressMode v) + QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) { - return new QD3D11Sampler(this, magFilter, minFilter, mipmapMode, u, v); + return new QD3D11Sampler(this, magFilter, minFilter, mipmapMode, u, v, w); } QRhiTextureRenderTarget *QRhiD3D11::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, @@ -2768,8 +2768,8 @@ ID3D11UnorderedAccessView *QD3D11Texture::unorderedAccessViewForLevel(int level) } QD3D11Sampler::QD3D11Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v) - : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v) + AddressMode u, AddressMode v, AddressMode w) + : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w) { } @@ -3467,6 +3467,9 @@ bool QD3D11GraphicsPipeline::build() rastDesc.FillMode = D3D11_FILL_SOLID; rastDesc.CullMode = toD3DCullMode(m_cullMode); rastDesc.FrontCounterClockwise = m_frontFace == CCW; + rastDesc.DepthBias = m_depthBias; + rastDesc.SlopeScaledDepthBias = m_slopeScaledDepthBias; + rastDesc.DepthClipEnable = true; rastDesc.ScissorEnable = m_flags.testFlag(UsesScissor); rastDesc.MultisampleEnable = rhiD->effectiveSampleCount(m_sampleCount).Count > 1; HRESULT hr = rhiD->dev->CreateRasterizerState(&rastDesc, &rastState); diff --git a/src/gui/rhi/qrhid3d11_p_p.h b/src/gui/rhi/qrhid3d11_p_p.h index 9ddd2aa797..c3a4021241 100644 --- a/src/gui/rhi/qrhid3d11_p_p.h +++ b/src/gui/rhi/qrhid3d11_p_p.h @@ -120,7 +120,7 @@ struct QD3D11Texture : public QRhiTexture struct QD3D11Sampler : public QRhiSampler { QD3D11Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v); + AddressMode u, AddressMode v, AddressMode w); ~QD3D11Sampler(); void release() override; bool build() override; @@ -559,9 +559,12 @@ public: const QSize &pixelSize, int sampleCount, QRhiTexture::Flags flags) override; - QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, + QRhiSampler *createSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override; + QRhiSampler:: AddressMode u, + QRhiSampler::AddressMode v, + QRhiSampler::AddressMode w) override; QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override; diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index ec5e531e14..62f808ce81 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -838,9 +838,9 @@ QRhiTexture *QRhiGles2::createTexture(QRhiTexture::Format format, const QSize &p QRhiSampler *QRhiGles2::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler::AddressMode u, QRhiSampler::AddressMode v) + QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) { - return new QGles2Sampler(this, magFilter, minFilter, mipmapMode, u, v); + return new QGles2Sampler(this, magFilter, minFilter, mipmapMode, u, v, w); } QRhiTextureRenderTarget *QRhiGles2::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, @@ -2386,7 +2386,14 @@ void QRhiGles2::executeBindGraphicsPipeline(QRhiGraphicsPipeline *ps) f->glDisable(GL_STENCIL_TEST); } - if (psD->topology() == QRhiGraphicsPipeline::Lines || psD->topology() == QRhiGraphicsPipeline::LineStrip) + if (psD->m_depthBias != 0 || !qFuzzyIsNull(psD->m_slopeScaledDepthBias)) { + f->glPolygonOffset(psD->m_slopeScaledDepthBias, psD->m_depthBias); + f->glEnable(GL_POLYGON_OFFSET_FILL); + } else { + f->glDisable(GL_POLYGON_OFFSET_FILL); + } + + if (psD->m_topology == QRhiGraphicsPipeline::Lines || psD->m_topology == QRhiGraphicsPipeline::LineStrip) f->glLineWidth(psD->m_lineWidth); f->glUseProgram(psD->program); @@ -3626,8 +3633,8 @@ QRhiTexture::NativeTexture QGles2Texture::nativeTexture() } QGles2Sampler::QGles2Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v) - : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v) + AddressMode u, AddressMode v, AddressMode w) + : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w) { } diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h index 679f806004..00caf40118 100644 --- a/src/gui/rhi/qrhigles2_p_p.h +++ b/src/gui/rhi/qrhigles2_p_p.h @@ -170,7 +170,7 @@ struct QGles2Texture : public QRhiTexture struct QGles2Sampler : public QRhiSampler { QGles2Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v); + AddressMode u, AddressMode v, AddressMode w); ~QGles2Sampler(); void release() override; bool build() override; @@ -613,9 +613,12 @@ public: const QSize &pixelSize, int sampleCount, QRhiTexture::Flags flags) override; - QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, + QRhiSampler *createSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override; + QRhiSampler:: AddressMode u, + QRhiSampler::AddressMode v, + QRhiSampler::AddressMode w) override; QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override; diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 98cbd416b5..9e8f1ac096 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -298,6 +298,8 @@ struct QMetalGraphicsPipelineData MTLPrimitiveType primitiveType; MTLWinding winding; MTLCullMode cullMode; + float depthBias; + float slopeScaledDepthBias; QMetalShader vs; QMetalShader fs; }; @@ -626,9 +628,9 @@ QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format, const QSize &p QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler::AddressMode u, QRhiSampler::AddressMode v) + QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) { - return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v); + return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v, w); } QRhiTextureRenderTarget *QRhiMetal::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, @@ -684,11 +686,27 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD bool offsetOnlyChange, const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES]) { - struct { - QRhiBatchedBindings<id<MTLBuffer> > buffers; - QRhiBatchedBindings<NSUInteger> bufferOffsets; - QRhiBatchedBindings<id<MTLTexture> > textures; - QRhiBatchedBindings<id<MTLSamplerState> > samplers; + struct Stage { + struct Buffer { + int nativeBinding; + id<MTLBuffer> mtlbuf; + uint offset; + }; + struct Texture { + int nativeBinding; + id<MTLTexture> mtltex; + }; + struct Sampler { + int nativeBinding; + id<MTLSamplerState> mtlsampler; + }; + QVarLengthArray<Buffer, 8> buffers; + QVarLengthArray<Texture, 8> textures; + QVarLengthArray<Sampler, 8> samplers; + QRhiBatchedBindings<id<MTLBuffer> > bufferBatches; + QRhiBatchedBindings<NSUInteger> bufferOffsetBatches; + QRhiBatchedBindings<id<MTLTexture> > textureBatches; + QRhiBatchedBindings<id<MTLSamplerState> > samplerBatches; } res[SUPPORTED_STAGES]; enum { VERTEX = 0, FRAGMENT = 1, COMPUTE = 2 }; @@ -709,24 +727,18 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD } if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer); - if (nativeBinding >= 0) { - res[VERTEX].buffers.feed(nativeBinding, mtlbuf); - res[VERTEX].bufferOffsets.feed(b->binding, offset); - } + if (nativeBinding >= 0) + res[VERTEX].buffers.append({ nativeBinding, mtlbuf, offset }); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer); - if (nativeBinding >= 0) { - res[FRAGMENT].buffers.feed(nativeBinding, mtlbuf); - res[FRAGMENT].bufferOffsets.feed(b->binding, offset); - } + if (nativeBinding >= 0) + res[FRAGMENT].buffers.append({ nativeBinding, mtlbuf, offset }); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer); - if (nativeBinding >= 0) { - res[COMPUTE].buffers.feed(nativeBinding, mtlbuf); - res[COMPUTE].bufferOffsets.feed(b->binding, offset); - } + if (nativeBinding >= 0) + res[COMPUTE].buffers.append({ nativeBinding, mtlbuf, offset }); } } break; @@ -738,24 +750,24 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD const int nativeBindingTexture = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture); const int nativeBindingSampler = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Sampler); if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) { - res[VERTEX].textures.feed(nativeBindingTexture, texD->d->tex); - res[VERTEX].samplers.feed(nativeBindingSampler, samplerD->d->samplerState); + res[VERTEX].textures.append({ nativeBindingTexture, texD->d->tex }); + res[VERTEX].samplers.append({ nativeBindingSampler, samplerD->d->samplerState }); } } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { const int nativeBindingTexture = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture); const int nativeBindingSampler = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Sampler); if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) { - res[FRAGMENT].textures.feed(nativeBindingTexture, texD->d->tex); - res[FRAGMENT].samplers.feed(nativeBindingSampler, samplerD->d->samplerState); + res[FRAGMENT].textures.append({ nativeBindingTexture, texD->d->tex }); + res[FRAGMENT].samplers.append({ nativeBindingSampler, samplerD->d->samplerState }); } } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { const int nativeBindingTexture = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture); const int nativeBindingSampler = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Sampler); if (nativeBindingTexture >= 0 && nativeBindingSampler >= 0) { - res[COMPUTE].textures.feed(nativeBindingTexture, texD->d->tex); - res[COMPUTE].samplers.feed(nativeBindingSampler, samplerD->d->samplerState); + res[COMPUTE].textures.append({ nativeBindingTexture, texD->d->tex }); + res[COMPUTE].samplers.append({ nativeBindingSampler, samplerD->d->samplerState }); } } } @@ -769,17 +781,17 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture); if (nativeBinding >= 0) - res[VERTEX].textures.feed(nativeBinding, t); + res[VERTEX].textures.append({ nativeBinding, t }); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture); if (nativeBinding >= 0) - res[FRAGMENT].textures.feed(nativeBinding, t); + res[FRAGMENT].textures.append({ nativeBinding, t }); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture); if (nativeBinding >= 0) - res[COMPUTE].textures.feed(nativeBinding, t); + res[COMPUTE].textures.append({ nativeBinding, t }); } } break; @@ -792,24 +804,18 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD uint offset = uint(b->u.sbuf.offset); if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { const int nativeBinding = mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer); - if (nativeBinding >= 0) { - res[VERTEX].buffers.feed(nativeBinding, mtlbuf); - res[VERTEX].bufferOffsets.feed(b->binding, offset); - } + if (nativeBinding >= 0) + res[VERTEX].buffers.append({ nativeBinding, mtlbuf, offset }); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { const int nativeBinding = mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer); - if (nativeBinding >= 0) { - res[FRAGMENT].buffers.feed(nativeBinding, mtlbuf); - res[FRAGMENT].bufferOffsets.feed(b->binding, offset); - } + if (nativeBinding >= 0) + res[FRAGMENT].buffers.append({ nativeBinding, mtlbuf, offset }); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { const int nativeBinding = mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer); - if (nativeBinding >= 0) { - res[COMPUTE].buffers.feed(nativeBinding, mtlbuf); - res[COMPUTE].bufferOffsets.feed(b->binding, offset); - } + if (nativeBinding >= 0) + res[COMPUTE].buffers.append({ nativeBinding, mtlbuf, offset }); } } break; @@ -825,12 +831,26 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD if (cbD->recordingPass != QMetalCommandBuffer::ComputePass && stage == COMPUTE) continue; - res[stage].buffers.finish(); - res[stage].bufferOffsets.finish(); + // QRhiBatchedBindings works with the native bindings and expects + // sorted input. The pre-sorted QRhiShaderResourceBinding list (based + // on the QRhi (SPIR-V) binding) is not helpful in this regard, so we + // have to sort here every time. + + std::sort(res[stage].buffers.begin(), res[stage].buffers.end(), [](const Stage::Buffer &a, const Stage::Buffer &b) { + return a.nativeBinding < b.nativeBinding; + }); + + for (const Stage::Buffer &buf : qAsConst(res[stage].buffers)) { + res[stage].bufferBatches.feed(buf.nativeBinding, buf.mtlbuf); + res[stage].bufferOffsetBatches.feed(buf.nativeBinding, buf.offset); + } + + res[stage].bufferBatches.finish(); + res[stage].bufferOffsetBatches.finish(); - for (int i = 0, ie = res[stage].buffers.batches.count(); i != ie; ++i) { - const auto &bufferBatch(res[stage].buffers.batches[i]); - const auto &offsetBatch(res[stage].bufferOffsets.batches[i]); + for (int i = 0, ie = res[stage].bufferBatches.batches.count(); i != ie; ++i) { + const auto &bufferBatch(res[stage].bufferBatches.batches[i]); + const auto &offsetBatch(res[stage].bufferOffsetBatches.batches[i]); switch (stage) { case VERTEX: [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData() @@ -856,11 +876,25 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD if (offsetOnlyChange) continue; - res[stage].textures.finish(); - res[stage].samplers.finish(); + std::sort(res[stage].textures.begin(), res[stage].textures.end(), [](const Stage::Texture &a, const Stage::Texture &b) { + return a.nativeBinding < b.nativeBinding; + }); - for (int i = 0, ie = res[stage].textures.batches.count(); i != ie; ++i) { - const auto &batch(res[stage].textures.batches[i]); + std::sort(res[stage].samplers.begin(), res[stage].samplers.end(), [](const Stage::Sampler &a, const Stage::Sampler &b) { + return a.nativeBinding < b.nativeBinding; + }); + + for (const Stage::Texture &t : qAsConst(res[stage].textures)) + res[stage].textureBatches.feed(t.nativeBinding, t.mtltex); + + for (const Stage::Sampler &s : qAsConst(res[stage].samplers)) + res[stage].samplerBatches.feed(s.nativeBinding, s.mtlsampler); + + res[stage].textureBatches.finish(); + res[stage].samplerBatches.finish(); + + for (int i = 0, ie = res[stage].textureBatches.batches.count(); i != ie; ++i) { + const auto &batch(res[stage].textureBatches.batches[i]); switch (stage) { case VERTEX: [cbD->d->currentRenderPassEncoder setVertexTextures: batch.resources.constData() @@ -879,8 +913,8 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD break; } } - for (int i = 0, ie = res[stage].samplers.batches.count(); i != ie; ++i) { - const auto &batch(res[stage].samplers.batches[i]); + for (int i = 0, ie = res[stage].samplerBatches.batches.count(); i != ie; ++i) { + const auto &batch(res[stage].samplerBatches.batches[i]); switch (stage) { case VERTEX: [cbD->d->currentRenderPassEncoder setVertexSamplerStates: batch.resources.constData() @@ -924,6 +958,14 @@ void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline [cbD->d->currentRenderPassEncoder setFrontFacingWinding: psD->d->winding]; cbD->currentFrontFaceWinding = int(psD->d->winding); } + if (!qFuzzyCompare(psD->d->depthBias, cbD->currentDepthBiasValues.first) + || !qFuzzyCompare(psD->d->slopeScaledDepthBias, cbD->currentDepthBiasValues.second)) + { + [cbD->d->currentRenderPassEncoder setDepthBias: psD->d->depthBias + slopeScale: psD->d->slopeScaledDepthBias + clamp: 0.0f]; + cbD->currentDepthBiasValues = { psD->d->depthBias, psD->d->slopeScaledDepthBias }; + } } psD->lastActiveFrameSlot = currentFrameSlot; @@ -2548,8 +2590,8 @@ id<MTLTexture> QMetalTextureData::viewForLevel(int level) } QMetalSampler::QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v) - : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v), + AddressMode u, AddressMode v, AddressMode w) + : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w), d(new QMetalSamplerData) { } @@ -3422,6 +3464,8 @@ bool QMetalGraphicsPipeline::build() d->primitiveType = toMetalPrimitiveType(m_topology); d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise; d->cullMode = toMetalCullMode(m_cullMode); + d->depthBias = float(m_depthBias); + d->slopeScaledDepthBias = m_slopeScaledDepthBias; lastActiveFrameSlot = -1; generation += 1; @@ -3570,6 +3614,7 @@ void QMetalCommandBuffer::resetPerPassCachedState() currentIndexFormat = QRhiCommandBuffer::IndexUInt16; currentCullMode = -1; currentFrontFaceWinding = -1; + currentDepthBiasValues = { 0.0f, 0.0f }; d->currentFirstVertexBinding = -1; d->currentVertexInputsBuffers.clear(); diff --git a/src/gui/rhi/qrhimetal_p_p.h b/src/gui/rhi/qrhimetal_p_p.h index 71d4325b1a..58e93e2cdb 100644 --- a/src/gui/rhi/qrhimetal_p_p.h +++ b/src/gui/rhi/qrhimetal_p_p.h @@ -120,7 +120,7 @@ struct QMetalSamplerData; struct QMetalSampler : public QRhiSampler { QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v); + AddressMode u, AddressMode v, AddressMode w); ~QMetalSampler(); void release() override; bool build() override; @@ -288,6 +288,7 @@ struct QMetalCommandBuffer : public QRhiCommandBuffer QRhiCommandBuffer::IndexFormat currentIndexFormat; int currentCullMode; int currentFrontFaceWinding; + QPair<float, float> currentDepthBiasValues; const QRhiNativeHandles *nativeHandles(); void resetState(); @@ -349,9 +350,12 @@ public: const QSize &pixelSize, int sampleCount, QRhiTexture::Flags flags) override; - QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, + QRhiSampler *createSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override; + QRhiSampler:: AddressMode u, + QRhiSampler::AddressMode v, + QRhiSampler::AddressMode w) override; QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override; diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp index ea67f80138..4c59900aa6 100644 --- a/src/gui/rhi/qrhinull.cpp +++ b/src/gui/rhi/qrhinull.cpp @@ -193,9 +193,9 @@ QRhiTexture *QRhiNull::createTexture(QRhiTexture::Format format, const QSize &pi QRhiSampler *QRhiNull::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler::AddressMode u, QRhiSampler::AddressMode v) + QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) { - return new QNullSampler(this, magFilter, minFilter, mipmapMode, u, v); + return new QNullSampler(this, magFilter, minFilter, mipmapMode, u, v, w); } QRhiTextureRenderTarget *QRhiNull::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, @@ -645,8 +645,8 @@ bool QNullTexture::buildFrom(QRhiTexture::NativeTexture src) } QNullSampler::QNullSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v) - : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v) + AddressMode u, AddressMode v, AddressMode w) + : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w) { } diff --git a/src/gui/rhi/qrhinull_p_p.h b/src/gui/rhi/qrhinull_p_p.h index f541fd90b8..0b3d40f1aa 100644 --- a/src/gui/rhi/qrhinull_p_p.h +++ b/src/gui/rhi/qrhinull_p_p.h @@ -88,7 +88,7 @@ struct QNullTexture : public QRhiTexture struct QNullSampler : public QRhiSampler { QNullSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v); + AddressMode u, AddressMode v, AddressMode w); ~QNullSampler(); void release() override; bool build() override; @@ -212,9 +212,12 @@ public: const QSize &pixelSize, int sampleCount, QRhiTexture::Flags flags) override; - QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, + QRhiSampler *createSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override; + QRhiSampler:: AddressMode u, + QRhiSampler::AddressMode v, + QRhiSampler::AddressMode w) override; QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override; diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index e7faa0cadd..84ca835392 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -2462,9 +2462,10 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i { QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb); - QVarLengthArray<VkDescriptorBufferInfo, 4> bufferInfos; - QVarLengthArray<VkDescriptorImageInfo, 4> imageInfos; - QVarLengthArray<VkWriteDescriptorSet, 8> writeInfos; + QVarLengthArray<VkDescriptorBufferInfo, 8> bufferInfos; + QVarLengthArray<VkDescriptorImageInfo, 8> imageInfos; + QVarLengthArray<VkWriteDescriptorSet, 12> writeInfos; + QVarLengthArray<QPair<int, int>, 12> infoIndices; const bool updateAll = descSetIdx < 0; int frameSlot = updateAll ? 0 : descSetIdx; @@ -2481,6 +2482,9 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i writeInfo.dstBinding = uint32_t(b->binding); writeInfo.descriptorCount = 1; + int bufferInfoIndex = -1; + int imageInfoIndex = -1; + switch (b->type) { case QRhiShaderResourceBinding::UniformBuffer: { @@ -2496,8 +2500,8 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i bufInfo.range = VkDeviceSize(b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size); // be nice and assert when we know the vulkan device would die a horrible death due to non-aligned reads Q_ASSERT(aligned(bufInfo.offset, ubufAlign) == bufInfo.offset); + bufferInfoIndex = bufferInfos.count(); bufferInfos.append(bufInfo); - writeInfo.pBufferInfo = &bufferInfos.last(); } break; case QRhiShaderResourceBinding::SampledTexture: @@ -2513,8 +2517,8 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i imageInfo.sampler = samplerD->sampler; imageInfo.imageView = texD->imageView; imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfoIndex = imageInfos.count(); imageInfos.append(imageInfo); - writeInfo.pImageInfo = &imageInfos.last(); } break; case QRhiShaderResourceBinding::ImageLoad: @@ -2531,8 +2535,8 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i imageInfo.sampler = VK_NULL_HANDLE; imageInfo.imageView = view; imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + imageInfoIndex = imageInfos.count(); imageInfos.append(imageInfo); - writeInfo.pImageInfo = &imageInfos.last(); } } break; @@ -2548,8 +2552,8 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[frameSlot] : bufD->buffers[0]; bufInfo.offset = VkDeviceSize(b->u.ubuf.offset); bufInfo.range = VkDeviceSize(b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size); + bufferInfoIndex = bufferInfos.count(); bufferInfos.append(bufInfo); - writeInfo.pBufferInfo = &bufferInfos.last(); } break; default: @@ -2557,10 +2561,20 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i } writeInfos.append(writeInfo); + infoIndices.append({ bufferInfoIndex, imageInfoIndex }); } ++frameSlot; } + for (int i = 0, writeInfoCount = writeInfos.count(); i < writeInfoCount; ++i) { + const int bufferInfoIndex = infoIndices[i].first; + const int imageInfoIndex = infoIndices[i].second; + if (bufferInfoIndex >= 0) + writeInfos[i].pBufferInfo = &bufferInfos[bufferInfoIndex]; + else if (imageInfoIndex >= 0) + writeInfos[i].pImageInfo = &imageInfos[imageInfoIndex]; + } + df->vkUpdateDescriptorSets(dev, uint32_t(writeInfos.count()), writeInfos.constData(), 0, nullptr); } @@ -4046,9 +4060,9 @@ QRhiTexture *QRhiVulkan::createTexture(QRhiTexture::Format format, const QSize & QRhiSampler *QRhiVulkan::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler::AddressMode u, QRhiSampler::AddressMode v) + QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) { - return new QVkSampler(this, magFilter, minFilter, mipmapMode, u, v); + return new QVkSampler(this, magFilter, minFilter, mipmapMode, u, v, w); } QRhiTextureRenderTarget *QRhiVulkan::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, @@ -5543,8 +5557,8 @@ VkImageView QVkTexture::imageViewForLevel(int level) } QVkSampler::QVkSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v) - : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v) + AddressMode u, AddressMode v, AddressMode w) + : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w) { } @@ -6208,6 +6222,11 @@ bool QVkGraphicsPipeline::build() rastInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rastInfo.cullMode = toVkCullMode(m_cullMode); rastInfo.frontFace = toVkFrontFace(m_frontFace); + if (m_depthBias != 0 || !qFuzzyIsNull(m_slopeScaledDepthBias)) { + rastInfo.depthBiasEnable = true; + rastInfo.depthBiasConstantFactor = float(m_depthBias); + rastInfo.depthBiasSlopeFactor = m_slopeScaledDepthBias; + } rastInfo.lineWidth = rhiD->hasWideLines ? m_lineWidth : 1.0f; pipelineInfo.pRasterizationState = &rastInfo; diff --git a/src/gui/rhi/qrhivulkan_p_p.h b/src/gui/rhi/qrhivulkan_p_p.h index b0e90dae56..d42b83b882 100644 --- a/src/gui/rhi/qrhivulkan_p_p.h +++ b/src/gui/rhi/qrhivulkan_p_p.h @@ -154,7 +154,7 @@ struct QVkTexture : public QRhiTexture struct QVkSampler : public QRhiSampler { QVkSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, - AddressMode u, AddressMode v); + AddressMode u, AddressMode v, AddressMode w); ~QVkSampler(); void release() override; bool build() override; @@ -657,9 +657,12 @@ public: const QSize &pixelSize, int sampleCount, QRhiTexture::Flags flags) override; - QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, + QRhiSampler *createSampler(QRhiSampler::Filter magFilter, + QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, - QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override; + QRhiSampler:: AddressMode u, + QRhiSampler::AddressMode v, + QRhiSampler::AddressMode w) override; QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override; diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp index 0024f070ea..ac39a8cf69 100644 --- a/src/gui/text/qtextengine.cpp +++ b/src/gui/text/qtextengine.cpp @@ -1762,7 +1762,10 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, } #endif +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED if (!actualFontEngine->supportsSubPixelPositions() || (actualFontEngine->fontDef.styleStrategy & QFont::ForceIntegerMetrics)) { +QT_WARNING_POP for (uint i = 0; i < num_glyphs; ++i) g.advances[i] = g.advances[i].round(); } diff --git a/src/gui/text/qtextmarkdownimporter.cpp b/src/gui/text/qtextmarkdownimporter.cpp index 88965046ce..7e18a10895 100644 --- a/src/gui/text/qtextmarkdownimporter.cpp +++ b/src/gui/text/qtextmarkdownimporter.cpp @@ -397,10 +397,12 @@ int QTextMarkdownImporter::cbEnterSpan(int spanType, void *det) break; case MD_SPAN_A: { MD_SPAN_A_DETAIL *detail = static_cast<MD_SPAN_A_DETAIL *>(det); - QString url = QString::fromLatin1(detail->href.text, int(detail->href.size)); - QString title = QString::fromLatin1(detail->title.text, int(detail->title.size)); + QString url = QString::fromUtf8(detail->href.text, int(detail->href.size)); + QString title = QString::fromUtf8(detail->title.text, int(detail->title.size)); + charFmt.setAnchor(true); charFmt.setAnchorHref(url); - charFmt.setAnchorNames(QStringList(title)); + if (!title.isEmpty()) + charFmt.setToolTip(title); charFmt.setForeground(m_palette.link()); qCDebug(lcMD) << "anchor" << url << title; } break; diff --git a/src/gui/text/qtextmarkdownwriter.cpp b/src/gui/text/qtextmarkdownwriter.cpp index c9a63920c3..7bd321becc 100644 --- a/src/gui/text/qtextmarkdownwriter.cpp +++ b/src/gui/text/qtextmarkdownwriter.cpp @@ -56,10 +56,13 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcMDW, "qt.text.markdown.writer") static const QChar Space = QLatin1Char(' '); +static const QChar Tab = QLatin1Char('\t'); static const QChar Newline = QLatin1Char('\n'); +static const QChar CarriageReturn = QLatin1Char('\r'); static const QChar LineBreak = QChar(0x2028); static const QChar DoubleQuote = QLatin1Char('"'); static const QChar Backtick = QLatin1Char('`'); +static const QChar Backslash = QLatin1Char('\\'); static const QChar Period = QLatin1Char('.'); QTextMarkdownWriter::QTextMarkdownWriter(QTextStream &stream, QTextDocument::MarkdownFeatures features) @@ -291,6 +294,72 @@ static void maybeEscapeFirstChar(QString &s) } } +struct LineEndPositions { + const QChar *lineEnd; + const QChar *nextLineBegin; +}; + +static LineEndPositions findLineEnd(const QChar *begin, const QChar *end) +{ + LineEndPositions result{ end, end }; + + while (begin < end) { + if (*begin == Newline) { + result.lineEnd = begin; + result.nextLineBegin = begin + 1; + break; + } else if (*begin == CarriageReturn) { + result.lineEnd = begin; + result.nextLineBegin = begin + 1; + if (((begin + 1) < end) && begin[1] == Newline) + ++result.nextLineBegin; + break; + } + + ++begin; + } + + return result; +} + +static bool isBlankLine(const QChar *begin, const QChar *end) +{ + while (begin < end) { + if (*begin != Space && *begin != Tab) + return false; + ++begin; + } + return true; +} + +static QString createLinkTitle(const QString &title) +{ + QString result; + result.reserve(title.size() + 2); + result += DoubleQuote; + + const QChar *data = title.data(); + const QChar *end = data + title.size(); + + while (data < end) { + const auto lineEndPositions = findLineEnd(data, end); + + if (!isBlankLine(data, lineEndPositions.lineEnd)) { + while (data < lineEndPositions.nextLineBegin) { + if (*data == DoubleQuote) + result += Backslash; + result += *data; + ++data; + } + } + + data = lineEndPositions.nextLineBegin; + } + + result += DoubleQuote; + return result; +} + int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ignoreFormat, bool ignoreEmpty) { if (block.text().isEmpty() && ignoreEmpty) @@ -445,7 +514,12 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign col += s.length(); } else if (fmt.hasProperty(QTextFormat::AnchorHref)) { QString s = QLatin1Char('[') + fragmentText + QLatin1String("](") + - fmt.property(QTextFormat::AnchorHref).toString() + QLatin1Char(')'); + fmt.property(QTextFormat::AnchorHref).toString(); + if (fmt.hasProperty(QTextFormat::TextToolTip)) { + s += Space; + s += createLinkTitle(fmt.property(QTextFormat::TextToolTip).toString()); + } + s += QLatin1Char(')'); if (wrap && col + s.length() > ColumnLimit) { m_stream << Newline << wrapIndentString; col = m_wrappedLineIndent; |