From 5ca04e5b2ec8eef88df6cbd4e3d09ac7dab55c0d Mon Sep 17 00:00:00 2001 From: Julian T Date: Fri, 14 Aug 2020 18:53:08 +0200 Subject: Added multithreading --- app/mainwindow.cpp | 7 +++ app/rendercoord.cpp | 127 ++++++++++++++++++++++++++++++++++++++++++++++------ app/rendercoord.hpp | 27 ++++++++--- 3 files changed, 141 insertions(+), 20 deletions(-) diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index a365f38..3fcc9c5 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -18,9 +18,16 @@ MainWindow::MainWindow(Renderer r) saveAct->setStatusTip(tr("Save the rendered image")); connect(saveAct, &QAction::triggered, this, &MainWindow::saveimage); + auto stopAct = new QAction(tr("&Stop"), this); + stopAct->setStatusTip(tr("Stop and sync threads")); + QObject::connect(stopAct, &QAction::triggered, &m_render, &RenderCoordinator::stop); + fileMenu = menuBar()->addMenu(tr("&File")); fileMenu->addAction(saveAct); + fileMenu = menuBar()->addMenu(tr("&Render")); + fileMenu->addAction(stopAct); + helpMenu = menuBar()->addMenu(tr("&Help")); helpMenu->addAction(tr("About Qt"), qApp, &QApplication::aboutQt); diff --git a/app/rendercoord.cpp b/app/rendercoord.cpp index a5a3da5..fd325e3 100644 --- a/app/rendercoord.cpp +++ b/app/rendercoord.cpp @@ -1,8 +1,10 @@ #include "rendercoord.hpp" +#include #include #include #include +#include #include #include @@ -16,12 +18,14 @@ uint32_t colorToUint32(const Color &c) { } // Run by main thread -RenderThread::RenderThread(Renderer r, QObject *parent, unsigned id) +RenderThread::RenderThread(Renderer r, unsigned threads, QObject *parent, unsigned id) : QThread(parent), m_lock(1), - m_render(r) + m_render(r), + m_pause(1) { m_id = id; + m_workers = threads; } // Run on new thread @@ -35,17 +39,21 @@ void RenderThread::run() { m_current_samples = 0; - for (unsigned sample = 1; sample < m_samples+1; sample++) { - for (unsigned x = 0; x < m_render.m_width; x++) { - for (unsigned y = 0; y < m_render.m_height; y++) { - auto index = x + y * m_render.m_height; + for (int sample = 0; sample < m_samples; sample++) { + m_current_samples = sample; + // Probably not that smart + m_pause.acquire(); + m_pause.release(); + + for (unsigned y = m_id; y < m_render.m_height; y += m_workers) { + for (unsigned x = 0; x < m_render.m_width; x++) { + auto index = x + y * m_render.m_width; sum[index] += m_render.render(m_render.m_width - x, m_render.m_height - y, 1); - m_writebuffer[index] = colorToUint32(sum[index] / sample); + m_writebuffer[index] = colorToUint32(sum[index] / (sample+1)); } } - m_current_samples = sample; } // Signal done @@ -54,6 +62,14 @@ void RenderThread::run() { } } +void RenderThread::pause() { + m_pause.acquire(); +} + +void RenderThread::resume() { + m_pause.release(); +} + int RenderThread::render(QRgb *buffer, unsigned samples) { // Check if already running if (!m_lock.tryAcquire()) { @@ -66,6 +82,15 @@ int RenderThread::render(QRgb *buffer, unsigned samples) { return 0; } +unsigned RenderThread::stop() { + stopAt(m_current_samples); + return m_current_samples; +} + +void RenderThread::stopAt(int at) { + m_samples = at; +} + // Running on main thread unsigned RenderThread::current_samples() { // No sync should not be a problem here. @@ -76,17 +101,30 @@ RenderCoordinator::RenderCoordinator(QObject *parent, DrawWidget &target, Render : QObject(parent), m_target(target), m_renderer(r), - m_worker(m_renderer, this), m_timer(this) { m_status = status; - m_worker.start(); + // Create and start workers + for (int i = 0; i < 4; i++) { + auto thread = new RenderThread(m_renderer, 4, this, i); + + thread->start(); + QObject::connect(thread, &RenderThread::done, this, &RenderCoordinator::workerDone); + + m_workers.push_back(thread); + } + + render(); - QObject::connect(&m_worker, &RenderThread::done, - this, &RenderCoordinator::workerDone); +} - m_worker.render(target.m_drawbuffer, 100); +void RenderCoordinator::render() { + m_started = 0; + for (auto thd : m_workers) { + thd->render(m_target.m_drawbuffer, 20); + m_started++; + } m_state = running; updateUi(); @@ -94,15 +132,66 @@ RenderCoordinator::RenderCoordinator(QObject *parent, DrawWidget &target, Render QObject::connect(&m_timer, &QTimer::timeout, this, &RenderCoordinator::updateUi); m_timer.start(500); +} + +void RenderCoordinator::stop() { + unsigned max = 0; + for (auto thd : m_workers) { + thd->pause(); + auto val = thd->current_samples(); + if (val>max) { + max = val; + } + } + + std::cout << max << std::endl; + + for (auto thd : m_workers) { + thd->stopAt(max+1); + thd->resume(); + } + + m_state = stopping; + updateUi(); } void RenderCoordinator::workerDone(unsigned workerid) { + std::cout << "Worker " << workerid << " done!" << std::endl; + if (--m_started) { + return; + } + std::cout << "All done :-)" << std::endl; + + // All workers are done m_state = stopped; m_timer.stop(); updateUi(); } +unsigned RenderCoordinator::calcStats(unsigned *max, unsigned *min, double *avg) { + unsigned count = 0; + unsigned sum = 0; + for (auto thd : m_workers) { + auto val = thd->current_samples(); + if (min && (val < *min || !count)) { + *min = val; + } + if (max && (val > *max || !count)) { + *max = val; + } + + sum += val; + count++; + } + + if (avg) { + *avg = (double)sum / count; + } + + return count; +} + void RenderCoordinator::updateUi() { m_target.repaint(); @@ -111,8 +200,18 @@ void RenderCoordinator::updateUi() { return; } + // Gather statictics from workers + unsigned max; + unsigned min; + double avg; + unsigned count = calcStats(&max, &min, &avg); + std::ostringstream status; - status << states[m_state] << " " << m_worker.current_samples() << " samples"; + status << states[m_state] << + " Threads: " << count << + " Max: " << max << " samples" << + " Min: " << min << " samples" << + " Avg: " << avg << " samples"; m_status->setText(QString::fromStdString(status.str())); } diff --git a/app/rendercoord.hpp b/app/rendercoord.hpp index 113b4dc..a749f8a 100644 --- a/app/rendercoord.hpp +++ b/app/rendercoord.hpp @@ -2,6 +2,7 @@ #define RENDER_THREAD_H #include "draw.hpp" +#include #include #include @@ -16,10 +17,17 @@ class RenderThread : public QThread { Q_OBJECT public: - RenderThread(Renderer r, QObject *parent = nullptr, unsigned id = 0); + RenderThread(Renderer r, unsigned threads, QObject *parent = nullptr, unsigned id = 0); // Returns 0 if successful or 1 if busy int render(QRgb *buffer, unsigned samples); + + void pause(); + void resume(); + + unsigned stop(); + void stopAt(int at); + unsigned current_samples(); signals: @@ -31,18 +39,21 @@ class RenderThread : public QThread { QSemaphore m_lock; QRgb *m_writebuffer; - unsigned m_samples; - unsigned m_current_samples; + std::atomic_int m_samples; + std::atomic_int m_current_samples; Renderer m_render; + unsigned m_workers; + // Value in here means work is to be done QSemaphore m_work; + QSemaphore m_pause; unsigned m_id; }; -const std::string states[] = { "Stopped", "Running" }; -enum State { stopped, running }; +const std::string states[] = { "Stopped", "Running", "Stopping" }; +enum State { stopped, running, stopping }; class RenderCoordinator : public QObject { Q_OBJECT @@ -54,15 +65,19 @@ class RenderCoordinator : public QObject { public slots: void workerDone(unsigned workerid); + void stop(); private slots: void updateUi(); private: + unsigned calcStats(unsigned *max, unsigned *min, double *avg); + DrawWidget &m_target; Renderer m_renderer; - RenderThread m_worker; + std::vector m_workers; + unsigned m_started; QLabel *m_status; QTimer m_timer; -- cgit v1.2.3