diff options
Diffstat (limited to 'src/gui/util')
-rw-r--r-- | src/gui/util/qastchandler.cpp | 6 | ||||
-rw-r--r-- | src/gui/util/qdesktopservices.cpp | 44 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapture.cpp | 123 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapture_p.h | 55 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapture_p_p.h | 67 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapturemetal.mm | 169 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapturemetal_p_p.h | 57 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapturerenderdoc.cpp | 310 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapturerenderdoc_p_p.h | 53 | ||||
-rw-r--r-- | src/gui/util/qgridlayoutengine.cpp | 70 | ||||
-rw-r--r-- | src/gui/util/qgridlayoutengine_p.h | 11 | ||||
-rw-r--r-- | src/gui/util/qktxhandler.cpp | 231 | ||||
-rw-r--r-- | src/gui/util/qktxhandler_p.h | 4 | ||||
-rw-r--r-- | src/gui/util/qvalidator.cpp | 93 |
14 files changed, 1151 insertions, 142 deletions
diff --git a/src/gui/util/qastchandler.cpp b/src/gui/util/qastchandler.cpp index f5c1d84f91..ec8b92f557 100644 --- a/src/gui/util/qastchandler.cpp +++ b/src/gui/util/qastchandler.cpp @@ -109,9 +109,9 @@ QTextureFileData QAstcHandler::read() int zBlocks = (zSz + header->blockDimZ - 1) / header->blockDimZ; int byteCount = 0; - bool oob = mul_overflow(xBlocks, yBlocks, &byteCount) - || mul_overflow(byteCount, zBlocks, &byteCount) - || mul_overflow(byteCount, 16, &byteCount); + bool oob = qMulOverflow(xBlocks, yBlocks, &byteCount) + || qMulOverflow(byteCount, zBlocks, &byteCount) + || qMulOverflow(byteCount, 16, &byteCount); res.setDataOffset(sizeof(AstcHeader)); diff --git a/src/gui/util/qdesktopservices.cpp b/src/gui/util/qdesktopservices.cpp index 4a12f6db6f..7ca138b1b7 100644 --- a/src/gui/util/qdesktopservices.cpp +++ b/src/gui/util/qdesktopservices.cpp @@ -18,8 +18,6 @@ #include <qpa/qplatformintegration.h> #include <qdir.h> -#include <QtCore/private/qlocking_p.h> - QT_BEGIN_NAMESPACE class QOpenUrlHandlerRegistry @@ -36,36 +34,10 @@ public: }; typedef QHash<QString, Handler> HandlerHash; HandlerHash handlers; - -#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) - QObject context; - - void handlerDestroyed(QObject *handler); -#endif - }; Q_GLOBAL_STATIC(QOpenUrlHandlerRegistry, handlerRegistry) -#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) -void QOpenUrlHandlerRegistry::handlerDestroyed(QObject *handler) -{ - const auto lock = qt_scoped_lock(mutex); - HandlerHash::Iterator it = handlers.begin(); - while (it != handlers.end()) { - if (it->receiver == handler) { - it = handlers.erase(it); - qWarning("Please call QDesktopServices::unsetUrlHandler() before destroying a " - "registered URL handler object.\n" - "Support for destroying a registered URL handler object is deprecated, " - "and will be removed in Qt 6.6."); - } else { - ++it; - } - } -} -#endif - /*! \class QDesktopServices \brief The QDesktopServices class provides methods for accessing common desktop services. @@ -238,10 +210,12 @@ bool QDesktopServices::openUrl(const QUrl &url) the destruction of the handler object does not overlap with concurrent invocations of openUrl() using it. - \section1 iOS + \target configuring qdesktopservices url handler on ios and macos + \section1 iOS and \macos - To use this function for receiving data from other apps on iOS you also need to - add the custom scheme to the \c CFBundleURLSchemes list in your Info.plist file: + To use this function for receiving data from other apps on iOS/\macos + you also need to add the custom scheme to the \c CFBundleURLSchemes + list in your Info.plist file: \snippet code/src_gui_util_qdesktopservices.cpp 4 @@ -256,7 +230,7 @@ bool QDesktopServices::openUrl(const QUrl &url) \snippet code/src_gui_util_qdesktopservices.cpp 7 - iOS will search for /.well-known/apple-app-site-association on your domain, + iOS/\macos will search for /.well-known/apple-app-site-association on your domain, when the application is installed. If you want to listen to \c{https://your.domain.com/help?topic=ABCDEF} you need to provide the following content there: @@ -266,6 +240,7 @@ bool QDesktopServices::openUrl(const QUrl &url) For more information, see the Apple Developer Documentation for \l {iOS: Supporting Associated Domains}{Supporting Associated Domains}. + \target configuring qdesktopservices url handler on android \section1 Android To use this function for receiving data from other apps on Android, you @@ -306,11 +281,6 @@ void QDesktopServices::setUrlHandler(const QString &scheme, QObject *receiver, c h.receiver = receiver; h.name = method; registry->handlers.insert(scheme.toLower(), h); -#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) - QObject::connect(receiver, &QObject::destroyed, ®istry->context, - [registry](QObject *obj) { registry->handlerDestroyed(obj); }, - Qt::DirectConnection); -#endif } /*! diff --git a/src/gui/util/qgraphicsframecapture.cpp b/src/gui/util/qgraphicsframecapture.cpp new file mode 100644 index 0000000000..e49fb83008 --- /dev/null +++ b/src/gui/util/qgraphicsframecapture.cpp @@ -0,0 +1,123 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qgraphicsframecapture_p.h" +#if (defined (Q_OS_WIN) || defined(Q_OS_LINUX)) && QT_CONFIG(library) +#include "qgraphicsframecapturerenderdoc_p_p.h" +#elif QT_CONFIG(metal) +#include "qgraphicsframecapturemetal_p_p.h" +#else +#include "qgraphicsframecapture_p_p.h" +#endif + +#include <QtCore/qstandardpaths.h> +#include <QtCore/qcoreapplication.h> + +QT_BEGIN_NAMESPACE + +QGraphicsFrameCapturePrivate::QGraphicsFrameCapturePrivate() + : m_capturePath(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + + QStringLiteral("/") + QCoreApplication::applicationName() + + QStringLiteral("/captures")), + m_capturePrefix(QCoreApplication::applicationName()) +{ + +} + +QGraphicsFrameCapture::QGraphicsFrameCapture() +{ +#if (defined (Q_OS_WIN) || defined(Q_OS_LINUX)) && QT_CONFIG(library) + d.reset(new QGraphicsFrameCaptureRenderDoc); +#elif QT_CONFIG(metal) + d.reset(new QGraphicsFrameCaptureMetal); +#endif +} + +QGraphicsFrameCapture::~QGraphicsFrameCapture() +{ + +} + +void QGraphicsFrameCapture::setRhi(QRhi *rhi) +{ + if (!d.isNull()) + d->setRhi(rhi); +} + +void QGraphicsFrameCapture::startCaptureFrame() +{ + if (!d.isNull()) + d->startCaptureFrame(); +} + +void QGraphicsFrameCapture::endCaptureFrame() +{ + if (!d.isNull()) + d->endCaptureFrame(); +} + +QString QGraphicsFrameCapture::capturePath() const +{ + if (!d.isNull()) + return d->capturePath(); + return QString(); +} + +void QGraphicsFrameCapture::setCapturePath(const QString &path) +{ + if (!d.isNull()) + d->setCapturePath(path); +} + +QString QGraphicsFrameCapture::capturePrefix() const +{ + if (!d.isNull()) + return d->capturePrefix(); + return QString(); +} + +void QGraphicsFrameCapture::setCapturePrefix(const QString &prefix) +{ + if (!d.isNull()) + d->setCapturePrefix(prefix); +} + +QString QGraphicsFrameCapture::capturedFileName() +{ + if (!d.isNull()) + return d->capturedFileName(); + + return QString(); +} + +QStringList QGraphicsFrameCapture::capturedFilesNames() +{ + if (!d.isNull()) + return d->capturedFilesNames(); + + return QStringList(); +} + +bool QGraphicsFrameCapture::isLoaded() const +{ + if (!d.isNull()) + return d->initialized(); + + return false; +} + +bool QGraphicsFrameCapture::isCapturing() const +{ + if (!d.isNull()) + return d->isCapturing(); + + return false; +} + +void QGraphicsFrameCapture::openCapture() const +{ + if (!d.isNull()) + d->openCapture(); +} + +QT_END_NAMESPACE diff --git a/src/gui/util/qgraphicsframecapture_p.h b/src/gui/util/qgraphicsframecapture_p.h new file mode 100644 index 0000000000..fef37d2869 --- /dev/null +++ b/src/gui/util/qgraphicsframecapture_p.h @@ -0,0 +1,55 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QGRAPHICSFRAMECAPTURE_P_H +#define QGRAPHICSFRAMECAPTURE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qscopedpointer.h> +#include <QtGui/qtguiglobal.h> + +QT_BEGIN_NAMESPACE + +class QGraphicsFrameCapturePrivate; +class QRhi; + +class Q_GUI_EXPORT QGraphicsFrameCapture +{ +public: + QGraphicsFrameCapture(); + ~QGraphicsFrameCapture(); + + void setRhi(QRhi *rhi); + void startCaptureFrame(); + void endCaptureFrame(); + + QString capturePath() const; + void setCapturePath(const QString &path); + + QString capturePrefix() const; + void setCapturePrefix(const QString &prefix); + + QString capturedFileName(); + QStringList capturedFilesNames(); + + bool isLoaded() const; + bool isCapturing() const; + void openCapture() const; + +private: + QScopedPointer<QGraphicsFrameCapturePrivate> d; +}; + +QT_END_NAMESPACE + +#endif // QGRAPHICSFRAMECAPTURE_P_H diff --git a/src/gui/util/qgraphicsframecapture_p_p.h b/src/gui/util/qgraphicsframecapture_p_p.h new file mode 100644 index 0000000000..f8dbd2ca1d --- /dev/null +++ b/src/gui/util/qgraphicsframecapture_p_p.h @@ -0,0 +1,67 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QGRAPHICSFRAMECAPTURE_P_P_H +#define QGRAPHICSFRAMECAPTURE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qnamespace.h> +#include <QtCore/qstring.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qstringlist.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcGraphicsFrameCapture) + +class QRhi; +struct QRhiNativeHandles; + +class QGraphicsFrameCapturePrivate +{ +public: + QGraphicsFrameCapturePrivate() ; + virtual ~QGraphicsFrameCapturePrivate() = default; + + virtual void setRhi(QRhi *rhi) = 0; + virtual void startCaptureFrame() = 0; + virtual void endCaptureFrame() = 0; + + QString capturePath() const { return m_capturePath; }; + virtual void setCapturePath(const QString &path) { m_capturePath = path; } + + QString capturePrefix() const { return m_capturePrefix; } + virtual void setCapturePrefix(const QString &prefix) { m_capturePrefix = prefix; } + + virtual QString capturedFileName() const + { + return !m_capturedFilesNames.isEmpty() ? m_capturedFilesNames.last() : QString(); + } + virtual QStringList capturedFilesNames() const { return m_capturedFilesNames; } + + virtual bool initialized() const = 0; + virtual bool isCapturing() const = 0; + virtual void openCapture() = 0; + +protected: + QRhi *m_rhi = nullptr; + QRhiNativeHandles *m_rhiHandles = nullptr; + void *m_nativeHandle = nullptr; + QString m_capturePath; + QString m_capturePrefix; + QStringList m_capturedFilesNames; +}; + +QT_END_NAMESPACE + +#endif // QGRAPHICSFRAMECAPTURE_P_P_H diff --git a/src/gui/util/qgraphicsframecapturemetal.mm b/src/gui/util/qgraphicsframecapturemetal.mm new file mode 100644 index 0000000000..b0ff0bab2b --- /dev/null +++ b/src/gui/util/qgraphicsframecapturemetal.mm @@ -0,0 +1,169 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qgraphicsframecapturemetal_p_p.h" +#include <QtCore/qurl.h> +#include "Metal/Metal.h" +#include "qglobal.h" +#include <QtGui/rhi/qrhi.h> +#include <QtGui/rhi/qrhi_platform.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcGraphicsFrameCapture, "qt.gui.graphicsframecapture") + +#if __has_feature(objc_arc) +#error ARC not supported +#endif + +uint QGraphicsFrameCaptureMetal::frameNumber = 0; + +QGraphicsFrameCaptureMetal::QGraphicsFrameCaptureMetal() +{ + qputenv("METAL_CAPTURE_ENABLED", QByteArrayLiteral("1")); + + m_captureDescriptor = [MTLCaptureDescriptor new]; +} + +QGraphicsFrameCaptureMetal::~QGraphicsFrameCaptureMetal() +{ +#if defined(Q_OS_MACOS) && QT_CONFIG(process) + if (m_process) { + m_process->terminate(); + delete m_process; + } +#endif + [m_captureDescriptor release]; +} + +void QGraphicsFrameCaptureMetal::setRhi(QRhi *rhi) +{ + if (!rhi) + return; + + QRhi::Implementation backend = rhi->backend(); + const QRhiNativeHandles *nh = rhi->nativeHandles(); + + switch (backend) { + case QRhi::Implementation::Metal: { + const QRhiMetalNativeHandles *mtlnh = static_cast<const QRhiMetalNativeHandles *>(nh); + if (mtlnh->cmdQueue) { + m_captureDescriptor.captureObject = mtlnh->cmdQueue; + } else if (mtlnh->dev) { + m_captureDescriptor.captureObject = mtlnh->dev; + } else { + qCWarning(lcGraphicsFrameCapture) << "No valid Metal Device or Metal Command Queue found"; + m_initialized = false; + return; + } + break; + } + default: { + qCWarning(lcGraphicsFrameCapture) << "Invalid handles were provided. MTLCaptureManager works only with Metal API"; + m_initialized = false; + return; + break; + } + } + + if (!m_captureManager) { + m_captureManager = MTLCaptureManager.sharedCaptureManager; + bool supportDocs = [m_captureManager supportsDestination:MTLCaptureDestinationGPUTraceDocument]; + if (supportDocs) { + m_captureDescriptor.destination = MTLCaptureDestinationGPUTraceDocument; + m_initialized = true; + } + } +} + +void QGraphicsFrameCaptureMetal::startCaptureFrame() +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "Capturing on Metal was not initialized. Starting capturing can not be done."; + return; + } + + if (isCapturing()) { + qCWarning(lcGraphicsFrameCapture) << "A frame capture is already in progress," + "will not initiate another one until QGraphicsFrameCapture::endCaptureFrame is called."; + return; + } + + updateCaptureFileName(); + NSError *error; + if (![m_captureManager startCaptureWithDescriptor:m_captureDescriptor error:&error]) { + QString errorMsg = QString::fromNSString(error.localizedDescription); + qCWarning(lcGraphicsFrameCapture, "Failed to start capture : %s", qPrintable(errorMsg)); + } +} + +void QGraphicsFrameCaptureMetal::endCaptureFrame() +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "Capturing on Metal was not initialized. End capturing can not be done."; + return; + } + + if (!isCapturing()) { + qCWarning(lcGraphicsFrameCapture) << "A call to QGraphicsFrameCapture::endCaptureFrame can not be done" + " without a call to QGraphicsFrameCapture::startCaptureFrame"; + return; + } + + [m_captureManager stopCapture]; + m_capturedFilesNames.append(QString::fromNSString(m_traceURL.path)); + frameNumber++; +} + +bool QGraphicsFrameCaptureMetal::initialized() const +{ + return m_initialized; +} + +bool QGraphicsFrameCaptureMetal::isCapturing() const +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "Capturing on Metal was not initialized. Can not query if capturing is in progress or not."; + return false; + } + + return [m_captureManager isCapturing]; +} + +void QGraphicsFrameCaptureMetal::openCapture() +{ +#if defined(Q_OS_MACOS) +#if !QT_CONFIG(process) + qFatal("QGraphicsFrameCapture requires QProcess on macOS"); +#else + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "Capturing on Metal was not initialized. Can not open XCode with a valid capture."; + return; + } + + if (!m_process) { + m_process = new QProcess(); + m_process->setProgram(QStringLiteral("xed")); + QStringList args; + args.append(QUrl::fromNSURL(m_traceURL).toLocalFile()); + m_process->setArguments(args); + } + + m_process->kill(); + m_process->start(); +#endif +#endif +} + +void QGraphicsFrameCaptureMetal::updateCaptureFileName() +{ + m_traceURL = QUrl::fromLocalFile(m_capturePath + "/" + m_capturePrefix + "_" + QString::number(frameNumber) + ".gputrace").toNSURL(); + // We need to remove the trace file if it already existed else MTLCaptureManager + // will fail to. + if ([NSFileManager.defaultManager fileExistsAtPath:m_traceURL.path]) + [NSFileManager.defaultManager removeItemAtURL:m_traceURL error:nil]; + + m_captureDescriptor.outputURL = m_traceURL; +} + +QT_END_NAMESPACE diff --git a/src/gui/util/qgraphicsframecapturemetal_p_p.h b/src/gui/util/qgraphicsframecapturemetal_p_p.h new file mode 100644 index 0000000000..d703949de2 --- /dev/null +++ b/src/gui/util/qgraphicsframecapturemetal_p_p.h @@ -0,0 +1,57 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QGRAPHICSFRAMEDECAPTUREMETAL_P_P_H +#define QGRAPHICSFRAMEDECAPTUREMETAL_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicsframecapture_p_p.h" +#include <QtCore/qmutex.h> +#ifdef Q_OS_MACOS +#include <QtCore/qprocess.h> +#endif + +Q_FORWARD_DECLARE_OBJC_CLASS(MTLCaptureManager); +Q_FORWARD_DECLARE_OBJC_CLASS(MTLCaptureDescriptor); +Q_FORWARD_DECLARE_OBJC_CLASS(NSURL); + +QT_BEGIN_NAMESPACE + +class QGraphicsFrameCaptureMetal : public QGraphicsFrameCapturePrivate +{ +public: + QGraphicsFrameCaptureMetal(); + ~QGraphicsFrameCaptureMetal(); + + void setRhi(QRhi *rhi) override; + void startCaptureFrame() override; + void endCaptureFrame() override; + bool initialized() const override; + bool isCapturing() const override; + void openCapture() override; + +private: + void updateCaptureFileName(); +#if defined(Q_OS_MACOS) && QT_CONFIG(process) + QProcess *m_process = nullptr; +#endif + MTLCaptureManager *m_captureManager = nullptr; + MTLCaptureDescriptor *m_captureDescriptor = nullptr; + NSURL *m_traceURL = nullptr; + bool m_initialized = false; + static uint frameNumber; +}; + +QT_END_NAMESPACE + +#endif // QGRAPHICSFRAMEDECAPTUREMETAL_P_P_H diff --git a/src/gui/util/qgraphicsframecapturerenderdoc.cpp b/src/gui/util/qgraphicsframecapturerenderdoc.cpp new file mode 100644 index 0000000000..88ba9d839f --- /dev/null +++ b/src/gui/util/qgraphicsframecapturerenderdoc.cpp @@ -0,0 +1,310 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qgraphicsframecapturerenderdoc_p_p.h" +#include <QtCore/qcoreapplication.h> +#include <QtCore/qfile.h> +#include <QtCore/qlibrary.h> +#include <QtCore/qmutex.h> +#include "QtGui/rhi/qrhi.h" +#include "QtGui/rhi/qrhi_platform.h" +#include "QtGui/qopenglcontext.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcGraphicsFrameCapture, "qt.gui.graphicsframecapture") + +RENDERDOC_API_1_6_0 *QGraphicsFrameCaptureRenderDoc::s_rdocApi = nullptr; +#if QT_CONFIG(thread) +QBasicMutex QGraphicsFrameCaptureRenderDoc::s_frameCaptureMutex; +#endif + +#if QT_CONFIG(opengl) +static void *glNativeContext(QOpenGLContext *context) { + void *nctx = nullptr; + if (context != nullptr && context->isValid()) { +#ifdef Q_OS_WIN + nctx = context->nativeInterface<QNativeInterface::QWGLContext>()->nativeContext(); +#endif + +#ifdef Q_OS_LINUX +#if QT_CONFIG(egl) + QNativeInterface::QEGLContext *eglItf = context->nativeInterface<QNativeInterface::QEGLContext>(); + if (eglItf) + nctx = eglItf->nativeContext(); +#endif + +#if QT_CONFIG(xcb_glx_plugin) + QNativeInterface::QGLXContext *glxItf = context->nativeInterface<QNativeInterface::QGLXContext>(); + if (glxItf) + nctx = glxItf->nativeContext(); +#endif +#endif + +#if QT_CONFIG(metal) + nctx = context->nativeInterface<QNativeInterface::QCocoaGLContext>()->nativeContext(); +#endif + } + return nctx; +} +#endif // QT_CONFIG(opengl) + +/*! + \class QGraphicsFrameCaptureRenderDoc + \internal + \brief The QGraphicsFrameCaptureRenderDoc class provides a way to capture a record of draw calls + for different graphics APIs. + \since 6.6 + \inmodule QtGui + + For applications that render using graphics APIs like Vulkan or OpenGL, it would be + convenient to have a way to check the draw calls done by the application. Specially + for applications that make a large amount of draw calls and the output is different + from what is expected. + + This class acts as a wrapper over \l {https://renderdoc.org/}{RenderDoc} that allows + applications to capture a rendered frame either programmatically, by clicking a key + on the keyboard or both. The captured frame could be viewed later using RenderDoc GUI. + + Read the \l {https://renderdoc.org/docs/index.html} {RenderDoc Documentation} + for more information. + + \section1 API device handle + + The functions that capture a frame like QGraphicsFrameCaptureRenderDoc::startCaptureFrame takes a device + pointer as argument. This pointer is unique for each graphics API and is associated + with the window that will be captured. This pointer has a default value of \c nullptr. + If no value is passed to the function the underlying API will try to find the device to + use, but it is not guaranteed specially in a multi-window applications. + + For OpenGL, the pointer should be the OpenGL context on the platform OpenGL is being + used. For example, on Windows it should be \c HGLRC. + + For Vulkan, the pointer should point to the dispatch table within the \c VkInstance. +*/ + + + +/*! + Creates a new object of this class. The constructor will load RenderDoc library + from the default path. + + Only one instance of RenderDoc library is loaded at runtime which means creating + several instances of this class will not affect the RenderDoc initialization. +*/ + +QGraphicsFrameCaptureRenderDoc::QGraphicsFrameCaptureRenderDoc() + : m_nativeHandlesSet(false) +{ + if (!s_rdocApi) + init(); +} + +void QGraphicsFrameCaptureRenderDoc::setRhi(QRhi *rhi) +{ + if (!rhi) + return; + + QRhi::Implementation backend = rhi->backend(); + const QRhiNativeHandles *nh = rhi->nativeHandles(); + + switch (backend) { + case QRhi::Implementation::D3D11: { +#ifdef Q_OS_WIN + const QRhiD3D11NativeHandles *d3d11nh = static_cast<const QRhiD3D11NativeHandles *>(nh); + m_nativeHandle = d3d11nh->dev; + break; +#endif + qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for D3D11. Check platform support"; + break; + } + case QRhi::Implementation::D3D12: { +#ifdef Q_OS_WIN + const QRhiD3D12NativeHandles *d3d12nh = static_cast<const QRhiD3D12NativeHandles *>(nh); + m_nativeHandle = d3d12nh->dev; + break; +#endif + qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for D3D12. Check platform support"; + break; + } + case QRhi::Implementation::Vulkan: { +#if QT_CONFIG(vulkan) + const QRhiVulkanNativeHandles *vknh = static_cast<const QRhiVulkanNativeHandles *>(nh); + m_nativeHandle = RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(vknh->inst->vkInstance()); + break; +#endif + qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for Vulkan. Check platform support"; + break; + } + case QRhi::Implementation::OpenGLES2: { +#ifndef QT_NO_OPENGL + const QRhiGles2NativeHandles *glnh = static_cast<const QRhiGles2NativeHandles *>(nh); + m_nativeHandle = glNativeContext(glnh->context); + if (m_nativeHandle) + break; +#endif + qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for OpenGL. Check platform support"; + break; + } + case QRhi::Implementation::Metal: + case QRhi::Implementation::Null: + qCWarning(lcGraphicsFrameCapture) << "Invalid handles were provided." + " Metal and Null backends are not supported with RenderDoc"; + break; + } + + if (m_nativeHandle) + m_nativeHandlesSet = true; +} + +/*! + Starts a frame capture using the set native handles provided through QGraphicsFrameCaptureRenderDoc::setRhi + \a device. This function must be called before QGraphicsFrameCaptureRenderDoc::endCaptureFrame. + \sa {API device handle} +*/ +void QGraphicsFrameCaptureRenderDoc::startCaptureFrame() +{ + + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized." + " Starting capturing can not be done."; + return; + } + +#if QT_CONFIG(thread) + // There is a single instance of RenderDoc library and it needs mutex for multithreading access. + QMutexLocker locker(&s_frameCaptureMutex); +#endif + if (s_rdocApi->IsFrameCapturing()) { + qCWarning(lcGraphicsFrameCapture) << "A frame capture is already in progress, " + "will not initiate another one until" + " QGraphicsFrameCapture::endCaptureFrame is called."; + return; + } + + qCInfo(lcGraphicsFrameCapture) << "A frame capture is going to start."; + updateCapturePathAndTemplate(); + s_rdocApi->StartFrameCapture(m_nativeHandle, nullptr); +} + +/*! + Ends a frame capture started by a call to QGraphicsFrameCaptureRenderDoc::startCaptureFrame + using the set native handles provided through QGraphicsFrameCaptureRenderDoc::setRhi. + This function must be called after QGraphicsFrameCaptureRenderDoc::startCaptureFrame. + Otherwise, a warning message will be printend and nothing will happen. + \sa {API device handle} +*/ +void QGraphicsFrameCaptureRenderDoc::endCaptureFrame() +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized." + " End capturing can not be done."; + return; + } + +#if QT_CONFIG(thread) + // There is a single instance of RenderDoc library and it needs mutex for multithreading access. + QMutexLocker locker(&s_frameCaptureMutex); +#endif + if (!s_rdocApi->IsFrameCapturing()) { + qCWarning(lcGraphicsFrameCapture) << "A call to QGraphicsFrameCapture::endCaptureFrame can not be done" + " without a call to QGraphicsFrameCapture::startCaptureFrame"; + return; + } + + qCInfo(lcGraphicsFrameCapture) << "A frame capture is going to end."; + uint32_t result = s_rdocApi->EndFrameCapture(m_nativeHandle, nullptr); + + if (result) { + uint32_t count = s_rdocApi->GetNumCaptures(); + uint32_t pathLength = 0; + s_rdocApi->GetCapture(count - 1, nullptr, &pathLength, nullptr); + if (pathLength > 0) { + QVarLengthArray<char> name(pathLength, 0); + s_rdocApi->GetCapture(count - 1, name.data(), &pathLength, nullptr); + m_capturedFilesNames.append(QString::fromUtf8(name.data(), -1)); + } + } +} + +void QGraphicsFrameCaptureRenderDoc::updateCapturePathAndTemplate() +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized." + " Updating save location can not be done."; + return; + } + + + QString rdocFilePathTemplate = m_capturePath + QStringLiteral("/") + m_capturePrefix; + s_rdocApi->SetCaptureFilePathTemplate(rdocFilePathTemplate.toUtf8().constData()); +} + +/*! + Returns true if the API is loaded and can capture frames or not. +*/ +bool QGraphicsFrameCaptureRenderDoc::initialized() const +{ + return s_rdocApi && m_nativeHandlesSet; +} + +bool QGraphicsFrameCaptureRenderDoc::isCapturing() const +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized." + " Can not query if capturing is in progress or not."; + return false; + } + + return s_rdocApi->IsFrameCapturing(); +} + +void QGraphicsFrameCaptureRenderDoc::openCapture() +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized." + " Can not open RenderDoc UI tool."; + return; + } + +#if QT_CONFIG(thread) + // There is a single instance of RenderDoc library and it needs mutex for multithreading access. + QMutexLocker locker(&s_frameCaptureMutex); +#endif + if (s_rdocApi->IsTargetControlConnected()) + s_rdocApi->ShowReplayUI(); + else + s_rdocApi->LaunchReplayUI(1, nullptr); +} + +void QGraphicsFrameCaptureRenderDoc::init() +{ +#if QT_CONFIG(thread) + // There is a single instance of RenderDoc library and it needs mutex for multithreading access. + QMutexLocker locker(&s_frameCaptureMutex); +#endif + + QLibrary renderDocLib(QStringLiteral("renderdoc")); + pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI) renderDocLib.resolve("RENDERDOC_GetAPI"); + if (!renderDocLib.isLoaded() || (RENDERDOC_GetAPI == nullptr)) { + qCWarning(lcGraphicsFrameCapture) << renderDocLib.errorString().toLatin1(); + return; + } + + int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, static_cast<void **>(static_cast<void *>(&s_rdocApi))); + + if (ret == 0) { + qCWarning(lcGraphicsFrameCapture) << "The requested RenderDoc API is invalid or not supported"; + return; + } + + s_rdocApi->MaskOverlayBits(RENDERDOC_OverlayBits::eRENDERDOC_Overlay_None, + RENDERDOC_OverlayBits::eRENDERDOC_Overlay_None); + s_rdocApi->SetCaptureKeys(nullptr, 0); + s_rdocApi->SetFocusToggleKeys(nullptr, 0); + + QString rdocFilePathTemplate = m_capturePath + QStringLiteral("/") + m_capturePrefix; + s_rdocApi->SetCaptureFilePathTemplate(rdocFilePathTemplate.toUtf8().constData()); +} + +QT_END_NAMESPACE diff --git a/src/gui/util/qgraphicsframecapturerenderdoc_p_p.h b/src/gui/util/qgraphicsframecapturerenderdoc_p_p.h new file mode 100644 index 0000000000..c0d92ea733 --- /dev/null +++ b/src/gui/util/qgraphicsframecapturerenderdoc_p_p.h @@ -0,0 +1,53 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QGRAPHICSFRAMECAPTURERENDERDOC_P_P_H +#define QGRAPHICSFRAMECAPTURERENDERDOC_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + + +#include <renderdoc_app.h> +#include "qgraphicsframecapture_p_p.h" + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(thread) +class QBasicMutex; +#endif + +class QGraphicsFrameCaptureRenderDoc : public QGraphicsFrameCapturePrivate +{ +public: + QGraphicsFrameCaptureRenderDoc(); + ~QGraphicsFrameCaptureRenderDoc() = default; + + void setRhi(QRhi *rhi) override; + void startCaptureFrame() override; + void endCaptureFrame() override; + bool initialized() const override; + bool isCapturing() const override; + void openCapture() override; + +private: + void init(); + void updateCapturePathAndTemplate(); + static RENDERDOC_API_1_5_0 *s_rdocApi; +#if QT_CONFIG(thread) + static QBasicMutex s_frameCaptureMutex; +#endif + bool m_nativeHandlesSet; +}; + +QT_END_NAMESPACE + +#endif // QGRAPHICSFRAMECAPTURERENDERDOC_P_P_H diff --git a/src/gui/util/qgridlayoutengine.cpp b/src/gui/util/qgridlayoutengine.cpp index 1cc42d3260..07981e8388 100644 --- a/src/gui/util/qgridlayoutengine.cpp +++ b/src/gui/util/qgridlayoutengine.cpp @@ -13,6 +13,8 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +#define LAYOUTITEMSIZE_MAX (1 << 24) + template<typename T> static void insertOrRemoveItems(QList<T> &items, int index, int delta) { @@ -194,7 +196,8 @@ void QGridLayoutRowData::calculateGeometries(int start, int end, qreal targetSiz sumAvailable = targetSize - totalBox.q_minimumSize; if (sumAvailable > 0.0) { - qreal sumDesired = totalBox.q_preferredSize - totalBox.q_minimumSize; + const qreal totalBox_preferredSize = qMin(totalBox.q_preferredSize, qreal(LAYOUTITEMSIZE_MAX)); + qreal sumDesired = totalBox_preferredSize - totalBox.q_minimumSize; for (int i = 0; i < n; ++i) { if (ignore.testBit(start + i)) { @@ -203,7 +206,8 @@ void QGridLayoutRowData::calculateGeometries(int start, int end, qreal targetSiz } const QGridLayoutBox &box = boxes.at(start + i); - qreal desired = box.q_preferredSize - box.q_minimumSize; + const qreal box_preferredSize = qMin(box.q_preferredSize, qreal(LAYOUTITEMSIZE_MAX)); + qreal desired = box_preferredSize - box.q_minimumSize; factors[i] = growthFactorBelowPreferredSize(desired, sumAvailable, sumDesired); sumFactors += factors[i]; } @@ -716,7 +720,7 @@ void QGridLayoutItem::dump(int indent) const if (q_alignment != 0) qDebug("%*s Alignment: %x", indent, "", uint(q_alignment)); qDebug("%*s Horizontal size policy: %x Vertical size policy: %x", - indent, "", sizePolicy(Qt::Horizontal), sizePolicy(Qt::Vertical)); + indent, "", (unsigned int)sizePolicy(Qt::Horizontal), (unsigned int)sizePolicy(Qt::Vertical)); } #endif @@ -758,6 +762,8 @@ QGridLayoutEngine::QGridLayoutEngine(Qt::Alignment defaultAlignment, bool snapTo m_visualDirection = Qt::LeftToRight; m_defaultAlignment = defaultAlignment; m_snapToPixelGrid = snapToPixelGrid; + m_uniformCellWidths = false; + m_uniformCellHeights = false; invalidate(); } @@ -875,6 +881,34 @@ qreal QGridLayoutEngine::rowSizeHint(Qt::SizeHint which, int row, Qt::Orientatio return q_infos[orientation].boxes.value(row).q_sizes(which); } +bool QGridLayoutEngine::uniformCellWidths() const +{ + return m_uniformCellWidths; +} + +void QGridLayoutEngine::setUniformCellWidths(bool uniformCellWidths) +{ + if (m_uniformCellWidths == uniformCellWidths) + return; + + m_uniformCellWidths = uniformCellWidths; + invalidate(); +} + +bool QGridLayoutEngine::uniformCellHeights() const +{ + return m_uniformCellHeights; +} + +void QGridLayoutEngine::setUniformCellHeights(bool uniformCellHeights) +{ + if (m_uniformCellHeights == uniformCellHeights) + return; + + m_uniformCellHeights = uniformCellHeights; + invalidate(); +} + void QGridLayoutEngine::setRowAlignment(int row, Qt::Alignment alignment, Qt::Orientation orientation) { @@ -930,8 +964,11 @@ void QGridLayoutEngine::insertItem(QGridLayoutItem *item, int index) for (int i = item->firstRow(); i <= item->lastRow(); ++i) { for (int j = item->firstColumn(); j <= item->lastColumn(); ++j) { - if (itemAt(i, j)) - qWarning("QGridLayoutEngine::addItem: Cell (%d, %d) already taken", i, j); + const auto existingItem = itemAt(i, j); + if (existingItem) { + qWarning("QGridLayoutEngine::addItem: Can't add %s at cell (%d, %d) because it's already taken by %s", + qPrintable(item->toString()), i, j, qPrintable(existingItem->toString())); + } setItemAt(i, j, item); } } @@ -1145,7 +1182,7 @@ void QGridLayoutEngine::dump(int indent) const { qDebug("%*sEngine", indent, ""); - qDebug("%*s Items (%d)", indent, "", q_items.count()); + qDebug("%*s Items (%lld)", indent, "", q_items.count()); int i; for (i = 0; i < q_items.count(); ++i) q_items.at(i)->dump(indent + 2); @@ -1499,6 +1536,27 @@ void QGridLayoutEngine::fillRowData(QGridLayoutRowData *rowData, rowSpacing = qMax(windowMargin, rowSpacing); } } + + if (rowData->boxes.size() > 1 && + ((orientation == Qt::Horizontal && m_uniformCellWidths) || + (orientation == Qt::Vertical && m_uniformCellHeights))) { + qreal averagePreferredSize = 0.; + qreal minimumMaximumSize = std::numeric_limits<qreal>::max(); + qreal maximumMinimumSize = 0.; + for (const auto &box : rowData->boxes) { + averagePreferredSize += box.q_preferredSize; + minimumMaximumSize = qMin(minimumMaximumSize, box.q_maximumSize); + maximumMinimumSize = qMax(maximumMinimumSize, box.q_minimumSize); + } + averagePreferredSize /= rowData->boxes.size(); + minimumMaximumSize = qMax(minimumMaximumSize, maximumMinimumSize); + averagePreferredSize = qBound(maximumMinimumSize, averagePreferredSize, minimumMaximumSize); + for (auto &box : rowData->boxes) { + box.q_preferredSize = averagePreferredSize; + box.q_minimumSize = maximumMinimumSize; + box.q_maximumSize = minimumMaximumSize; + } + } } void QGridLayoutEngine::ensureEffectiveFirstAndLastRows() const diff --git a/src/gui/util/qgridlayoutengine_p.h b/src/gui/util/qgridlayoutengine_p.h index 8344d488c1..2f60cb99fd 100644 --- a/src/gui/util/qgridlayoutengine_p.h +++ b/src/gui/util/qgridlayoutengine_p.h @@ -24,6 +24,7 @@ #include <QtCore/qpair.h> #include <QtCore/qsize.h> #include <QtCore/qrect.h> +#include <QtCore/qdebug.h> #include <float.h> #include "qlayoutpolicy_p.h" @@ -283,6 +284,8 @@ public: virtual QLayoutPolicy::ControlTypes controlTypes(LayoutSide side) const; + inline virtual QString toString() const { return QDebug::toString(this); } + QRectF geometryWithin(qreal x, qreal y, qreal width, qreal height, qreal rowDescent, Qt::Alignment align, bool snapToPixelGrid) const; QGridLayoutBox box(Qt::Orientation orientation, bool snapToPixelGrid, qreal constraint = -1.0) const; @@ -334,6 +337,12 @@ public: qreal rowSizeHint(Qt::SizeHint which, int row, Qt::Orientation orientation = Qt::Vertical) const; + bool uniformCellWidths() const; + void setUniformCellWidths(bool uniformCellWidths); + + bool uniformCellHeights() const; + void setUniformCellHeights(bool uniformCellHeights); + void setRowAlignment(int row, Qt::Alignment alignment, Qt::Orientation orientation); Qt::Alignment rowAlignment(int row, Qt::Orientation orientation) const; @@ -415,6 +424,8 @@ private: // Configuration Qt::Alignment m_defaultAlignment; unsigned m_snapToPixelGrid : 1; + unsigned m_uniformCellWidths : 1; + unsigned m_uniformCellHeights : 1; // Lazily computed from the above user input mutable QHVContainer<int> q_cachedEffectiveFirstRows; diff --git a/src/gui/util/qktxhandler.cpp b/src/gui/util/qktxhandler.cpp index f04da929c3..35f1df1185 100644 --- a/src/gui/util/qktxhandler.cpp +++ b/src/gui/util/qktxhandler.cpp @@ -41,7 +41,7 @@ struct KTXHeader { quint32 bytesOfKeyValueData; }; -static const quint32 qktxh_headerSize = sizeof(KTXHeader); +static constexpr quint32 qktxh_headerSize = sizeof(KTXHeader); // Currently unused, declared for future reference struct KTXKeyValuePairItem { @@ -71,11 +71,24 @@ struct KTXMipmapLevel { */ }; -// Returns the nearest multiple of 'rounding' greater than or equal to 'value' -constexpr quint32 withPadding(quint32 value, quint32 rounding) +// Returns the nearest multiple of 4 greater than or equal to 'value' +static const std::optional<quint32> nearestMultipleOf4(quint32 value) { - Q_ASSERT(rounding > 1); - return value + (rounding - 1) - ((value + (rounding - 1)) % rounding); + constexpr quint32 rounding = 4; + quint32 result = 0; + if (qAddOverflow(value, rounding - 1, &result)) + return std::nullopt; + result &= ~(rounding - 1); + return result; +} + +// Returns a view with prechecked bounds +static QByteArrayView safeView(QByteArrayView view, quint32 start, quint32 length) +{ + quint32 end = 0; + if (qAddOverflow(start, length, &end) || end > quint32(view.length())) + return {}; + return view.sliced(start, length); } QKtxHandler::~QKtxHandler() = default; @@ -83,8 +96,7 @@ QKtxHandler::~QKtxHandler() = default; bool QKtxHandler::canRead(const QByteArray &suffix, const QByteArray &block) { Q_UNUSED(suffix); - - return (qstrncmp(block.constData(), ktxIdentifier, KTX_IDENTIFIER_LENGTH) == 0); + return block.startsWith(ktxIdentifier); } QTextureFileData QKtxHandler::read() @@ -93,55 +105,122 @@ QTextureFileData QKtxHandler::read() return QTextureFileData(); const QByteArray buf = device()->readAll(); - const quint32 dataSize = quint32(buf.size()); - if (dataSize < qktxh_headerSize || !canRead(QByteArray(), buf)) { - qCDebug(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData()); + if (static_cast<size_t>(buf.size()) > std::numeric_limits<quint32>::max()) { + qWarning(lcQtGuiTextureIO, "Too big KTX file %s", logName().constData()); + return QTextureFileData(); + } + + if (!canRead(QByteArray(), buf)) { + qWarning(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData()); return QTextureFileData(); } - const KTXHeader *header = reinterpret_cast<const KTXHeader *>(buf.data()); - if (!checkHeader(*header)) { - qCDebug(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData()); + if (buf.size() < qsizetype(qktxh_headerSize)) { + qWarning(lcQtGuiTextureIO, "Invalid KTX header size in %s", logName().constData()); + return QTextureFileData(); + } + + KTXHeader header; + memcpy(&header, buf.data(), qktxh_headerSize); + if (!checkHeader(header)) { + qWarning(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData()); return QTextureFileData(); } QTextureFileData texData; texData.setData(buf); - texData.setSize(QSize(decode(header->pixelWidth), decode(header->pixelHeight))); - texData.setGLFormat(decode(header->glFormat)); - texData.setGLInternalFormat(decode(header->glInternalFormat)); - texData.setGLBaseInternalFormat(decode(header->glBaseInternalFormat)); + texData.setSize(QSize(decode(header.pixelWidth), decode(header.pixelHeight))); + texData.setGLFormat(decode(header.glFormat)); + texData.setGLInternalFormat(decode(header.glInternalFormat)); + texData.setGLBaseInternalFormat(decode(header.glBaseInternalFormat)); - texData.setNumLevels(decode(header->numberOfMipmapLevels)); - texData.setNumFaces(decode(header->numberOfFaces)); + texData.setNumLevels(decode(header.numberOfMipmapLevels)); + texData.setNumFaces(decode(header.numberOfFaces)); + + const quint32 bytesOfKeyValueData = decode(header.bytesOfKeyValueData); + quint32 headerKeyValueSize; + if (qAddOverflow(qktxh_headerSize, bytesOfKeyValueData, &headerKeyValueSize)) { + qWarning(lcQtGuiTextureIO, "Overflow in size of key value data in header of KTX file %s", + logName().constData()); + return QTextureFileData(); + } - const quint32 bytesOfKeyValueData = decode(header->bytesOfKeyValueData); - if (qktxh_headerSize + bytesOfKeyValueData < quint64(buf.size())) // oob check - texData.setKeyValueMetadata(decodeKeyValues( - QByteArrayView(buf.data() + qktxh_headerSize, bytesOfKeyValueData))); - quint32 offset = qktxh_headerSize + bytesOfKeyValueData; + if (headerKeyValueSize >= quint32(buf.size())) { + qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData()); + return QTextureFileData(); + } + + // File contains key/values + if (bytesOfKeyValueData > 0) { + auto keyValueDataView = safeView(buf, qktxh_headerSize, bytesOfKeyValueData); + if (keyValueDataView.isEmpty()) { + qWarning(lcQtGuiTextureIO, "Invalid view in KTX file %s", logName().constData()); + return QTextureFileData(); + } + + auto keyValues = decodeKeyValues(keyValueDataView); + if (!keyValues) { + qWarning(lcQtGuiTextureIO, "Could not parse key values in KTX file %s", + logName().constData()); + return QTextureFileData(); + } + + texData.setKeyValueMetadata(*keyValues); + } + + // Technically, any number of levels is allowed but if the value is bigger than + // what is possible in KTX V2 (and what makes sense) we return an error. + // maxLevels = log2(max(width, height, depth)) + const int maxLevels = (sizeof(quint32) * 8) + - qCountLeadingZeroBits(std::max( + { header.pixelWidth, header.pixelHeight, header.pixelDepth })); + + if (texData.numLevels() > maxLevels) { + qWarning(lcQtGuiTextureIO, "Too many levels in KTX file %s", logName().constData()); + return QTextureFileData(); + } - constexpr int MAX_ITERATIONS = 32; // cap iterations in case of corrupt data + if (texData.numFaces() != 1 && texData.numFaces() != 6) { + qWarning(lcQtGuiTextureIO, "Invalid number of faces in KTX file %s", logName().constData()); + return QTextureFileData(); + } - for (int level = 0; level < qMin(texData.numLevels(), MAX_ITERATIONS); level++) { - if (offset + sizeof(quint32) > dataSize) // Corrupt file; avoid oob read - break; + quint32 offset = headerKeyValueSize; + for (int level = 0; level < texData.numLevels(); level++) { + const auto imageSizeView = safeView(buf, offset, sizeof(quint32)); + if (imageSizeView.isEmpty()) { + qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData()); + return QTextureFileData(); + } - const quint32 imageSize = decode(qFromUnaligned<quint32>(buf.data() + offset)); - offset += sizeof(quint32); + const quint32 imageSize = decode(qFromUnaligned<quint32>(imageSizeView.data())); + offset += sizeof(quint32); // overflow checked indirectly above - for (int face = 0; face < qMin(texData.numFaces(), MAX_ITERATIONS); face++) { + for (int face = 0; face < texData.numFaces(); face++) { texData.setDataOffset(offset, level, face); texData.setDataLength(imageSize, level, face); // Add image data and padding to offset - offset += withPadding(imageSize, 4); + const auto padded = nearestMultipleOf4(imageSize); + if (!padded) { + qWarning(lcQtGuiTextureIO, "Overflow in KTX file %s", logName().constData()); + return QTextureFileData(); + } + + quint32 offsetNext; + if (qAddOverflow(offset, *padded, &offsetNext)) { + qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData()); + return QTextureFileData(); + } + + offset = offsetNext; } } if (!texData.isValid()) { - qCDebug(lcQtGuiTextureIO, "Invalid values in header of KTX file %s", logName().constData()); + qWarning(lcQtGuiTextureIO, "Invalid values in header of KTX file %s", + logName().constData()); return QTextureFileData(); } @@ -187,33 +266,83 @@ bool QKtxHandler::checkHeader(const KTXHeader &header) return is2D && (isCubeMap || isCompressedImage); } -QMap<QByteArray, QByteArray> QKtxHandler::decodeKeyValues(QByteArrayView view) const +std::optional<QMap<QByteArray, QByteArray>> QKtxHandler::decodeKeyValues(QByteArrayView view) const { QMap<QByteArray, QByteArray> output; quint32 offset = 0; - while (offset < view.size() + sizeof(quint32)) { + while (offset < quint32(view.size())) { + const auto keyAndValueByteSizeView = safeView(view, offset, sizeof(quint32)); + if (keyAndValueByteSizeView.isEmpty()) { + qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value"); + return std::nullopt; + } + const quint32 keyAndValueByteSize = - decode(qFromUnaligned<quint32>(view.constData() + offset)); - offset += sizeof(quint32); + decode(qFromUnaligned<quint32>(keyAndValueByteSizeView.data())); - if (offset + keyAndValueByteSize > quint64(view.size())) - break; // oob read + quint32 offsetKeyAndValueStart; + if (qAddOverflow(offset, quint32(sizeof(quint32)), &offsetKeyAndValueStart)) { + qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); + return std::nullopt; + } + + quint32 offsetKeyAndValueEnd; + if (qAddOverflow(offsetKeyAndValueStart, keyAndValueByteSize, &offsetKeyAndValueEnd)) { + qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); + return std::nullopt; + } + + const auto keyValueView = safeView(view, offsetKeyAndValueStart, keyAndValueByteSize); + if (keyValueView.isEmpty()) { + qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value"); + return std::nullopt; + } // 'key' is a UTF-8 string ending with a null terminator, 'value' is the rest. // To separate the key and value we convert the complete data to utf-8 and find the first // null terminator from the left, here we split the data into two. - const auto str = QString::fromUtf8(view.constData() + offset, keyAndValueByteSize); - const int idx = str.indexOf('\0'_L1); - if (idx == -1) - continue; - - const QByteArray key = str.left(idx).toUtf8(); - const size_t keySize = key.size() + 1; // Actual data size - const QByteArray value = QByteArray::fromRawData(view.constData() + offset + keySize, - keyAndValueByteSize - keySize); - - offset = withPadding(offset + keyAndValueByteSize, 4); - output.insert(key, value); + + const int idx = keyValueView.indexOf('\0'); + if (idx == -1) { + qWarning(lcQtGuiTextureIO, "Invalid key in KTX key-value"); + return std::nullopt; + } + + const QByteArrayView keyView = safeView(view, offsetKeyAndValueStart, idx); + if (keyView.isEmpty()) { + qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); + return std::nullopt; + } + + const quint32 keySize = idx + 1; // Actual data size + + quint32 offsetValueStart; + if (qAddOverflow(offsetKeyAndValueStart, keySize, &offsetValueStart)) { + qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); + return std::nullopt; + } + + quint32 valueSize; + if (qSubOverflow(keyAndValueByteSize, keySize, &valueSize)) { + qWarning(lcQtGuiTextureIO, "Underflow in KTX key-value"); + return std::nullopt; + } + + const QByteArrayView valueView = safeView(view, offsetValueStart, valueSize); + if (valueView.isEmpty()) { + qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value"); + return std::nullopt; + } + + output.insert(keyView.toByteArray(), valueView.toByteArray()); + + const auto offsetNext = nearestMultipleOf4(offsetKeyAndValueEnd); + if (!offsetNext) { + qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); + return std::nullopt; + } + + offset = *offsetNext; } return output; diff --git a/src/gui/util/qktxhandler_p.h b/src/gui/util/qktxhandler_p.h index 7d54b20922..3a0b8fcf7e 100644 --- a/src/gui/util/qktxhandler_p.h +++ b/src/gui/util/qktxhandler_p.h @@ -17,6 +17,8 @@ #include "qtexturefilehandler_p.h" +#include <optional> + QT_BEGIN_NAMESPACE struct KTXHeader; @@ -33,7 +35,7 @@ public: private: bool checkHeader(const KTXHeader &header); - QMap<QByteArray, QByteArray> decodeKeyValues(QByteArrayView view) const; + std::optional<QMap<QByteArray, QByteArray>> decodeKeyValues(QByteArrayView view) const; quint32 decode(quint32 val) const; bool inverseEndian = false; diff --git a/src/gui/util/qvalidator.cpp b/src/gui/util/qvalidator.cpp index 8b0f9ac321..2a81006657 100644 --- a/src/gui/util/qvalidator.cpp +++ b/src/gui/util/qvalidator.cpp @@ -362,34 +362,45 @@ static qlonglong pow10(int exp) return result; } -QValidator::State QIntValidator::validate(QString & input, int&) const +template <typename T> static inline +std::optional<QValidator::State> initialResultCheck(T min, T max, const ParsingResult &result) { - QByteArray buff; - if (!locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, &buff, -1, - locale().numberOptions())) { - return Invalid; - } + if (result.state == ParsingResult::Invalid) + return QValidator::Invalid; + const CharBuff &buff = result.buff; if (buff.isEmpty()) - return Intermediate; + return QValidator::Intermediate; - const bool startsWithMinus(buff[0] == '-'); - if (b >= 0 && startsWithMinus) - return Invalid; + char ch = buff[0]; + const bool signConflicts = (min >= 0 && ch == '-') || (max < 0 && ch == '+'); + if (signConflicts) + return QValidator::Invalid; - const bool startsWithPlus(buff[0] == '+'); - if (t < 0 && startsWithPlus) - return Invalid; + if (result.state == ParsingResult::Intermediate) + return QValidator::Intermediate; - if (buff.size() == 1 && (startsWithPlus || startsWithMinus)) - return Intermediate; + return std::nullopt; +} - bool ok; - qlonglong entered = QLocaleData::bytearrayToLongLong(buff, 10, &ok); - if (!ok) +QValidator::State QIntValidator::validate(QString & input, int&) const +{ + ParsingResult result = + locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, -1, + locale().numberOptions()); + + std::optional<State> opt = initialResultCheck(b, t, result); + if (opt) + return *opt; + + const CharBuff &buff = result.buff; + QSimpleParsedNumber r = QLocaleData::bytearrayToLongLong(buff, 10); + if (!r.ok()) return Invalid; + qint64 entered = r.result; if (entered >= b && entered <= t) { + bool ok = false; locale().toInt(input, &ok); return ok ? Acceptable : Intermediate; } @@ -401,7 +412,7 @@ QValidator::State QIntValidator::validate(QString & input, int&) const // of a number of digits equal to or less than the max value as intermediate. int buffLength = buff.size(); - if (startsWithPlus) + if (buff[0] == '+') buffLength--; const int tLength = t != 0 ? static_cast<int>(std::log10(qAbs(t))) + 1 : 1; @@ -414,15 +425,15 @@ QValidator::State QIntValidator::validate(QString & input, int&) const /*! \reimp */ void QIntValidator::fixup(QString &input) const { - QByteArray buff; - if (!locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, &buff, -1, - locale().numberOptions())) { + auto [parseState, buff] = + locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, -1, + locale().numberOptions()); + if (parseState == ParsingResult::Invalid) return; - } - bool ok; - qlonglong entered = QLocaleData::bytearrayToLongLong(buff, 10, &ok); - if (ok) - input = locale().toString(entered); + + QSimpleParsedNumber r = QLocaleData::bytearrayToLongLong(buff, 10); + if (r.ok()) + input = locale().toString(r.result); } /*! @@ -646,19 +657,12 @@ QValidator::State QDoubleValidator::validate(QString & input, int &) const QValidator::State QDoubleValidatorPrivate::validateWithLocale(QString &input, QLocaleData::NumberMode numMode, const QLocale &locale) const { Q_Q(const QDoubleValidator); - QByteArray buff; - if (!locale.d->m_data->validateChars(input, numMode, &buff, q->dec, locale.numberOptions())) { - return QValidator::Invalid; - } + ParsingResult result = + locale.d->m_data->validateChars(input, numMode, q->dec, locale.numberOptions()); - if (buff.isEmpty()) - return QValidator::Intermediate; - - if (q->b >= 0 && buff.startsWith('-')) - return QValidator::Invalid; - - if (q->t < 0 && buff.startsWith('+')) - return QValidator::Invalid; + std::optional<QValidator::State> opt = initialResultCheck(q->b, q->t, result); + if (opt) + return *opt; bool ok = false; double i = locale.toDouble(input, &ok); // returns 0.0 if !ok @@ -737,15 +741,16 @@ void QDoubleValidatorPrivate::fixupWithLocale(QString &input, QLocaleData::Numbe const QLocale &locale) const { Q_Q(const QDoubleValidator); - QByteArray buff; // Passing -1 as the number of decimals, because fixup() exists to improve // an Intermediate value, if it can. - if (!locale.d->m_data->validateChars(input, numMode, &buff, -1, locale.numberOptions())) + auto [parseState, buff] = + locale.d->m_data->validateChars(input, numMode, -1, locale.numberOptions()); + if (parseState == ParsingResult::Invalid) return; - // buff now contains data in C locale. + // buff contains data in C locale. bool ok = false; - const double entered = buff.toDouble(&ok); + const double entered = QByteArrayView(buff).toDouble(&ok); if (ok) { // Here we need to adjust the output format accordingly char mode; @@ -769,7 +774,7 @@ void QDoubleValidatorPrivate::fixupWithLocale(QString &input, QLocaleData::Numbe if (eIndex < 0) eIndex = buff.size(); precision = eIndex - (buff.contains('.') ? 1 : 0) - - (buff.startsWith('-') || buff.startsWith('+') ? 1 : 0); + - (buff[0] == '-' || buff[0] == '+' ? 1 : 0); } // Use q->dec to limit the number of decimals, because we want the // fixup() result to pass validate(). |