File indexing completed on 2024-05-12 04:44:29

0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT
0003 
0004 #ifndef ASYNCIMAGERENDERTHREAD_H
0005 #define ASYNCIMAGERENDERTHREAD_H
0006 
0007 #include "asyncimagerendercallback.h"
0008 #include <atomic>
0009 #include <functional>
0010 #include <qglobal.h>
0011 #include <qmutex.h>
0012 #include <qthread.h>
0013 #include <qvariant.h>
0014 #include <qwaitcondition.h>
0015 
0016 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0017 #include <qtmetamacros.h>
0018 #else
0019 #include <qobjectdefs.h>
0020 #include <qstring.h>
0021 #endif
0022 
0023 class QImage;
0024 class QObject;
0025 
0026 namespace PerceptualColor
0027 {
0028 /** @internal
0029  *
0030  * @brief Provides threaded rendering for @ref AsyncImageProvider. */
0031 class AsyncImageRenderThread : public QThread, public AsyncImageRenderCallback
0032 {
0033     Q_OBJECT
0034 
0035 public:
0036     /** @brief Function pointer to a render function.
0037      *
0038      * The function pointed to by this pointer has <tt>void</tt> as its
0039      * return value. It has the following parameters:
0040      *
0041      * @param variantParameters A <tt>QVariant</tt> that contains the
0042      *        image parameters.
0043      * @param callbackObject An object that provides the necessary
0044      *        callbacks.
0045      *
0046      * The function pointed to by this pointer is supposed to
0047      * render the image with the given parameters, and deliver the
0048      * result of each interlacing pass and also the final result by
0049      * callbacks. It also is supposed to check regularly via callbacks
0050      * if it should abort the rendering.
0051      *
0052      * The function pointed to by this pointer must be thread-safe.
0053      *
0054      * @internal
0055      *
0056      * The render function is meant to be used
0057      * by @ref AsyncImageRenderThread.
0058      *
0059      * @note It might be possible to use <tt>
0060      * <a href="https://en.cppreference.com/w/cpp/utility/any">std::any</a>
0061      * </tt> instead of <tt><a href="https://doc.qt.io/qt-6/qvariant.html">
0062      * QVariant</a></tt>. This might eliminate the need to register
0063      * types to the Qt meta-type system. On the other hand, it
0064      * probably will not integrate as well with other parts of Qt
0065      * (signals, slots…). So currently we are doing well by using <tt>
0066      * <a href="https://doc.qt.io/qt-6/qvariant.html"> QVariant</a></tt>. */
0067     using pointerToRenderFunction = std::function<void(const QVariant &variantParameters, AsyncImageRenderCallback &callbackObject)>;
0068 
0069     explicit AsyncImageRenderThread(const pointerToRenderFunction &renderFunction, QObject *parent = nullptr);
0070     virtual ~AsyncImageRenderThread() override;
0071 
0072     virtual void deliverInterlacingPass(const QImage &image, const QVariant &parameters, const AsyncImageRenderCallback::InterlacingState state) override;
0073     void startRenderingAsync(const QVariant &parameters);
0074     [[nodiscard]] virtual bool shouldAbort() const override;
0075     void waitForIdle();
0076 
0077 Q_SIGNALS:
0078     /** @brief Result of an interlacing pass of the <em>rendering</em>
0079      * operation.
0080      *
0081      * <em>Rendering</em> operations can be started
0082      * by @ref startRenderingAsync().
0083      *
0084      * @note <em>Rendering</em> operations might be stopped before emitting
0085      * this signal by calling again @ref startRenderingAsync(); therefore it
0086      * is <em>not</em> guaranteed that each call of @ref startRenderingAsync()
0087      * will finally emit this signal.
0088      *
0089      * @param image The image
0090      * @param parameters The parameters of the image
0091      * @param state The interlacing state of the image. A render function
0092      * must first return zero or more images with intermediate state. After
0093      * that, it must return exactly one image with final state (unless it
0094      * was aborted). After that, it must not return any more images.
0095      *
0096      * @warning This signal can be emitted by a thread other than the
0097      * thread in which this object itself lives. Therefore, use only
0098      * <tt>Qt::AutoConnection</tt> or <tt>Qt::QueuedConnection</tt>
0099      * when connecting to this signal. */
0100     void interlacingPassCompleted(const QImage &image, const QVariant &parameters, const PerceptualColor::AsyncImageRenderCallback::InterlacingState state);
0101 
0102 protected:
0103     virtual void run() override;
0104 
0105 private:
0106     Q_DISABLE_COPY(AsyncImageRenderThread)
0107 
0108     /** @internal @brief Only for unit tests. */
0109     friend class TestAsyncImageRenderThread;
0110 
0111     /** @brief Provide parameters for the next re(start) of @ref run().
0112      *
0113      * @ref run() is supposed to read these parameters on each round,
0114      * and to render a corresponding image.
0115      *
0116      * @note This data member has read and write access protected
0117      * by @ref m_loopMutex. */
0118     QVariant m_imageParameters;
0119     /** @brief Request @ref run() to abort.
0120      *
0121      * @ref run() is supposed to control regularly if this value
0122      * is <tt>true</tt>. If so, it should return as fast as possible.
0123      * This variable is used by the destructor to make sure that the
0124      * associated thread is stopped before destroying this object.
0125      *
0126      * @warning This is used with @ref m_loopCondition. See there for details.
0127      *
0128      * @note This data member has write access protected
0129      * by @ref m_loopMutex. */
0130     std::atomic_bool m_loopAbort = false;
0131     /** @brief Wait condition used between the rendering rounds.
0132      *
0133      * @warning @ref m_loopAbort and @ref m_loopRestart are used to control the
0134      * waiting. Changing them requires locking @ref m_loopMutex (otherwise,
0135      * this condition could become out-of-synchronization). Reading them
0136      * during the rendering to stop more immediately should be okay, as
0137      * both variables are atomic.
0138      *
0139      * @note See
0140      * <a href="https://www.heise.de/developer/artikel/C-Core-Guidelines-Sei-dir-der-Fallen-von-Bedingungsvariablen-bewusst-4063822.html">
0141      * this in-depth explication</a> or also
0142      * <a href="https://www.grimm-jaud.de/index.php/blog/bedingungsvariablen">
0143      * this other in-depth explication</a>, both of Rainer Grimm, for
0144      * more details about this synchronization pattern. */
0145     QWaitCondition m_loopCondition;
0146     /** @brief Mutex protection for @ref m_loopAbort and @ref m_loopRestart
0147      * and @ref m_imageParameters.
0148      *
0149      * @warning This is used with @ref m_loopCondition. See there for details. */
0150     QMutex m_loopMutex;
0151     /** @brief Request @ref run() to restart its outer loop.
0152      *
0153      * @ref run() is supposed to control regularly if this value is
0154      * <tt>true</tt>. If so, it should restart its outer loop as fast as
0155      * possible. This variable is set by @ref startRenderingAsync() to
0156      * <tt>true</tt> to make sure that the outer loop restarts, and it is set
0157      * by @ref run() to <tt>false</tt> once the restart of the outer loop
0158      * has happened.
0159      *
0160      * @warning This is used with @ref m_loopCondition. See there for details.
0161      *
0162      * @note This data member has write access protected
0163      * by @ref m_loopMutex. */
0164     std::atomic_bool m_loopRestart = false;
0165     /** @brief Function pointer to the function that does the
0166      * actual rendering. */
0167     const pointerToRenderFunction m_renderFunction;
0168     /** @brief Wait condition to wait until this thread goes to sleep. */
0169     QWaitCondition m_syncCondition;
0170     /** @brief Is <tt>true</tt> if the render thread is either sleeping
0171      * or not yet started at all. */
0172     std::atomic_bool m_syncIsIdle = true;
0173     /** @brief Mutex protection for @ref m_syncCondition */
0174     QMutex m_syncMutex;
0175 };
0176 
0177 } // namespace PerceptualColor
0178 
0179 #endif // ASYNCIMAGERENDERTHREAD_H