Perform CGAL evaluation in a separate thread. First steps towards better GUI responsiveness and parallelization

felipesanches-svg
Marius Kintel 2011-12-25 23:00:30 +01:00
parent 3e64e63b01
commit 7c48b345b1
8 changed files with 174 additions and 123 deletions

View File

@ -249,7 +249,8 @@ HEADERS += src/cgal.h \
src/CGALCache.h \
src/PolySetCGALEvaluator.h \
src/CGALRenderer.h \
src/CGAL_Nef_polyhedron.h
src/CGAL_Nef_polyhedron.h \
src/cgalworker.h
SOURCES += src/cgalutils.cc \
src/CGALEvaluator.cc \
@ -258,7 +259,8 @@ SOURCES += src/cgalutils.cc \
src/CGALRenderer.cc \
src/CGAL_Nef_polyhedron.cc \
src/CGAL_Nef_polyhedron_DxfData.cc \
src/cgaladv_minkowski2.cc
src/cgaladv_minkowski2.cc \
src/cgalworker.cc
}
macx {

View File

@ -9,6 +9,7 @@
#include "Tree.h"
#include "memory.h"
#include <vector>
#include <QMutex>
class MainWindow : public QMainWindow, public Ui::MainWindow
{
@ -67,9 +68,7 @@ private slots:
void updateTVal();
void setFileName(const QString &filename);
void setFont(const QString &family, uint size);
#ifdef USE_PROGRESSWIDGET
void showProgress();
#endif
private:
void openFile(const QString &filename);
@ -80,9 +79,7 @@ private:
bool maybeSave();
bool checkModified();
QString dumpCSGTree(AbstractNode *root);
static void consoleOutput(const std::string &msg, void *userdata) {
static_cast<MainWindow*>(userdata)->console->append(QString::fromStdString(msg));
}
static void consoleOutput(const std::string &msg, void *userdata);
void loadViewSettings();
void loadDesignSettings();
@ -110,6 +107,7 @@ private slots:
void actionCompile();
#ifdef ENABLE_CGAL
void actionRenderCGAL();
void actionRenderCGALDone(class CGAL_Nef_polyhedron *);
#endif
void actionDisplayAST();
void actionDisplayCSGTree();
@ -163,6 +161,13 @@ public slots:
void actionReloadCompile();
void checkAutoReload();
void autoReloadSet(bool);
private:
static void report_func(const class AbstractNode*, void *vp, int mark);
class ProgressWidget *progresswidget;
class CGALWorker *cgalworker;
QMutex consolemutex;
};
class GuiLocker
@ -175,6 +180,8 @@ public:
gui_locked--;
}
static bool isLocked() { return gui_locked > 0; }
static void lock() { gui_locked++; }
static void unlock() { gui_locked--; }
private:
static unsigned int gui_locked;

View File

@ -39,7 +39,11 @@
<enum>Qt::Vertical</enum>
</property>
<widget class="GLView" name="glview" native="true"/>
<widget class="QTextEdit" name="console"/>
<widget class="QTextEdit" name="console">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</widget>
</item>
<item>

View File

