begin rendering rework

Thu, 11 Jan 2018 15:09:44 +0200

author
Santeri Piippo
date
Thu, 11 Jan 2018 15:09:44 +0200
changeset 1231
ce0c9f2e6b9c
parent 1230
29dc03eceb5f
child 1232
7eb8b59577d0

begin rendering rework

src/basics.h file | annotate | diff | comparison | revisions
src/dialogs.cpp file | annotate | diff | comparison | revisions
src/dialogs/configdialog.cpp file | annotate | diff | comparison | revisions
src/documentmanager.cpp file | annotate | diff | comparison | revisions
src/format.h file | annotate | diff | comparison | revisions
src/glCompiler.cpp file | annotate | diff | comparison | revisions
src/glCompiler.h file | annotate | diff | comparison | revisions
src/glRenderer.cpp file | annotate | diff | comparison | revisions
src/glRenderer.h file | annotate | diff | comparison | revisions
src/guiutilities.cpp file | annotate | diff | comparison | revisions
src/guiutilities.h file | annotate | diff | comparison | revisions
src/ldDocument.cpp file | annotate | diff | comparison | revisions
src/ldDocument.h file | annotate | diff | comparison | revisions
src/ldObject.cpp file | annotate | diff | comparison | revisions
src/main.h file | annotate | diff | comparison | revisions
src/mainwindow.cpp file | annotate | diff | comparison | revisions
src/mainwindow.h file | annotate | diff | comparison | revisions
src/mainwindow.ui file | annotate | diff | comparison | revisions
src/toolsets/basictoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/filetoolset.cpp file | annotate | diff | comparison | revisions
src/toolsets/viewtoolset.cpp file | annotate | diff | comparison | revisions
--- a/src/basics.h	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/basics.h	Thu Jan 11 15:09:44 2018 +0200
@@ -18,6 +18,7 @@
 
 #pragma once
 #include <cmath>
+#include <QSize>
 #include <QString>
 #include <QObject>
 #include <QStringList>
--- a/src/dialogs.cpp	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/dialogs.cpp	Thu Jan 11 15:09:44 2018 +0200
@@ -60,7 +60,7 @@
 		{ ui->right,  ERightCamera }
 	};
 
-	ECamera cam = g_win->renderer()->camera();
+	ECamera cam = g_win->currentRenderer()->camera();
 
 	if (cam == EFreeCamera)
 		cam = ETopCamera;
@@ -85,7 +85,7 @@
 // =============================================================================
 void OverlayDialog::fillDefaults(int newcam)
 {
-	LDGLOverlay& info = g_win->renderer()->getOverlay(newcam);
+	LDGLOverlay& info = g_win->currentRenderer()->getOverlay(newcam);
 	RadioDefault<int>(newcam, m_cameraArgs);
 
 	if (info.img)
--- a/src/dialogs/configdialog.cpp	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/dialogs/configdialog.cpp	Thu Jan 11 15:09:44 2018 +0200
@@ -297,7 +297,7 @@
 	m_window->syncSettings();
 	currentDocument()->reloadAllSubfiles();
 	m_documents->loadLogoedStuds();
-	m_window->renderer()->setBackground();
+	m_window->currentRenderer()->setBackground();
 	m_window->doFullRefresh();
 	m_window->updateDocumentList();
 }
--- a/src/documentmanager.cpp	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/documentmanager.cpp	Thu Jan 11 15:09:44 2018 +0200
@@ -352,7 +352,6 @@
 	if (m_loadingMainFile)
 	{
 		m_window->changeDocument(load);
-		m_window->renderer()->setDocument(load);
 		print(tr("File %1 parsed successfully(%2 errors)."), path, numWarnings);
 	}
 
--- a/src/format.h	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/format.h	Thu Jan 11 15:09:44 2018 +0200
@@ -162,6 +162,12 @@
 }
 
 
+inline QString toString(const QSize& size)
+{
+	return toString(size.width()) + " × " + toString(size.height());
+}
+
+
 /*
  * Formats the message with the given args using QString::arg().
  */
--- a/src/glCompiler.cpp	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/glCompiler.cpp	Thu Jan 11 15:09:44 2018 +0200
@@ -25,10 +25,12 @@
 #include "ldDocument.h"
 #include "miscallenous.h"
 #include "glRenderer.h"
-#include "dialogs.h"
 #include "guiutilities.h"
 #include "documentmanager.h"
 
