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 ¶meters, const AsyncImageRenderCallback::InterlacingState state) override; 0073 void startRenderingAsync(const QVariant ¶meters); 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 ¶meters, 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