@ -5,7 +5,11 @@ ProgressWidget::ProgressWidget(QWidget *parent)
:QWidget(parent)
{
setupUi(this);
setRange(0, 100);
setValue(0);
this->wascanceled = false;
this->starttime.start();
connect(this->stopButton, SIGNAL(clicked()), this, SLOT(cancel()));
QTimer::singleShot(1000, this, SIGNAL(requestShow()));
}
@ -15,6 +19,14 @@ bool ProgressWidget::wasCanceled() const
return this->wascanceled;
}
/*!
Returns milliseconds since this widget was created
*/
int ProgressWidget::elapsedTime() const
{
return this->starttime.elapsed();
}
void ProgressWidget::cancel()
{
this->wascanceled = true;

View File

@ -2,15 +2,17 @@
#define PROGRESSWIDGET_H_
#include "ui_ProgressWidget.h"
#include <QTime>
class ProgressWidget : public QWidget, public Ui::ProgressWidget
{
Q_OBJECT;
Q_PROPERTY(bool wasCanceled READ wasCanceled);
public:
ProgressWidget(QWidget *parent = NULL);
bool wasCanceled() const;
int elapsedTime() const;
public slots:
void setRange(int minimum, int maximum);
@ -23,6 +25,7 @@ signals:
private:
bool wascanceled;
QTime starttime;
};
#endif

40
src/cgalworker.cc Normal file
View File

@ -0,0 +1,40 @@
#include "cgalworker.h"
#include <QThread>
#include "Tree.h"
#include "CGALEvaluator.h"
#include "progress.h"
#include "printutils.h"
CGALWorker::CGALWorker()
{
this->thread = new QThread();
connect(this->thread, SIGNAL(started()), this, SLOT(work()));
moveToThread(this->thread);
}
CGALWorker::~CGALWorker()
{
delete this->thread;
}
void CGALWorker::start(const Tree &tree)
{
this->tree = &tree;
this->thread->start();
}
void CGALWorker::work()
{
CGAL_Nef_polyhedron *root_N = NULL;
try {
CGALEvaluator evaluator(*this->tree);
root_N = new CGAL_Nef_polyhedron(evaluator.evaluateCGALMesh(*this->tree->root()));
}
catch (ProgressCancelException e) {
PRINT("Rendering cancelled.");
}
emit done(root_N);
thread->quit();
}

28
src/cgalworker.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef CGALWORKER_H_
#define CGALWORKER_H_
#include <QObject>
class CGALWorker : public QObject
{
Q_OBJECT;
public:
CGALWorker();
virtual ~CGALWorker();
public slots:
void start(const class Tree &tree);
protected slots:
void work();
signals:
void done(class CGAL_Nef_polyhedron *);
protected:
class QThread *thread;
const class Tree *tree;
};
#endif

View File

@ -41,9 +41,7 @@
#include "CSGTermEvaluator.h"
#include "OpenCSGRenderer.h"
#endif
#ifdef USE_PROGRESSWIDGET
#include "ProgressWidget.h"
#endif
#include "ThrownTogetherRenderer.h"
#include <QMenu>
@ -52,8 +50,6 @@
#include <QSplitter>
#include <QFileDialog>
#include <QApplication>
#include <QProgressDialog>
#include <QProgressBar>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
@ -67,6 +63,8 @@
#include <QMessageBox>
#include <QDesktopServices>
#include <QSettings>
#include <QProgressDialog>
#include <QMutexLocker>
#ifdef _QCODE_EDIT_
#include "qdocument.h"
#include "qformatscheme.h"
@ -89,6 +87,7 @@ using namespace boost::lambda;
#include "CGALRenderer.h"
#include "CGAL_Nef_polyhedron.h"
#include "cgal.h"
#include "cgalworker.h"
#endif // ENABLE_CGAL
@ -140,9 +139,13 @@ settings_valueList(const QString &key, const QList<int> &defaultList = QList<int
}
MainWindow::MainWindow(const QString &filename)
: progresswidget(NULL)
{
setupUi(this);
this->cgalworker = new CGALWorker();
connect(this->cgalworker, SIGNAL(done(CGAL_Nef_polyhedron *)), this, SLOT(actionRenderCGALDone(CGAL_Nef_polyhedron *)));
register_builtin(root_ctx);
this->openglbox = NULL;
@ -316,7 +319,6 @@ MainWindow::MainWindow(const QString &filename)
connect(this->helpActionOpenGLInfo, SIGNAL(triggered()), this, SLOT(helpOpenGL()));
console->setReadOnly(true);
setCurrentOutput();
PRINT(helptitle);
@ -421,32 +423,23 @@ MainWindow::~MainWindow()
#endif
}
#ifdef USE_PROGRESSWIDGET
void MainWindow::showProgress()
{
this->statusBar()->addPermanentWidget(qobject_cast<ProgressWidget*>(sender()));
}
#endif
static void report_func(const class AbstractNode*, void *vp, int mark)
void MainWindow::report_func(const class AbstractNode*, void *vp, int mark)
{
#ifdef USE_PROGRESSWIDGET
ProgressWidget *pw = static_cast<ProgressWidget*>(vp);
MainWindow *thisp = static_cast<MainWindow*>(vp);
int v = (int)((mark*100.0) / progress_report_count);
int percent = v < 100 ? v : 99;
if (percent > pw->value()) pw->setValue(percent);
QApplication::processEvents();
if (pw->wasCanceled()) throw ProgressCancelException();
#else
QProgressDialog *pd = static_cast<QProgressDialog*>(vp);
int v = (int)((mark*100.0) / progress_report_count);
pd->setValue(v < 100 ? v : 99);
QString label;
label.sprintf("Rendering Polygon Mesh (%d/%d)", mark, progress_report_count);
pd->setLabelText(label);
QApplication::processEvents();
if (pd->wasCanceled()) throw ProgressCancelException();
#endif
if (percent > thisp->progresswidget->value()) {
QMetaObject::invokeMethod(thisp->progresswidget, "setValue", Qt::QueuedConnection,
Q_ARG(int, percent));
}
if (thisp->progresswidget->wasCanceled()) throw ProgressCancelException();
}
/*!
@ -740,21 +733,11 @@ void MainWindow::compileCSG(bool procevents)
QTime t;
t.start();
#ifdef USE_PROGRESSWIDGET
ProgressWidget *pd = new ProgressWidget(this);
pd->setRange(0, 100);
pd->setValue(0);
connect(pd, SIGNAL(requestShow()), this, SLOT(showProgress()));
#else
QProgressDialog *pd = new QProgressDialog("Rendering CSG products...", "Cancel", 0, 100);
pd->setRange(0, 100);
pd->setValue(0);
pd->setAutoClose(false);
pd->show();
#endif
this->progresswidget = new ProgressWidget(this);
connect(this->progresswidget, SIGNAL(requestShow()), this, SLOT(showProgress()));
QApplication::processEvents();
progress_report_prep(root_node, report_func, pd);
progress_report_prep(root_node, report_func, this);
try {
CGALEvaluator cgalevaluator(this->tree);
PolySetCGALEvaluator psevaluator(cgalevaluator);
@ -772,10 +755,9 @@ void MainWindow::compileCSG(bool procevents)
PRINT("CSG generation cancelled.");
}
progress_report_fin();
#ifdef USE_PROGRESSWIDGET
this->statusBar()->removeWidget(pd);
#endif
delete pd;
this->statusBar()->removeWidget(this->progresswidget);
delete this->progresswidget;
this->progresswidget = NULL;
if (root_raw_term) {
PRINT("Compiling design (CSG Products normalization)...");
@ -1150,7 +1132,7 @@ void MainWindow::actionCompile()
void MainWindow::actionRenderCGAL()
{
if (GuiLocker::isLocked()) return;
GuiLocker lock;
GuiLocker::lock();
setCurrentOutput();
console->clear();
@ -1172,88 +1154,52 @@ void MainWindow::actionRenderCGAL()
PRINT("Rendering Polygon Mesh using CGAL...");
QApplication::processEvents();
QTime t;
t.start();
#ifdef USE_PROGRESSWIDGET
ProgressWidget *pd = new ProgressWidget(this);
pd->setRange(0, 100);
pd->setValue(0);
connect(pd, SIGNAL(requestShow()), this, SLOT(showProgress()));
#else
QProgressDialog *pd = new QProgressDialog("Rendering Polygon Mesh using CGAL...", "Cancel", 0, 100);
pd->setRange(0, 100);
pd->setValue(0);
pd->setAutoClose(false);
pd->show();
#endif
this->progresswidget = new ProgressWidget(this);
connect(this->progresswidget, SIGNAL(requestShow()), this, SLOT(showProgress()));
QApplication::processEvents();
progress_report_prep(this->root_node, report_func, pd);
try {
CGALEvaluator evaluator(this->tree);
this->root_N = new CGAL_Nef_polyhedron(evaluator.evaluateCGALMesh(*this->root_node));
PolySetCache::instance()->print();
CGALCache::instance()->print();
}
catch (ProgressCancelException e) {
PRINT("Rendering cancelled.");
}
progress_report_prep(this->root_node, report_func, this);
this->cgalworker->start(this->tree);
}
void MainWindow::actionRenderCGALDone(CGAL_Nef_polyhedron *root_N)
{
progress_report_fin();
if (this->root_N)
{
// FIXME: Reenable cache cost info
// PRINTF("Number of vertices currently in CGAL cache: %d", AbstractNode::cgal_nef_cache.totalCost());
// PRINTF("Number of objects currently in CGAL cache: %d", AbstractNode::cgal_nef_cache.size());
QApplication::processEvents();
if (root_N) {
PolySetCache::instance()->print();
CGALCache::instance()->print();
if (this->root_N->dim == 2) {
if (root_N->dim == 2) {
PRINTF(" Top level object is a 2D object:");
QApplication::processEvents();
PRINTF(" Empty: %6s", this->root_N->p2->is_empty() ? "yes" : "no");
QApplication::processEvents();
PRINTF(" Plane: %6s", this->root_N->p2->is_plane() ? "yes" : "no");
QApplication::processEvents();
PRINTF(" Vertices: %6d", (int)this->root_N->p2->explorer().number_of_vertices());
QApplication::processEvents();
PRINTF(" Halfedges: %6d", (int)this->root_N->p2->explorer().number_of_halfedges());
QApplication::processEvents();
PRINTF(" Edges: %6d", (int)this->root_N->p2->explorer().number_of_edges());
QApplication::processEvents();
PRINTF(" Faces: %6d", (int)this->root_N->p2->explorer().number_of_faces());
QApplication::processEvents();
PRINTF(" FaceCycles: %6d", (int)this->root_N->p2->explorer().number_of_face_cycles());
QApplication::processEvents();
PRINTF(" ConnComp: %6d", (int)this->root_N->p2->explorer().number_of_connected_components());
QApplication::processEvents();
PRINTF(" Empty: %6s", root_N->p2->is_empty() ? "yes" : "no");
PRINTF(" Plane: %6s", root_N->p2->is_plane() ? "yes" : "no");
PRINTF(" Vertices: %6d", (int)root_N->p2->explorer().number_of_vertices());
PRINTF(" Halfedges: %6d", (int)root_N->p2->explorer().number_of_halfedges());
PRINTF(" Edges: %6d", (int)root_N->p2->explorer().number_of_edges());
PRINTF(" Faces: %6d", (int)root_N->p2->explorer().number_of_faces());
PRINTF(" FaceCycles: %6d", (int)root_N->p2->explorer().number_of_face_cycles());
PRINTF(" ConnComp: %6d", (int)root_N->p2->explorer().number_of_connected_components());
}
if (this->root_N->dim == 3) {
if (root_N->dim == 3) {
PRINTF(" Top level object is a 3D object:");
PRINTF(" Simple: %6s", this->root_N->p3->is_simple() ? "yes" : "no");
QApplication::processEvents();
PRINTF(" Valid: %6s", this->root_N->p3->is_valid() ? "yes" : "no");
QApplication::processEvents();
PRINTF(" Vertices: %6d", (int)this->root_N->p3->number_of_vertices());
QApplication::processEvents();
PRINTF(" Halfedges: %6d", (int)this->root_N->p3->number_of_halfedges());
QApplication::processEvents();
PRINTF(" Edges: %6d", (int)this->root_N->p3->number_of_edges());
QApplication::processEvents();
PRINTF(" Halffacets: %6d", (int)this->root_N->p3->number_of_halffacets());
QApplication::processEvents();
PRINTF(" Facets: %6d", (int)this->root_N->p3->number_of_facets());
QApplication::processEvents();
PRINTF(" Volumes: %6d", (int)this->root_N->p3->number_of_volumes());
QApplication::processEvents();
PRINTF(" Simple: %6s", root_N->p3->is_simple() ? "yes" : "no");
PRINTF(" Valid: %6s", root_N->p3->is_valid() ? "yes" : "no");
PRINTF(" Vertices: %6d", (int)root_N->p3->number_of_vertices());
PRINTF(" Halfedges: %6d", (int)root_N->p3->number_of_halfedges());
PRINTF(" Edges: %6d", (int)root_N->p3->number_of_edges());
PRINTF(" Halffacets: %6d", (int)root_N->p3->number_of_halffacets());
PRINTF(" Facets: %6d", (int)root_N->p3->number_of_facets());
PRINTF(" Volumes: %6d", (int)root_N->p3->number_of_volumes());
}
int s = t.elapsed() / 1000;
int s = this->progresswidget->elapsedTime() / 1000;
PRINTF("Total rendering time: %d hours, %d minutes, %d seconds", s / (60*60), (s / 60) % 60, s % 60);
this->root_N = root_N;
if (!this->root_N->empty()) {
this->cgalRenderer = new CGALRenderer(*this->root_N);
// Go to CGAL view mode
@ -1271,11 +1217,12 @@ void MainWindow::actionRenderCGAL()
}
}
#ifdef USE_PROGRESSWIDGET
this->statusBar()->removeWidget(pd);
#endif
delete pd;
this->statusBar()->removeWidget(this->progresswidget);
delete this->progresswidget;
this->progresswidget = NULL;
clearCurrentOutput();
GuiLocker::unlock();
}
#endif /* ENABLE_CGAL */
@ -1827,6 +1774,15 @@ void MainWindow::quit()
if (ev.isAccepted()) QApplication::instance()->quit();
}
void MainWindow::consoleOutput(const std::string &msg, void *userdata)
{
// Invoke the append function in the main thread in case the output
// originates in a worker thread.
MainWindow *thisp = static_cast<MainWindow*>(userdata);
QMetaObject::invokeMethod(thisp->console, "append", Qt::QueuedConnection,
Q_ARG(QString, QString::fromStdString(msg)));
}
void MainWindow::setCurrentOutput()
{
set_output_handler(&MainWindow::consoleOutput, this);
@ -1836,4 +1792,3 @@ void MainWindow::clearCurrentOutput()
{
set_output_handler(NULL, NULL);
}