+static const QColor bfcFrontColor {64, 192, 80};
+static const QColor bfcBackColor {208, 64, 64};
+
 struct GLErrorInfo
 {
 	GLenum	value;
@@ -49,8 +51,6 @@
 
 ConfigOption(QString SelectColorBlend = "#0080FF")
 
-// static QMap<LDObject*, String> g_objectOrigins;
-
 void CheckGLErrorImpl(const char* file, int line)
 {
 	QString errmsg;
@@ -72,160 +72,160 @@
 }
 
 
-GLCompiler::GLCompiler(GLRenderer* renderer) :
-	HierarchyElement(renderer),
-	m_renderer(renderer)
-{
-	needMerge();
-	memset(m_vboSizes, 0, sizeof m_vboSizes);
-}
+GLCompiler::GLCompiler(LDDocument* document, GLRenderer* renderer) :
+	document {document},
+	renderer {renderer} {}
 
 
 void GLCompiler::initialize()
 {
 	initializeOpenGLFunctions();
-	glGenBuffers(NumVbos, &m_vbo[0]);
+	glGenBuffers(NumVbos, &vboIndices[0]);
 	CHECK_GL_ERROR();
 }
 
 
 GLCompiler::~GLCompiler()
 {
-	glDeleteBuffers(NumVbos, &m_vbo[0]);
+	glDeleteBuffers(NumVbos, &vboIndices[0]);
 	CHECK_GL_ERROR();
 }
 
+/*
+ * Returns a color that represents this object index. There are 256³ possible
+ * colors, so that many indices can be bijectively addressed with colorss.
+ */
+QColor GLCompiler::indexColorForID(int id) const
+{
+	int red = (id / 0x10000) % 0x100;
+	int green = (id / 0x100) % 0x100;
+	int blue = id % 0x100;
+
+	return {red, green, blue};
+}
+
 
-QColor GLCompiler::indexColorForID(int id) const
+QColor blend(QColor baseColor, QColor blendColor, double intensity)
 {
-	// Calculate a color based from this index. This method caters for
-	// 16777216 objects. I don't think that will be exceeded anytime soon. :)
-	int r = (id / 0x10000) % 0x100,
-		g = (id / 0x100) % 0x100,
-		b = id % 0x100;
-
-	return QColor(r, g, b);
+	double red = baseColor.redF();
+	red += blendColor.redF() * intensity;
+	red /= (intensity + 1.0);
+	double green = baseColor.greenF();
+	green += blendColor.greenF() * intensity;
+	green /= (intensity + 1.0);
+	double blue = baseColor.blueF();
+	blue += blendColor.blueF() * intensity;
+	blue /= (intensity + 1.0);
+	return {int(round(red)), int(round(green)), int(round(blue))};
 }
 
 
 QColor GLCompiler::getColorForPolygon(LDPolygon& poly, LDObject* topobj, ComplementVboType complement) const
 {
-	QColor qcol;
-	static const QColor bfcFrontColor(64, 192, 80);
-	static const QColor bfcBackColor(208, 64, 64);
+	QColor color;
 
 	switch(complement)
 	{
 	case SurfacesVboComplement:
 	case NumVboComplements:
-		return QColor();
+		return {};
 
 	case BfcFrontColorsVboComplement:
-		qcol = bfcFrontColor;
+		color = bfcFrontColor;
 		break;
 
 	case BfcBackColorsVboComplement:
-		qcol = bfcBackColor;
+		color = bfcBackColor;
 		break;
 
 	case PickColorsVboComplement:
 		return indexColorForID(topobj->id());
 
 	case RandomColorsVboComplement:
-		qcol = topobj->randomColor();
+		color = topobj->randomColor();
 		break;
 
 	case NormalColorsVboComplement:
 		if (poly.color == MainColor)
 		{
 			if (topobj->color() == MainColor)
-				qcol = guiUtilities()->mainColorRepresentation();
+				color = mainColorRepresentation();
 			else
-				qcol = topobj->color().faceColor();
+				color = topobj->color().faceColor();
 		}
 		else if (poly.color == EdgeColor)
 		{
-			qcol = luma(QColor(config->backgroundColor())) > 40 ? Qt::black : Qt::white;
+			if (luma(config->backgroundColor()) > 40)
+				color = Qt::black;
+			else
+				color = Qt::white;
 		}
 		else
 		{
-			LDColor col = poly.color;
+			LDColor colorInfo = poly.color;
 
-			if (col.isValid())
-				qcol = col.faceColor();
+			if (colorInfo.isValid())
+				color = colorInfo.faceColor();
 		}
 		break;
 	}
 
-	if (not qcol.isValid())
+	if (not color.isValid())
 	{
 		// The color was unknown. Use main color to make the polygon at least
 		// not appear pitch-black.
 		if (poly.num != 2 and poly.num != 5)
-			qcol = guiUtilities()->mainColorRepresentation();
+			color = mainColorRepresentation();
 		else
-			qcol = Qt::black;
+			color = Qt::black;
 
 		// Warn about the unknown color, but only once.
-		static QList<int> warnedColors;
+		static QSet<int> warnedColors;
 		if (not warnedColors.contains(poly.color))
 		{
 			print("Unknown color %1!\n", poly.color);
-			warnedColors << poly.color;
+			warnedColors.insert(poly.color);
 		}
 
-		return qcol;
+		return color;
 	}
 
 	double blendAlpha = 0.0;
 
 	if (topobj->isSelected())
 		blendAlpha = 1.0;
-	else if (topobj == m_renderer->objectAtCursor())
+	else if (topobj == renderer->objectAtCursor())
 		blendAlpha = 0.5;
 
-	if (blendAlpha != 0.0)
-	{
-		QColor selcolor(config->selectColorBlend());
-		double denom = blendAlpha + 1.0;
-		qcol.setRed((qcol.red() +(selcolor.red() * blendAlpha)) / denom);
-		qcol.setGreen((qcol.green() +(selcolor.green() * blendAlpha)) / denom);
-		qcol.setBlue((qcol.blue() +(selcolor.blue() * blendAlpha)) / denom);
-	}
-
-	return qcol;
+	color = blend(color, config->selectColorBlend(), blendAlpha);
+	return color;
 }
 
 
 void GLCompiler::needMerge()
 {
-	for (int i = 0; i < countof(m_vboChanged); ++i)
-		m_vboChanged[i] = true;
+	for (bool& changed_flag : this->vboChanged)
+		changed_flag = true;
 }
 
 
 void GLCompiler::stageForCompilation(LDObject* obj)
 {
-	/*
-	g_objectOrigins[obj] = format("%1:%2(%3)",
-		obj->document()->getDisplayName(), obj->lineNumber(), obj->typeName());
-	*/
-
-	m_staged << obj;
+	stagedObjects << obj;
 }
 
 
 void GLCompiler::unstage(LDObject* obj)
 {
-	m_staged.remove(obj);
+	stagedObjects.remove(obj);
 }
 
 
-void GLCompiler::compileDocument(LDDocument* doc)
+void GLCompiler::compileDocument(LDDocument* document)
 {
-	if (doc)
+	if (document)
 	{
-		for (LDObject* obj : doc->objects())
+		for (LDObject* obj : document->objects())
 			compileObject(obj);
 	}
 }
@@ -233,10 +233,10 @@
 
 void GLCompiler::compileStaged()
 {
-	for (QSetIterator<LDObject*> it(m_staged); it.hasNext();)
+	for (QSetIterator<LDObject*> it(stagedObjects); it.hasNext();)
 		compileObject(it.next());
 
-	m_staged.clear();
+	stagedObjects.clear();
 }
 
 
@@ -245,39 +245,45 @@
 	// Compile anything that still awaits it
 	compileStaged();
 
-	if (not m_vboChanged[vbonum])
-		return;
-
-	QVector<GLfloat> vbodata;
+	if (not vboChanged[vbonum])
+	{
+		print("Merging %1", vbonum);
+		QVector<GLfloat> vbodata;
 
-	for (auto it = m_objectInfo.begin(); it != m_objectInfo.end();)
-	{
-		if (it.key() == nullptr)
+		for (auto it = objectInfo.begin(); it != objectInfo.end();)
 		{
-			it = m_objectInfo.erase(it);
-			continue;
+			if (it.key() == nullptr)
+			{
+				it = objectInfo.erase(it);
+			}
+			else
+			{
+				if (not it.key()->isHidden())
+					vbodata += it->data[vbonum];
+
+				++it;
+			}
 		}
 
-		if (it.key()->document() == currentDocument() and not it.key()->isHidden())
-			vbodata += it->data[vbonum];
-
-		++it;
+		glBindBuffer(GL_ARRAY_BUFFER, vboIndices[vbonum]);
+		glBufferData(
+			GL_ARRAY_BUFFER,
+			vbodata.size() * sizeof(GLfloat),
+			vbodata.constData(),
+			GL_STATIC_DRAW);
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+		CHECK_GL_ERROR();
+		vboChanged[vbonum] = false;
+		vboSizes[vbonum] = vbodata.size();
 	}
-
-	glBindBuffer(GL_ARRAY_BUFFER, m_vbo[vbonum]);
-	glBufferData(GL_ARRAY_BUFFER, vbodata.size() * sizeof(GLfloat), vbodata.constData(), GL_STATIC_DRAW);
-	glBindBuffer(GL_ARRAY_BUFFER, 0);
-	CHECK_GL_ERROR();
-	m_vboChanged[vbonum] = false;
-	m_vboSizes[vbonum] = vbodata.size();
 }
 
 
 void GLCompiler::dropObjectInfo(LDObject* obj)
 {
-	if (m_objectInfo.contains(obj))
+	if (objectInfo.contains(obj))
 	{
-		m_objectInfo.remove(obj);
+		objectInfo.remove(obj);
 		needMerge();
 	}
 }
@@ -336,7 +342,7 @@
 		break;
 	}
 
-	m_objectInfo[obj] = info;
+	objectInfo[obj] = info;
 	needMerge();
 }
 
@@ -381,13 +387,6 @@
 	}
 }
 
-
-void GLCompiler::setRenderer(GLRenderer* renderer)
-{
-	m_renderer = renderer;
-}
-
-
 int GLCompiler::vboNumber(SurfaceVboType surface, ComplementVboType complement)
 {
 	return (surface * NumVboComplements) + complement;
@@ -396,11 +395,11 @@
 
 GLuint GLCompiler::vbo(int vbonum) const
 {
-	return m_vbo[vbonum];
+	return vboIndices[vbonum];
 }
 
 
 int GLCompiler::vboSize(int vbonum) const
 {
-	return m_vboSizes[vbonum];
-}
\ No newline at end of file
+	return vboSizes[vbonum];
+}
--- a/src/glCompiler.h	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/glCompiler.h	Thu Jan 11 15:09:44 2018 +0200
@@ -17,33 +17,22 @@
  */
 
 #pragma once
+#include <QOpenGLFunctions>
 #include "main.h"
-#include "glRenderer.h"
 #include "glShared.h"
-#include <QMap>
-#include <QSet>
 
-// =============================================================================
-//
-class GLCompiler : public HierarchyElement, protected QOpenGLFunctions
+class GLCompiler : protected QOpenGLFunctions
 {
 public:
-	struct ObjectVBOInfo
-	{
-		QVector<GLfloat>	data[NumVbos];
-		bool				isChanged;
-	};
-
-	GLCompiler(GLRenderer* renderer);
+	GLCompiler(LDDocument* document, class GLRenderer* renderer);
 	~GLCompiler();
-	void compileDocument(LDDocument* doc);
+	void compileDocument(LDDocument* document);
 	void dropObjectInfo(LDObject* obj);
 	QColor getColorForPolygon(LDPolygon& poly, LDObject* topobj, ComplementVboType complement) const;
 	QColor indexColorForID(int id) const;
 	void initialize();
 	void needMerge();
 	void prepareVBO(int vbonum);
-	void setRenderer(GLRenderer* compiler);
 	void stageForCompilation(LDObject* obj);
 	void unstage(LDObject* obj);
 	GLuint vbo(int vbonum) const;
@@ -52,16 +41,23 @@
 	static int vboNumber(SurfaceVboType surface, ComplementVboType complement);
 
 private:
+	struct ObjectVBOInfo
+	{
+		QVector<GLfloat> data[NumVbos];
+		bool isChanged = false;
+	};
+
 	void compileStaged();
 	void compileObject(LDObject* obj);
 	void compilePolygon(LDPolygon& poly, LDObject* topobj, ObjectVBOInfo* objinfo);
 
-	QMap<LDObject*, ObjectVBOInfo>	m_objectInfo;
-	QSet<LDObject*> m_staged; // Objects that need to be compiled
-	GLuint m_vbo[NumVbos];
-	bool m_vboChanged[NumVbos];
-	int m_vboSizes[NumVbos];
-	GLRenderer* m_renderer;
+	QMap<LDObject*, ObjectVBOInfo> objectInfo;
+	QSet<LDObject*> stagedObjects; // Objects that need to be compiled
+	LDDocument* const document;
+	GLuint vboIndices[NumVbos];
+	bool vboChanged[NumVbos] = {true};
+	int vboSizes[NumVbos] = {0};
+	class GLRenderer* renderer;
 };
 
 #define CHECK_GL_ERROR() { CheckGLErrorImpl(__FILE__, __LINE__); }
--- a/src/glRenderer.cpp	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/glRenderer.cpp	Thu Jan 11 15:09:44 2018 +0200
@@ -71,10 +71,11 @@
 
 // =============================================================================
 //
-GLRenderer::GLRenderer(QWidget* parent) :
+GLRenderer::GLRenderer(LDDocument *document, QWidget* parent) :
 	QGLWidget(parent),
 	HierarchyElement(parent),
-	m_document(nullptr),
+	m_document(document),
+	m_compiler {document, this},
 	m_initialized(false)
 {
 	m_isPicking = false;
@@ -82,7 +83,6 @@
 	m_drawToolTip = false;
 	m_currentEditMode = AbstractEditMode::createByType(this, EditModeType::Select);
 	m_panning = false;
-	m_compiler = new GLCompiler(this);
 	m_objectAtCursor = nullptr;
 	setDrawOnly(false);
 	m_messageLog = new MessageManager(this);
@@ -128,8 +128,6 @@
 	if (messageLog())
 		messageLog()->setRenderer(nullptr);
 
-	m_compiler->setRenderer(nullptr);
-	delete m_compiler;
 	delete m_currentEditMode;
 	glDeleteBuffers(1, &m_axesVbo);
 	glDeleteBuffers(1, &m_axesColorVbo);
@@ -215,7 +213,7 @@
 
 GLCompiler* GLRenderer::compiler() const
 {
-	return m_compiler;
+	return &m_compiler;
 }
 
 LDObject* GLRenderer::objectAtCursor() const
@@ -506,13 +504,13 @@
 		return;
 	}
 
-	int surfacenum = m_compiler->vboNumber(surface, SurfacesVboComplement);
-	int colornum = m_compiler->vboNumber(surface, colors);
-	m_compiler->prepareVBO(surfacenum);
-	m_compiler->prepareVBO(colornum);
-	GLuint surfacevbo = m_compiler->vbo(surfacenum);
-	GLuint colorvbo = m_compiler->vbo(colornum);
-	GLsizei count = m_compiler->vboSize(surfacenum) / 3;
+	int surfacenum = m_compiler.vboNumber(surface, SurfacesVboComplement);
+	int colornum = m_compiler.vboNumber(surface, colors);
+	m_compiler.prepareVBO(surfacenum);
+	m_compiler.prepareVBO(colornum);
+	GLuint surfacevbo = m_compiler.vbo(surfacenum);
+	GLuint colorvbo = m_compiler.vbo(colornum);
+	GLsizei count = m_compiler.vboSize(surfacenum) / 3;
 
 	if (count > 0)
 	{
@@ -1059,26 +1057,6 @@
 
 // =============================================================================
 //
-void GLRenderer::setDocument(LDDocument* document)
-{
-	m_document = document;
-
-	if (document)
-	{
-		initOverlaysFromObjects();
-
-		if (not currentDocumentData().init)
-		{
-			resetAllAngles();
-			currentDocumentData().init = true;
-		}
-
-		currentDocumentData().needZoomToFit = true;
-	}
-}
-
-// =============================================================================
-//
 void GLRenderer::setPicking(bool value)
 {
 	m_isPicking = value;
@@ -1525,7 +1503,7 @@
 		}
 	}
 
-	if (m_window->renderer() == this)
+	if (m_window->currentRenderer() == this)
 		m_window->refresh();
 }
 
@@ -1592,7 +1570,7 @@
 		currentDocument()->insertObj(m_window->suggestInsertPoint(), ref);
 		ref->select();
 		m_window->buildObjectList();
-		m_window->renderer()->refresh();
+		m_window->currentRenderer()->refresh();
 		ev->acceptProposedAction();
 	}
 }
--- a/src/glRenderer.h	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/glRenderer.h	Thu Jan 11 15:09:44 2018 +0200
@@ -22,6 +22,7 @@
 #include "macros.h"
 #include "ldObject.h"
 #include "ldDocument.h"
+#include "glCompiler.h"
 #include "glShared.h"
 #include "editmodes/abstractEditMode.h"
 
@@ -136,7 +137,7 @@
 	Q_OBJECT
 
 public:
-	GLRenderer(QWidget* parent = nullptr);
+	GLRenderer(LDDocument* document, QWidget* parent = nullptr);
 	~GLRenderer();
 
 	ECamera camera() const;
@@ -186,7 +187,6 @@
 	void setBackground();
 	void setCamera(const ECamera cam);
 	void setDepthValue(double depth);
-	void setDocument(LDDocument* document);
 	void setDrawOnly(bool value);
 	void setEditMode(EditModeType type);
 	void setPicking(bool a);
@@ -213,8 +213,8 @@
 
 private:
 	MessageManager* m_messageLog;
-	LDDocument* m_document;
-	GLCompiler* m_compiler;
+	LDDocument* const m_document;
+	mutable GLCompiler m_compiler;
 	LDObject* m_objectAtCursor;
 
 	CameraIcon m_cameraIcons[7];
--- a/src/guiutilities.cpp	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/guiutilities.cpp	Thu Jan 11 15:09:44 2018 +0200
@@ -82,7 +82,7 @@
 	}
 }
 
-QColor GuiUtilities::mainColorRepresentation()
+QColor mainColorRepresentation()
 {
 	QColor col(config->mainColor());
 
--- a/src/guiutilities.h	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/guiutilities.h	Thu Jan 11 15:09:44 2018 +0200
@@ -28,5 +28,6 @@
 
 	QIcon makeColorIcon(LDColor ldcolor, int size);
 	void fillUsedColorsToComboBox(class QComboBox* box);
-	QColor mainColorRepresentation();
 };
+
+QColor mainColorRepresentation();
--- a/src/ldDocument.cpp	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/ldDocument.cpp	Thu Jan 11 15:09:44 2018 +0200
@@ -131,11 +131,10 @@
 	if (m_isCache)
 	{
 		m_isCache = false;
-		print("Opened %1", name());
 
 		// Cache files are not compiled by the GL renderer. Now that this file is open for editing, it needs to be
 		// compiled.
-		m_window->renderer()->compiler()->compileDocument(this);
+		m_window->currentRenderer()->compiler()->compileDocument(this);
 		m_window->updateDocumentList();
 	}
 }
@@ -177,10 +176,7 @@
 		m_isCache = true;
 		print("Closed %1", name());
 		m_window->updateDocumentList();
-
-		// If the current document just became implicit(i.e. user closed it), we need to get a new one to show.
-		if (currentDocument() == this)
-			m_window->currentDocumentClosed();
+		emit closed();
 	}
 }
 
@@ -599,7 +595,7 @@
 	m_objects << obj;
 	addKnownVertices(obj);
 	obj->setDocument(this);
-	m_window->renderer()->compileObject(obj);
+	m_window->currentRenderer()->compileObject(obj);
 	return getObjectCount() - 1;
 }
 
@@ -621,7 +617,7 @@
 	history()->add(new AddHistoryEntry(pos, obj));
 	m_objects.insert(pos, obj);
 	obj->setDocument(this);
-	m_window->renderer()->compileObject(obj);
+	m_window->currentRenderer()->compileObject(obj);
 	
 
 #ifdef DEBUG
@@ -686,7 +682,7 @@
 	m_objects[idx]->setDocument(nullptr);
 	obj->setDocument(this);
 	addKnownVertices(obj);
-	m_window->renderer()->compileObject(obj);
+	m_window->currentRenderer()->compileObject(obj);
 	m_objects[idx] = obj;
 	needVertexMerge();
 }
@@ -830,7 +826,7 @@
 	if (obj->isSelected() and obj->document() == this)
 	{
 		m_sel << obj;
-		m_window->renderer()->compileObject(obj);
+		m_window->currentRenderer()->compileObject(obj);
 	}
 }
 
@@ -841,7 +837,7 @@
 	if (not obj->isSelected() and obj->document() == this)
 	{
 		m_sel.removeOne(obj);
-		m_window->renderer()->compileObject(obj);
+		m_window->currentRenderer()->compileObject(obj);
 	}
 }
 
@@ -852,7 +848,7 @@
 	for (LDObject* obj : m_sel)
 	{
 		obj->deselect();
-		m_window->renderer()->compileObject(obj);
+		m_window->currentRenderer()->compileObject(obj);
 	}
 
 	m_sel.clear();
@@ -909,4 +905,4 @@
 void LDDocument::needVertexMerge()
 {
 	m_needVertexMerge = true;
-}
\ No newline at end of file
+}
--- a/src/ldDocument.h	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/ldDocument.h	Thu Jan 11 15:09:44 2018 +0200
@@ -100,6 +100,9 @@
 
 	static QString shortenName(QString a); // Turns a full path into a relative path
 
+signals:
+	void closed();
+
 private:
 	QString m_name;
 	QString m_fullPath;
--- a/src/ldObject.cpp	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/ldObject.cpp	Thu Jan 11 15:09:44 2018 +0200
@@ -315,7 +315,7 @@
 
 	// Delete the GL lists
 	if (g_win)
-		g_win->renderer()->forgetObject(this);
+		g_win->currentRenderer()->forgetObject(this);
 
 	// Remove this object from the list of LDObjects
 	g_allObjects.erase(g_allObjects.find(id()));
@@ -481,7 +481,7 @@
 	// The objects need to be recompiled, otherwise their pick lists are left with
 	// the wrong index colors which messes up selection.
 	for (LDObject* obj : objsToCompile)
-		g_win->renderer()->compileObject(obj);
+		g_win->currentRenderer()->compileObject(obj);
 }
 
 // =============================================================================
@@ -848,7 +848,7 @@
 		if (before != after)
 		{
 			obj->document()->addToHistory(new EditHistoryEntry(idx, before, after));
-			g_win->renderer()->compileObject(obj);
+			g_win->currentRenderer()->compileObject(obj);
 			g_win->currentDocument()->redoVertices();
 		}
 	}
--- a/src/main.h	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/main.h	Thu Jan 11 15:09:44 2018 +0200
@@ -25,6 +25,7 @@
 #include <stdlib.h>
 #include <stdint.h>
 #include <stdarg.h>
+#include <QSet>
 #include <QString>
 #include <QTextFormat>
 #include "macros.h"
@@ -33,4 +34,4 @@
 #include "hierarchyelement.h"
 #include "configurationvaluebag.h"
 
-extern ConfigurationValueBag* config;
\ No newline at end of file
+extern ConfigurationValueBag* config;
--- a/src/mainwindow.cpp	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/mainwindow.cpp	Thu Jan 11 15:09:44 2018 +0200
@@ -77,17 +77,11 @@
 	g_win = this;
 	ui.setupUi(this);
 	m_updatingTabs = false;
-	m_renderer = new GLRenderer(this);
 	m_tabs = new QTabBar;
 	m_tabs->setTabsClosable(true);
 	ui.verticalLayout->insertWidget(0, m_tabs);
 
 	createBlankDocument();
-	m_renderer->setDocument(m_currentDocument);
-
-	// Stuff the renderer into its frame
-	QVBoxLayout* rendererLayout = new QVBoxLayout(ui.rendererFrame);
-	rendererLayout->addWidget(renderer());
 
 	connect(ui.objectList, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged()));
 	connect(ui.objectList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(objectListDoubleClicked(QListWidgetItem*)));
@@ -117,7 +111,6 @@
 	updateTitle();
 	loadShortcuts();
 	setMinimumSize(300, 200);
-	connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(doLastSecondCleanup()));
 	connect(
 		ui.ringToolSegments, SIGNAL(valueChanged(int)),
 		this, SLOT(circleToolSegmentsChanged())
@@ -186,6 +179,10 @@
 
 MainWindow::~MainWindow()
 {
+	for (GLRenderer* renderer : m_renderers.values())
+		delete renderer;
+
+	delete &ui;
 	g_win = nullptr;
 }
 
@@ -222,14 +219,6 @@
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void MainWindow::doLastSecondCleanup()
-{
-	delete m_renderer;
-	delete &ui;
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-//
 void MainWindow::updateRecentFilesMenu()
 {
 	// First, clear any items in the recent files menu
@@ -319,7 +308,7 @@
 
 	// Recompile all Bézier curves, the changing grid affects their precision.
 	for (LDObjectIterator<LDBezierCurve> it(m_currentDocument); it.isValid(); ++it)
-		renderer()->compileObject(it);
+		currentRenderer()->compileObject(it);
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -457,7 +446,7 @@
 			case OBJ_Overlay:
 			{
 				LDOverlay* ovl = static_cast<LDOverlay*>(obj);
-				descr = format("[%1] %2(%3, %4), %5 x %6", renderer()->cameraName((ECamera) ovl->camera()),
+				descr = format("[%1] %2(%3, %4), %5 x %6", currentRenderer()->cameraName((ECamera) ovl->camera()),
 					Basename(ovl->fileName()), ovl->x(), ovl->y(),
 					ovl->width(), ovl->height());
 				break;
@@ -553,9 +542,9 @@
 	removeDuplicates(compound);
 
 	for (LDObject* obj : compound)
-		renderer()->compileObject(obj);
+		currentRenderer()->compileObject(obj);
 
-	renderer()->update();
+	currentRenderer()->update();
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -591,7 +580,7 @@
 			continue; // uncolored object
 
 		obj->setColor(color);
-		renderer()->compileObject(obj);
+		currentRenderer()->compileObject(obj);
 	}
 
 	endAction();
@@ -616,8 +605,8 @@
 //
 void MainWindow::doFullRefresh()
 {
-	buildObjectList();
-	m_renderer->hardRefresh();
+	this->buildObjectList();
+	this->currentRenderer()->hardRefresh();
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -627,7 +616,7 @@
 void MainWindow::refresh()
 {
 	buildObjectList();
-	m_renderer->update();
+	currentRenderer()->update();
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -782,7 +771,7 @@
 		contextMenu->addAction(ui.actionSubfileSelection);
 	}
 
-	if (renderer()->camera() != EFreeCamera)
+	if (currentRenderer()->camera() != EFreeCamera)
 	{
 		contextMenu->addSeparator();
 		contextMenu->addAction(ui.actionSetDrawDepth);
@@ -813,7 +802,7 @@
 //
 void MainWindow::updateEditModeActions()
 {
-	const EditModeType mode = renderer()->currentEditModeType();
+	const EditModeType mode = currentRenderer()->currentEditModeType();
 	ui.actionModeSelect->setChecked(mode == EditModeType::Select);
 	ui.actionModeDraw->setChecked(mode == EditModeType::Draw);
 	ui.actionModeRectangle->setChecked(mode == EditModeType::Rectangle);
@@ -894,7 +883,7 @@
 // Adds a message to the renderer's message manager.
 void MainWindow::addMessage(QString msg)
 {
-	m_renderer->messageLog()->addLine(msg);
+	this->currentRenderer()->messageLog()->addLine(msg);
 }
 
 // ============================================================================
@@ -1058,9 +1047,9 @@
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-GLRenderer* MainWindow::renderer()
+GLRenderer* MainWindow::currentRenderer()
 {
-	return m_renderer;
+	return m_renderers[m_currentDocument];
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -1231,9 +1220,17 @@
 	connect(document->history(), SIGNAL(undone()), this, SLOT(historyTraversed()));
 	connect(document->history(), SIGNAL(redone()), this, SLOT(historyTraversed()));
 	connect(document->history(), SIGNAL(stepAdded()), this, SLOT(updateActions()));
+	connect(document, SIGNAL(closed()), this, SLOT(documentClosed()));
+
+	GLRenderer* renderer = new GLRenderer {document, this};
+	m_renderers[document] = renderer;
+	ui.rendererStack->addWidget(renderer);
 
 	if (not cache)
+	{
+		changeDocument(document);
 		document->openForEditing();
+	}
 
 	return document;
 }
@@ -1251,7 +1248,8 @@
 //
 void MainWindow::changeDocument(LDDocument* document)
 {
-	// Implicit files were loaded for caching purposes and may never be switched to.
+	// Implicit files were loaded for caching purposes and may
+	// not be switched to.
 	if (document and document->isCache())
 		return;
 
@@ -1263,8 +1261,7 @@
 		updateDocumentListItem(document);
 		buildObjectList();
 		updateTitle();
-		m_renderer->setDocument(document);
-		m_renderer->compiler()->needMerge();
+		ui.rendererStack->setCurrentWidget(m_renderers[document]);
 		print("Changed document to %1", document->getDisplayName());
 	}
 }
@@ -1295,24 +1292,38 @@
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void MainWindow::currentDocumentClosed()
+void MainWindow::documentClosed()
 {
-	LDDocument* old = currentDocument();
+	LDDocument* document = qobject_cast<LDDocument*>(sender());
+
+	if (not document)
+		return;
+
+	if (document == currentDocument())
+	{
+		LDDocument* previousCurrentDocument = currentDocument();
 
-	// Find a replacement document to use
-	for (LDDocument* doc : m_documents->allDocuments())
-	{
-		if (doc != old and not doc->isCache())
+		// Find a replacement document to use
+		for (LDDocument* document : m_documents->allDocuments())
 		{
-			changeDocument(doc);
-			break;
+			if (document != previousCurrentDocument and not document->isCache())
+			{
+				changeDocument(document);
+				break;
+			}
+		}
+
+		if (currentDocument() == previousCurrentDocument)
+		{
+			// Failed to change to a suitable document, open a new one.
+			createBlankDocument();
 		}
 	}
 
-	if (currentDocument() == old)
+	if (m_renderers.contains(document))
 	{
-		// Failed to change to a suitable document, open a new one.
-		createBlankDocument();
+		delete m_renderers[document];
+		m_renderers.remove(document);
 	}
 }
 
--- a/src/mainwindow.h	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/mainwindow.h	Thu Jan 11 15:09:44 2018 +0200
@@ -80,7 +80,6 @@
 	void closeInitialDocument();
 	void createBlankDocument();
 	LDDocument* currentDocument();
-	void currentDocumentClosed();
 	QKeySequence defaultShortcut(QAction* act);
 	void deleteByColor(LDColor color);
 	int deleteSelection();
@@ -95,7 +94,7 @@
 	class GuiUtilities* guiUtilities();
 	void loadShortcuts();
 	LDDocument* newDocument(bool cache = false);
-	GLRenderer* renderer();
+	GLRenderer* currentRenderer();
 	void refresh();
 	void refreshObjectList();
 	int ringToolDivisions() const;
@@ -134,6 +133,7 @@
 	struct ToolInfo { QMetaMethod method; Toolset* object; };
 
 	class GuiUtilities* m_guiUtilities;
+	QMap<LDDocument*, GLRenderer*> m_renderers;
 	GLRenderer* m_renderer;
 	LDObjectList m_sel;
 	QList<ColorToolbarItem>	m_quickColors;
@@ -157,8 +157,8 @@
 	void selectionChanged();
 	void recentFileClicked();
 	void quickColorClicked();
-	void doLastSecondCleanup();
 	void objectListDoubleClicked(QListWidgetItem* listitem);
+	void documentClosed();
 };
 
 // Pointer to the instance of MainWindow.
--- a/src/mainwindow.ui	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/mainwindow.ui	Thu Jan 11 15:09:44 2018 +0200
@@ -20,9 +20,9 @@
   <widget class="QWidget" name="centralwidget">
    <layout class="QVBoxLayout" name="verticalLayout">
     <item>
-     <layout class="QHBoxLayout" name="horizontalLayout" stretch="3,1">
+     <layout class="QHBoxLayout" name="horizontalLayout" stretch="2,1">
       <item>
-       <widget class="QFrame" name="rendererFrame">
+       <widget class="QStackedWidget" name="rendererStack">
         <property name="sizePolicy">
          <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
           <horstretch>0</horstretch>
@@ -47,7 +47,7 @@
           <rect>
            <x>0</x>
            <y>0</y>
-           <width>233</width>
+           <width>465</width>
            <height>371</height>
           </rect>
          </property>
@@ -75,7 +75,7 @@
           <rect>
            <x>0</x>
            <y>0</y>
-           <width>233</width>
+           <width>310</width>
            <height>371</height>
           </rect>
          </property>
@@ -179,7 +179,7 @@
           <rect>
            <x>0</x>
            <y>0</y>
-           <width>233</width>
+           <width>465</width>
            <height>371</height>
           </rect>
          </property>
--- a/src/toolsets/basictoolset.cpp	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/toolsets/basictoolset.cpp	Thu Jan 11 15:09:44 2018 +0200
@@ -269,35 +269,35 @@
 
 void BasicToolset::modeSelect()
 {
-	m_window->renderer()->setEditMode(EditModeType::Select);
+	m_window->currentRenderer()->setEditMode(EditModeType::Select);
 }
 
 void BasicToolset::modeCurve()
 {
-	m_window->renderer()->setEditMode(EditModeType::Curve);
+	m_window->currentRenderer()->setEditMode(EditModeType::Curve);
 }
 
 void BasicToolset::modeDraw()
 {
-	m_window->renderer()->setEditMode(EditModeType::Draw);
+	m_window->currentRenderer()->setEditMode(EditModeType::Draw);
 }
 
 void BasicToolset::modeRectangle()
 {
-	m_window->renderer()->setEditMode(EditModeType::Rectangle);
+	m_window->currentRenderer()->setEditMode(EditModeType::Rectangle);
 }
 
 void BasicToolset::modeCircle()
 {
-	m_window->renderer()->setEditMode(EditModeType::Circle);
+	m_window->currentRenderer()->setEditMode(EditModeType::Circle);
 }
 
 void BasicToolset::modeMagicWand()
 {
- 	m_window->renderer()->setEditMode(EditModeType::MagicWand);
+	m_window->currentRenderer()->setEditMode(EditModeType::MagicWand);
 }
 
 void BasicToolset::modeLinePath()
 {
-	m_window->renderer()->setEditMode(EditModeType::LinePath);
-}
\ No newline at end of file
+	m_window->currentRenderer()->setEditMode(EditModeType::LinePath);
+}
--- a/src/toolsets/filetoolset.cpp	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/toolsets/filetoolset.cpp	Thu Jan 11 15:09:44 2018 +0200
@@ -134,7 +134,7 @@
 	{
 		currentDocument()->insertObj(idx, obj);
 		obj->select();
-		m_window->renderer()->compileObject(obj);
+		m_window->currentRenderer()->compileObject(obj);
 
 		idx++;
 	}
@@ -193,7 +193,7 @@
 	{
 		m_window->changeDocument(dialog->primaryFile());
 		m_window->doFullRefresh();
-		m_window->renderer()->resetAngles();
+		m_window->currentRenderer()->resetAngles();
 	});
 	dialog->exec();
 }
@@ -234,4 +234,4 @@
 void FileToolset::aboutQt()
 {
 	QMessageBox::aboutQt(m_window);
-}
\ No newline at end of file
+}
--- a/src/toolsets/viewtoolset.cpp	Thu Jan 11 11:41:40 2018 +0200
+++ b/src/toolsets/viewtoolset.cpp	Thu Jan 11 15:09:44 2018 +0200
@@ -98,17 +98,17 @@
 
 void ViewToolset::resetView()
 {
-	m_window->renderer()->resetAngles();
-	m_window->renderer()->update();
+	m_window->currentRenderer()->resetAngles();
+	m_window->currentRenderer()->update();
 }
 
 void ViewToolset::screenshot()
 {
 	const char* imageformats = "PNG images(*.png);;JPG images(*.jpg);;BMP images(*.bmp);;"
 		"PPM images(*.ppm);;X11 Bitmaps(*.xbm);;X11 Pixmaps(*.xpm);;All Files(*.*)";
-	int width = m_window->renderer()->width();
-	int height = m_window->renderer()->height();
-	QByteArray capture = m_window->renderer()->capturePixels();
+	int width = m_window->currentRenderer()->width();
+	int height = m_window->currentRenderer()->height();
+	QByteArray capture = m_window->currentRenderer()->capturePixels();
 	const uchar* imagedata = reinterpret_cast<const uchar*>(capture.constData());
 	// GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well.
 	QImage image = QImage(imagedata, width, height, QImage::Format_ARGB32).rgbSwapped().mirrored();
@@ -128,7 +128,7 @@
 {
 	config->setDrawAxes(not config->drawAxes());
 	m_window->updateActions();
-	m_window->renderer()->update();
+	m_window->currentRenderer()->update();
 }
 
 void ViewToolset::visibilityToggle()
@@ -152,7 +152,7 @@
 void ViewToolset::wireframe()
 {
 	config->setDrawWireframe(not config->drawWireframe());
-	m_window->renderer()->refresh();
+	m_window->currentRenderer()->refresh();
 }
 
 void ViewToolset::setOverlay()
@@ -162,33 +162,33 @@
 	if (not dlg.exec())
 		return;
 
-	m_window->renderer()->setupOverlay((ECamera) dlg.camera(), dlg.fpath(), dlg.ofsx(),
+	m_window->currentRenderer()->setupOverlay((ECamera) dlg.camera(), dlg.fpath(), dlg.ofsx(),
 		dlg.ofsy(), dlg.lwidth(), dlg.lheight());
 }
 
 void ViewToolset::clearOverlay()
 {
-	m_window->renderer()->clearOverlay();
+	m_window->currentRenderer()->clearOverlay();
 }
 
 void ViewToolset::drawAngles()
 {
 	config->setDrawAngles(not config->drawAngles());
-	m_window->renderer()->refresh();
+	m_window->currentRenderer()->refresh();
 }
 
 void ViewToolset::setDrawDepth()
 {
-	if (m_window->renderer()->camera() == EFreeCamera)
+	if (m_window->currentRenderer()->camera() == EFreeCamera)
 		return;
 
 	bool ok;
 	double depth = QInputDialog::getDouble(m_window, "Set Draw Depth",
-		format("Depth value for %1:", m_window->renderer()->currentCameraName()),
-		m_window->renderer()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok);
+		format("Depth value for %1:", m_window->currentRenderer()->currentCameraName()),
+		m_window->currentRenderer()->getDepthValue(), -10000.0f, 10000.0f, 3, &ok);
 
 	if (ok)
-		m_window->renderer()->setDepthValue(depth);
+		m_window->currentRenderer()->setDepthValue(depth);
 }
 
 #if 0
@@ -248,7 +248,7 @@
 		config->setRandomColors(false);
 
 	m_window->updateActions();
-	m_window->renderer()->refresh();
+	m_window->currentRenderer()->refresh();
 }
 
 void ViewToolset::jumpTo()
@@ -279,7 +279,7 @@
 		config->setBfcRedGreenView(false);
 
 	m_window->updateActions();
-	m_window->renderer()->refresh();
+	m_window->currentRenderer()->refresh();
 }
 
 void ViewToolset::drawSurfaces()

mercurial