Merge ../ldforge into gl

Tue, 08 Apr 2014 11:07:47 +0300

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Tue, 08 Apr 2014 11:07:47 +0300
changeset 711
7f7cbe63b16f
parent 710
86a19bfc34de (diff)
parent 658
2ca8316e0109 (current diff)
child 712
614b53aef8a9

Merge ../ldforge into gl

--- a/CMakeLists.txt	Tue Apr 08 11:07:25 2014 +0300
+++ b/CMakeLists.txt	Tue Apr 08 11:07:47 2014 +0300
@@ -33,6 +33,7 @@
 	src/editHistory.cc
 	src/extPrograms.cc
 	src/glRenderer.cc
+	src/glCompiler.cc
 	src/ldConfig.cc
 	src/ldDocument.cc
 	src/ldObject.cc
@@ -69,6 +70,7 @@
 	src/colorSelector.h
 	src/configDialog.h
 	src/glRenderer.h
+	src/glCompiler.h
 	src/configuration.h
 	src/mainWindow.h
 	src/editHistory.h
--- a/src/actions.cc	Tue Apr 08 11:07:25 2014 +0300
+++ b/src/actions.cc	Tue Apr 08 11:07:47 2014 +0300
@@ -35,6 +35,7 @@
 #include "primitives.h"
 #include "radioGroup.h"
 #include "colors.h"
+#include "glCompiler.h"
 #include "ui_newpart.h"
 
 extern_cfg (Bool,		gl_wireframe);
@@ -471,7 +472,6 @@
 
 		getCurrentDocument()->insertObj (idx, obj);
 		obj->select();
-		R()->compileObject (obj);
 		idx++;
 	}
 
@@ -847,9 +847,7 @@
 			obj->destroy();
 
 		// Compile all objects in the new subfile
-		for (LDObject* obj : doc->objects())
-			R()->compileObject (obj);
-
+		R()->compiler()->compileDocument (doc);
 		g_loadedFiles << doc;
 
 		// Add a reference to the new subfile to where the selection was
--- a/src/actionsEdit.cc	Tue Apr 08 11:07:25 2014 +0300
+++ b/src/actionsEdit.cc	Tue Apr 08 11:07:47 2014 +0300
@@ -94,7 +94,6 @@
 		LDObject* pasted = parseLine (line);
 		getCurrentDocument()->insertObj (idx++, pasted);
 		pasted->select();
-		R()->compileObject (pasted);
 		++num;
 	}
 
@@ -123,18 +122,10 @@
 		// inlined contents.
 		long idx = obj->lineNumber();
 
-		if (idx == -1)
+		if (idx == -1 || obj->type() != LDObject::ESubfile)
 			continue;
 
-		LDObjectList objs;
-
-		if (obj->type() == LDObject::ESubfile)
-		{
-			LDSubfile::InlineFlags flags = deep ? LDSubfile::DeepCacheInline : LDSubfile::CacheInline;
-			objs = static_cast<LDSubfile*> (obj)->inlineContents (flags);
-		}
-		else
-			continue;
+		LDObjectList objs = static_cast<LDSubfile*> (obj)->inlineContents (deep, false);
 
 		// Merge in the inlined objects
 		for (LDObject* inlineobj : objs)
@@ -144,7 +135,6 @@
 			LDObject* newobj = parseLine (line);
 			getCurrentDocument()->insertObj (idx++, newobj);
 			newobj->select();
-			g_win->R()->compileObject (newobj);
 		}
 
 		// Delete the subfile now as it's been inlined.
@@ -188,13 +178,7 @@
 		// after the first one.
 		getCurrentDocument()->setObject (index, triangles[0]);
 		getCurrentDocument()->insertObj (index + 1, triangles[1]);
-
-		for (LDTriangle* t : triangles)
-			R()->compileObject (t);
-
-		// Delete this quad now, it has been split.
 		obj->destroy();
-
 		num++;
 	}
 
@@ -224,17 +208,12 @@
 		ui.errorIcon->hide();
 	}
 
-	if (!dlg->exec())
+	if (dlg->exec() == QDialog::Rejected)
 		return;
 
-	LDObject* oldobj = obj;
-
 	// Reinterpret it from the text of the input field
-	obj = parseLine (ui.code->text());
-	oldobj->replace (obj);
-
-	// Refresh
-	R()->compileObject (obj);
+	LDObject* newobj = parseLine (ui.code->text());
+	obj->replace (newobj);
 	refresh();
 }
 
@@ -263,7 +242,6 @@
 				continue;
 
 			obj->setColor (colnum);
-			R()->compileObject (obj);
 		}
 
 		refresh();
@@ -312,7 +290,6 @@
 
 			lines[i]->setColor (edgecolor);
 			getCurrentDocument()->insertObj (idx, lines[i]);
-			R()->compileObject (lines[i]);
 		}
 
 		num += numLines;
@@ -340,9 +317,7 @@
 			LDVertex* vert = new LDVertex;
 			vert->pos = obj->vertex (i);
 			vert->setColor (obj->color());
-
 			getCurrentDocument()->insertObj (++ln, vert);
-			R()->compileObject (vert);
 			++num;
 		}
 	}
@@ -394,10 +369,7 @@
 	vect[Z] *= *currentGrid().confs[Grid::Z];
 
 	for (LDObject* obj : selection())
-	{
 		obj->move (vect);
-		g_win->R()->compileObject (obj);
-	}
 
 	g_win->refresh();
 }
@@ -441,10 +413,7 @@
 	LDObjectList sel = selection();
 
 	for (LDObject* obj : sel)
-	{
 		obj->invert();
-		R()->compileObject (obj);
-	}
 
 	refresh();
 }
@@ -516,8 +485,6 @@
 			rotateVertex (v, rotpoint, transform);
 			vert->pos = v;
 		}
-
-		g_win->R()->compileObject (obj);
 	}
 
 	g_win->refresh();
@@ -593,7 +560,6 @@
 					roundToDecimals (v[ax], 3);
 
 				obj->setVertex (i, v);
-				R()->compileObject (obj);
 				num += 3;
 			}
 		}
@@ -621,7 +587,6 @@
 			col = edgecolor;
 
 		obj->setColor (col);
-		R()->compileObject (obj);
 		num++;
 	}
 
@@ -673,7 +638,6 @@
 			}
 
 			obj->setVertex (i, v);
-			R()->compileObject (obj);
 		}
 	}
 
@@ -708,7 +672,6 @@
 				v[ax] *= -1;
 
 			obj->setVertex (i, v);
-			R()->compileObject (obj);
 		}
 	}
 
@@ -727,8 +690,7 @@
 		if (obj->type() != LDObject::ECondLine)
 			continue;
 
-		LDLine* repl = static_cast<LDCondLine*> (obj)->demote();
-		R()->compileObject (repl);
+		static_cast<LDCondLine*> (obj)->demote();
 		++num;
 	}
 
@@ -768,7 +730,6 @@
 			continue;
 
 		obj->setColor (colnum);
-		R()->compileObject (obj);
 	}
 
 	print (tr ("Auto-colored: new color is [%1] %2"), colnum, getColor (colnum)->name);
--- a/src/basics.cc	Tue Apr 08 11:07:25 2014 +0300
+++ b/src/basics.cc	Tue Apr 08 11:07:47 2014 +0300
@@ -316,7 +316,7 @@
 		case LDObject::ESubfile:
 		{
 			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline);
+			LDObjectList objs = ref->inlineContents (true, false);
 
 			for (LDObject * obj : objs)
 			{
--- a/src/colors.cc	Tue Apr 08 11:07:25 2014 +0300
+++ b/src/colors.cc	Tue Apr 08 11:07:47 2014 +0300
@@ -74,7 +74,7 @@
 
 // =============================================================================
 // =============================================================================
-int luma (QColor& col)
+int luma (const QColor& col)
 {
 	return (0.2126f * col.red()) +
 		   (0.7152f * col.green()) +
--- a/src/colors.h	Tue Apr 08 11:07:25 2014 +0300
+++ b/src/colors.h	Tue Apr 08 11:07:47 2014 +0300
@@ -31,7 +31,7 @@
 };
 
 void initColors();
-int luma (QColor& col);
+int luma (const QColor& col);
 
 // Safely gets a color with the given number or null if no such color.
 LDColor* getColor (int colnum);
--- a/src/extPrograms.cc	Tue Apr 08 11:07:25 2014 +0300
+++ b/src/extPrograms.cc	Tue Apr 08 11:07:47 2014 +0300
@@ -173,7 +173,7 @@
 		if (obj->type() == LDObject::ESubfile)
 		{
 			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			LDObjectList objs = ref->inlineContents (LDSubfile::DeepInline);
+			LDObjectList objs = ref->inlineContents (true, false);
 
 			writeObjects (objs, f);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/glCompiler.cc	Tue Apr 08 11:07:47 2014 +0300
@@ -0,0 +1,415 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define GL_GLEXT_PROTOTYPES
+#include <GL/glu.h>
+#include <GL/glext.h>
+#include "glCompiler.h"
+#include "ldObject.h"
+#include "colors.h"
+#include "ldDocument.h"
+#include "miscallenous.h"
+#include "glRenderer.h"
+#include "dialogs.h"
+
+cfg (String,	gl_selectcolor,			"#0080FF")
+
+struct GLErrorInfo
+{
+	GLenum	value;
+	QString	text;
+};
+
+static const GLErrorInfo g_GLErrors[] =
+{
+	{ GL_NO_ERROR,						"No error" },
+	{ GL_INVALID_ENUM,					"Unacceptable enumerator passed" },
+	{ GL_INVALID_VALUE,					"Numeric argument out of range" },
+	{ GL_INVALID_OPERATION,				"The operation is not allowed to be done in this state" },
+	{ GL_INVALID_FRAMEBUFFER_OPERATION,	"Framebuffer object is not complete"},
+	{ GL_OUT_OF_MEMORY,					"Out of memory" },
+	{ GL_STACK_UNDERFLOW,				"The operation would have caused an underflow" },
+	{ GL_STACK_OVERFLOW,				"The operation would have caused an overflow" },
+};
+
+#include <QTime>
+
+#define CLOCK_INIT QTime t0;
+
+#define CLOCK_START \
+{ \
+	t0 = QTime::currentTime(); \
+}
+
+#define CLOCK_TIME(A) \
+{ \
+	fprint (stderr, A ": %1ms\n", t0.msecsTo (QTime::currentTime())); \
+}
+
+#define DEBUG_PRINT(...) fprint (stdout, __VA_ARGS__)
+
+extern_cfg (Bool, gl_blackedges);
+extern_cfg (String, gl_bgcolor);
+static QList<short>		g_warnedColors;
+static const QColor		g_BFCFrontColor (40, 192, 40);
+static const QColor		g_BFCBackColor (224, 40, 40);
+static QMap<LDObject*, QString> g_objectOrigins;
+
+// =============================================================================
+//
+void checkGLError_private (const char* file, int line)
+{
+	QString errmsg;
+	GLenum errnum = glGetError();
+
+	if (errnum == GL_NO_ERROR)
+		return;
+
+	for (const GLErrorInfo& err : g_GLErrors)
+	{
+		if (err.value == errnum)
+		{
+			errmsg = err.text;
+			break;
+		}
+	}
+
+	print ("OpenGL ERROR: at %1:%2: %3", basename (QString (file)), line, errmsg);
+}
+
+// =============================================================================
+//
+GLCompiler::GLCompiler()
+{
+	needMerge();
+	memset (m_vboSizes, 0, sizeof m_vboSizes);
+}
+
+// =============================================================================
+//
+void GLCompiler::initialize()
+{
+	glGenBuffers (g_numVBOs, &m_vbo[0]);
+	checkGLError();
+}
+
+// =============================================================================
+//
+GLCompiler::~GLCompiler()
+{
+	glDeleteBuffers (g_numVBOs, &m_vbo[0]);
+	checkGLError();
+}
+
+// =============================================================================
+//
+uint32 GLCompiler::colorToRGB (const QColor& color)
+{
+	return
+		(color.red()   & 0xFF) << 0x00 |
+		(color.green() & 0xFF) << 0x08 |
+		(color.blue()  & 0xFF) << 0x10 |
+		(color.alpha() & 0xFF) << 0x18;
+}
+
+// =============================================================================
+//
+QColor GLCompiler::indexColorForID (int id) const
+{
+	// 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);
+}
+
+// =============================================================================
+//
+QColor GLCompiler::polygonColor (LDPolygon& poly, LDObject* topobj) const
+{
+	QColor qcol;
+
+	if (poly.color == maincolor)
+	{
+		if (topobj->color() == maincolor)
+			qcol = GLRenderer::getMainColor();
+		else
+			qcol = getColor (topobj->color())->faceColor;
+	}
+	elif (poly.color == edgecolor)
+	{
+		qcol = luma (QColor (gl_bgcolor)) > 40 ? Qt::black : Qt::white;
+	}
+	else
+	{
+		LDColor* col = getColor (poly.color);
+
+		if (col)
+			qcol = col->faceColor;
+	}
+
+	if (qcol.isValid() == false)
+	{
+		// The color was unknown. Use main color to make the poly.object at least
+		// not appear pitch-black.
+		if (poly.num != 2 && poly.num != 5)
+			qcol = GLRenderer::getMainColor();
+		else
+			qcol = Qt::black;
+
+		// Warn about the unknown color, but only once.
+		if (g_warnedColors.contains (poly.color) == false)
+		{
+			print ("Unknown color %1!\n", poly.color);
+			g_warnedColors << poly.color;
+		}
+
+		return qcol;
+	}
+
+	if (topobj->isSelected())
+	{
+		// Brighten it up for the select list.
+		QColor selcolor (gl_selectcolor);
+		qcol.setRed ((qcol.red() + selcolor.red()) / 2);
+		qcol.setGreen ((qcol.green() + selcolor.green()) / 2);
+		qcol.setBlue ((qcol.blue() + selcolor.blue()) / 2);
+	}
+
+	return qcol;
+}
+
+// =============================================================================
+//
+void GLCompiler::needMerge()
+{
+	for (int i = 0; i < countof (m_vboChanged); ++i)
+		m_vboChanged[i] = true;
+}
+
+// =============================================================================
+//
+void GLCompiler::stageForCompilation (LDObject* obj)
+{
+	m_staged << obj;
+}
+
+// =============================================================================
+//
+void GLCompiler::compileDocument (LDDocument* doc)
+{
+	if (doc == null)
+		return;
+
+	for (LDObject* obj : doc->objects())
+		compileObject (obj);
+}
+
+// =============================================================================
+//
+void GLCompiler::compileStaged()
+{
+	removeDuplicates (m_staged);
+
+	for (LDObject* obj : m_staged)
+		compileObject (obj);
+
+	m_staged.clear();
+}
+
+// =============================================================================
+//
+void GLCompiler::prepareVBO (int vbonum)
+{
+	// Compile anything that still awaits it
+	compileStaged();
+
+	if (m_vboChanged[vbonum] == false)
+		return;
+
+	QVector<GLfloat> vbodata;
+
+	for (auto it = m_objectInfo.begin(); it != m_objectInfo.end(); ++it)
+	{
+		if (it.key()->document() == getCurrentDocument())
+		{
+			print ("merge %1\n", it.key()->id());
+			vbodata += it->data[vbonum];
+		}
+	}
+
+	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);
+	checkGLError();
+	m_vboChanged[vbonum] = false;
+	m_vboSizes[vbonum] = vbodata.size();
+}
+
+// =============================================================================
+//
+void GLCompiler::dropObject (LDObject* obj)
+{
+	auto it = m_objectInfo.find (obj);
+
+	if (it != m_objectInfo.end())
+	{
+		m_objectInfo.erase (it);
+		needMerge();
+	}
+}
+
+// =============================================================================
+//
+void GLCompiler::compileObject (LDObject* obj)
+{
+	if (obj->document()->isImplicit())
+		return;
+
+	g_objectOrigins[obj] = obj->document()->getDisplayName() + ":" + QString::number (obj->lineNumber());
+
+	if (obj->id() == 563)
+		print ("compile %1\n", g_objectOrigins[obj]);
+
+	ObjectVBOInfo info;
+	dropObject (obj);
+	compileSubObject (obj, obj, &info);
+	m_objectInfo[obj] = info;
+	needMerge();
+}
+
+// =============================================================================
+//
+void GLCompiler::compilePolygon (LDPolygon& poly, LDObject* topobj, ObjectVBOInfo* objinfo)
+{
+	EVBOSurface surface;
+	int numverts;
+
+	switch (poly.num)
+	{
+		case 3:	surface = VBOSF_Triangles;	numverts = 3; break;
+		case 4:	surface = VBOSF_Quads;		numverts = 4; break;
+		case 2:	surface = VBOSF_Lines;		numverts = 2; break;
+		case 5:	surface = VBOSF_CondLines;	numverts = 2; break;
+
+		default:
+			print ("OMGWTFBBQ weird polygon with number %1 (topobj: #%2, %3), origin: %4",
+				(int) poly.num, topobj->id(), topobj->typeName(), poly.origin);
+			assert (false);
+	}
+
+	for (int complement = 0; complement < VBOCM_NumComplements; ++complement)
+	{
+		const int vbonum			= vboNumber (surface, (EVBOComplement) complement);
+		QVector<GLfloat>& vbodata	= objinfo->data[vbonum];
+		const QColor normalColor	= polygonColor (poly, topobj);
+		const QColor pickColor		= indexColorForID (topobj->id());
+
+		for (int vert = 0; vert < numverts; ++vert)
+		{
+			switch ((EVBOComplement) complement)
+			{
+				case VBOCM_Surfaces:
+				{
+					// Write coordinates. Apparently Z must be flipped too?
+					vbodata	<< poly.vertices[vert].x()
+							<< -poly.vertices[vert].y()
+							<< -poly.vertices[vert].z();
+					break;
+				}
+
+				case VBOCM_NormalColors:
+				{
+					writeColor (vbodata, normalColor);
+					break;
+				}
+
+				case VBOCM_PickColors:
+				{
+					writeColor (vbodata, pickColor);
+					break;
+				}
+
+				case VBOCM_BFCFrontColors:
+				{
+					writeColor (vbodata, g_BFCFrontColor);
+					break;
+				}
+
+				case VBOCM_BFCBackColors:
+				{
+					writeColor (vbodata, g_BFCBackColor);
+					break;
+				}
+
+				case VBOCM_NumComplements:
+					break;
+			}
+		}
+	}
+}
+
+// =============================================================================
+//
+void GLCompiler::compileSubObject (LDObject* obj, LDObject* topobj, ObjectVBOInfo* objinfo)
+{
+	switch (obj->type())
+	{
+		// Note: We cannot split quads into triangles here, it would mess up the
+		// wireframe view. Quads must go into separate vbos.
+		case LDObject::ETriangle:
+		case LDObject::EQuad:
+		case LDObject::ELine:
+		case LDObject::ECondLine:
+		{
+			LDPolygon* poly = obj->getPolygon();
+			poly->id = topobj->id();
+			compilePolygon (*poly, topobj, objinfo);
+			delete poly;
+			break;
+		}
+
+		case LDObject::ESubfile:
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			auto data = ref->inlinePolygons();
+
+			for (LDPolygon& poly : data)
+			{
+				poly.id = topobj->id();
+				compilePolygon (poly, topobj, objinfo);
+			}
+			break;
+		}
+
+		default:
+			break;
+	}
+}
+
+// =============================================================================
+//
+void GLCompiler::writeColor (QVector<GLfloat>& array, const QColor& color)
+{
+	array	<< ((float) color.red()) / 255.0f
+			<< ((float) color.green()) / 255.0f
+			<< ((float) color.blue()) / 255.0f
+			<< ((float) color.alpha()) / 255.0f;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/glCompiler.h	Tue Apr 08 11:07:47 2014 +0300
@@ -0,0 +1,82 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LDFORGE_GLCOMPILER_H
+#define LDFORGE_GLCOMPILER_H
+
+#include "main.h"
+#include "glRenderer.h"
+#include "glShared.h"
+#include <QMap>
+
+// =============================================================================
+//
+class GLCompiler
+{
+	public:
+		struct ObjectVBOInfo
+		{
+			QVector<GLfloat> data[g_numVBOs];
+		};
+
+		GLCompiler();
+		~GLCompiler();
+		void				compileDocument (LDDocument* doc);
+		void				dropObject (LDObject* obj);
+		void				initialize();
+		QColor				polygonColor (LDPolygon& poly, LDObject* topobj) const;
+		QColor				indexColorForID (int id) const;
+		void				needMerge();
+		void				prepareVBO (int vbonum);
+		void				stageForCompilation (LDObject* obj);
+
+		static uint32		colorToRGB (const QColor& color);
+
+		static inline int	vboNumber (EVBOSurface surface, EVBOComplement complement)
+		{
+			return (surface * VBOCM_NumComplements) + complement;
+		}
+
+		inline GLuint		vbo (int vbonum) const
+		{
+			return m_vbo[vbonum];
+		}
+
+		inline int			vboCount (int vbonum) const
+		{
+			return m_vboSizes[vbonum] / 3;
+		}
+
+	private:
+		void			compileStaged();
+		void			compileObject (LDObject* obj);
+		void			compileSubObject (LDObject* obj, LDObject* topobj, GLCompiler::ObjectVBOInfo* objinfo);
+		void			writeColor (QVector<float>& array, const QColor& color);
+		void			compilePolygon (LDPolygon& poly, LDObject* topobj, GLCompiler::ObjectVBOInfo* objinfo);
+
+		QMap<LDObject*, ObjectVBOInfo>		m_objectInfo;
+		LDObjectList						m_staged; // Objects that need to be compiled
+		GLuint								m_vbo[g_numVBOs];
+		bool								m_vboChanged[g_numVBOs];
+		int									m_vboSizes[g_numVBOs];
+};
+
+#define checkGLError() { checkGLError_private (__FILE__, __LINE__); }
+void checkGLError_private (const char* file, int line);
+
+#endif // LDFORGE_GLCOMPILER_H
--- a/src/glRenderer.cc	Tue Apr 08 11:07:25 2014 +0300
+++ b/src/glRenderer.cc	Tue Apr 08 11:07:47 2014 +0300
@@ -16,12 +16,16 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#define GL_GLEXT_PROTOTYPES
+#include <GL/glu.h>
+#include <GL/glext.h>
 #include <QGLWidget>
 #include <QWheelEvent>
 #include <QMouseEvent>
 #include <QContextMenuEvent>
 #include <QInputDialog>
 #include <QToolTip>
+#include <qtextdocument.h>
 #include <QTimer>
 #include <GL/glu.h>
 
@@ -38,6 +42,7 @@
 #include "messageLog.h"
 #include "primitives.h"
 #include "misc/ringFinder.h"
+#include "glCompiler.h"
 
 static const LDFixedCameraInfo g_FixedCameras[6] =
 {
@@ -61,7 +66,6 @@
 cfg (String,	gl_bgcolor,				"#FFFFFF")
 cfg (String,	gl_maincolor,			"#A0A0A0")
 cfg (Float,		gl_maincolor_alpha,		1.0)
-cfg (String,	gl_selectcolor,			"#0080FF")
 cfg (Int,		gl_linethickness,		2)
 cfg (Bool,		gl_colorbfc,			false)
 cfg (Int,		gl_camera,				GLRenderer::EFreeCamera)
@@ -96,37 +100,40 @@
 	GL::EFreeCamera
 };
 
-// Definitions for visual axes, drawn on the screen
-const struct LDGLAxis
+struct LDGLAxis
 {
 	const QColor col;
 	const Vertex vert;
-} g_GLAxes[3] =
+};
+
+// Definitions for visual axes, drawn on the screen
+static const LDGLAxis g_GLAxes[3] =
 {
 	{ QColor (255,   0,   0), Vertex (10000, 0, 0) }, // X
 	{ QColor (80,  192,   0), Vertex (0, 10000, 0) }, // Y
 	{ QColor (0,   160, 192), Vertex (0, 0, 10000) }, // Z
 };
 
-static bool g_glInvert = false;
-static QList<int> g_warnedColors;
+static GLuint g_GLAxes_VBO;
+static GLuint g_GLAxes_ColorVBO;
 
 // =============================================================================
 //
 GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent)
 {
 	m_isPicking = m_rangepick = false;
-	m_camera = (GL::EFixedCamera) gl_camera;
+	m_camera = (EFixedCamera) gl_camera;
 	m_drawToolTip = false;
 	m_editMode = ESelectMode;
 	m_rectdraw = false;
 	m_panning = false;
+	m_compiler = new GLCompiler;
 	setDocument (null);
 	setDrawOnly (false);
 	setMessageLog (null);
 	m_width = m_height = -1;
 	m_hoverpos = g_origin;
-
+	m_compiler = new GLCompiler;
 	m_toolTipTimer = new QTimer (this);
 	m_toolTipTimer->setSingleShot (true);
 	connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer()));
@@ -157,6 +164,8 @@
 
 	for (CameraIcon& info : m_cameraIcons)
 		delete info.img;
+
+	delete m_compiler;
 }
 
 // =============================================================================
@@ -242,13 +251,46 @@
 void GLRenderer::initializeGL()
 {
 	setBackground();
-
 	glLineWidth (gl_linethickness);
-
+	glLineStipple (1, 0x6666);
 	setAutoFillBackground (false);
 	setMouseTracking (true);
 	setFocusPolicy (Qt::WheelFocus);
-	compileAllObjects();
+	compiler()->initialize();
+	initializeAxes();
+}
+
+// =============================================================================
+//
+void GLRenderer::initializeAxes()
+{
+	float axesdata[18];
+	float colordata[18];
+	memset (axesdata, 0, sizeof axesdata);
+
+	for (int i = 0; i < 3; ++i)
+	{
+		for (int j = 0; j < 3; ++j)
+		{
+			axesdata[(i * 6) + j] = g_GLAxes[i].vert.getCoordinate (j);
+			axesdata[(i * 6) + 3 + j] = -g_GLAxes[i].vert.getCoordinate (j);
+		}
+
+		for (int j = 0; j < 2; ++j)
+		{
+			colordata[(i * 6) + (j * 3) + 0] = g_GLAxes[i].col.red();
+			colordata[(i * 6) + (j * 3) + 1] = g_GLAxes[i].col.green();
+			colordata[(i * 6) + (j * 3) + 2] = g_GLAxes[i].col.blue();
+		}
+	}
+
+	glGenBuffers (1, &g_GLAxes_VBO);
+	glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_VBO);
+	glBufferData (GL_ARRAY_BUFFER, sizeof axesdata, axesdata, GL_STATIC_DRAW);
+	glGenBuffers (1, &g_GLAxes_ColorVBO);
+	glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_ColorVBO);
+	glBufferData (GL_ARRAY_BUFFER, sizeof colordata, colordata, GL_STATIC_DRAW);
+	glBindBuffer (GL_ARRAY_BUFFER, 0);
 }
 
 // =============================================================================
@@ -282,104 +324,6 @@
 
 // =============================================================================
 //
-void GLRenderer::setObjectColor (LDObject* obj, const ListType list)
-{
-	QColor qcol;
-
-	if (!obj->isColored())
-		return;
-
-	if (list == GL::PickList)
-	{
-		// Make the color by the object's ID if we're picking, so we can make the
-		// ID again from the color we get from the picking results. Be sure to use
-		// the top level parent's index since we want a subfile's children point
-		// to the subfile itself.
-		long i = obj->topLevelParent()->id();
-
-		// Calculate a color based from this index. This method caters for
-		// 16777216 objects. I don't think that'll be exceeded anytime soon. :)
-		// ATM biggest is 53588.dat with 12600 lines.
-		double r = (i / 0x10000) % 0x100,
-			   g = (i / 0x100) % 0x100,
-			   b = i % 0x100;
-
-		qglColor (QColor (r, g, b));
-		return;
-	}
-
-	if ((list == BFCFrontList || list == BFCBackList) &&
-		obj->type() != LDObject::ELine &&
-		obj->type() != LDObject::ECondLine)
-	{
-		if (list == GL::BFCFrontList)
-			qcol = QColor (40, 192, 0);
-		else
-			qcol = QColor (224, 0, 0);
-	}
-	else
-	{
-		if (obj->color() == maincolor)
-			qcol = getMainColor();
-		else
-		{
-			LDColor* col = ::getColor (obj->color());
-
-			if (col)
-				qcol = col->faceColor;
-		}
-
-		if (obj->color() == edgecolor)
-		{
-			LDColor* col;
-
-			if (!gl_blackedges && obj->parent() && (col = ::getColor (obj->parent()->color())))
-				qcol = col->edgeColor;
-			else
-				qcol = (m_darkbg == false) ? Qt::black : Qt::white;
-		}
-
-		if (qcol.isValid() == false)
-		{
-			// The color was unknown. Use main color to make the object at least
-			// not appear pitch-black.
-			if (obj->color() != edgecolor)
-				qcol = getMainColor();
-
-			// Warn about the unknown colors, but only once.
-			for (int i : g_warnedColors)
-				if (obj->color() == i)
-					return;
-
-			print ("%1: Unknown color %2!\n", __func__, obj->color());
-			g_warnedColors << obj->color();
-			return;
-		}
-	}
-
-	int r = qcol.red(),
-		 g = qcol.green(),
-		 b = qcol.blue(),
-		 a = qcol.alpha();
-
-	if (obj->topLevelParent()->isSelected())
-	{
-		// Brighten it up for the select list.
-		QColor selcolor (gl_selectcolor);
-		r = (r + selcolor.red()) / 2;
-		g = (g + selcolor.green()) / 2;
-		b = (b + selcolor.blue()) / 2;
-	}
-
-	glColor4f (
-		((double) r) / 255.0f,
-		((double) g) / 255.0f,
-		((double) b) / 255.0f,
-		((double) a) / 255.0f);
-}
-
-// =============================================================================
-//
 void GLRenderer::refresh()
 {
 	update();
@@ -390,10 +334,9 @@
 //
 void GLRenderer::hardRefresh()
 {
-	compileAllObjects();
+	compiler()->compileDocument (getCurrentDocument());
 	refresh();
-
-	glLineWidth (gl_linethickness);
+	glLineWidth (gl_linethickness); // TODO: ...?
 }
 
 // =============================================================================
@@ -425,7 +368,7 @@
 	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 	glEnable (GL_DEPTH_TEST);
 
-	if (m_camera != EFreeCamera)
+	if (camera() != EFreeCamera)
 	{
 		glMatrixMode (GL_PROJECTION);
 		glPushMatrix();
@@ -434,7 +377,7 @@
 		glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f);
 		glTranslatef (pan (X), pan (Y), 0.0f);
 
-		if (m_camera != EFrontCamera && m_camera != EBackCamera)
+		if (camera() != EFrontCamera && camera() != EBackCamera)
 		{
 			glRotatef (90.0f, g_FixedCameras[camera()].glrotate[0],
 				g_FixedCameras[camera()].glrotate[1],
@@ -442,7 +385,7 @@
 		}
 
 		// Back camera needs to be handled differently
-		if (m_camera == GLRenderer::EBackCamera)
+		if (camera() == EBackCamera)
 		{
 			glRotatef (180.0f, 1.0f, 0.0f, 0.0f);
 			glRotatef (180.0f, 0.0f, 0.0f, 1.0f);
@@ -461,47 +404,86 @@
 		glRotatef (rot (Z), 0.0f, 0.0f, 1.0f);
 	}
 
-	const GL::ListType list = (!isDrawOnly() && isPicking()) ? PickList : NormalList;
-
-	if (gl_colorbfc && !isPicking() && !isDrawOnly())
-	{
-		glEnable (GL_CULL_FACE);
+	glEnableClientState (GL_VERTEX_ARRAY);
+	glEnableClientState (GL_COLOR_ARRAY);
 
-		for (LDObject* obj : document()->objects())
-		{
-			if (obj->isHidden())
-				continue;
-
-			glCullFace (GL_BACK);
-			glCallList (obj->glLists[BFCFrontList]);
-
-			glCullFace (GL_FRONT);
-			glCallList (obj->glLists[BFCBackList]);
-		}
-
-		glDisable (GL_CULL_FACE);
+	if (isPicking())
+	{
+		drawVBOs (VBOSF_Triangles, VBOCM_PickColors, GL_TRIANGLES);
+		drawVBOs (VBOSF_Quads, VBOCM_PickColors, GL_QUADS);
+		drawVBOs (VBOSF_Lines, VBOCM_PickColors, GL_LINES);
+		drawVBOs (VBOSF_CondLines, VBOCM_PickColors, GL_LINES);
 	}
 	else
 	{
-		for (LDObject* obj : document()->objects())
+		if (gl_colorbfc)
+		{
+			glEnable (GL_CULL_FACE);
+			glCullFace (GL_BACK);
+			drawVBOs (VBOSF_Triangles, VBOCM_BFCFrontColors, GL_TRIANGLES);
+			drawVBOs (VBOSF_Quads, VBOCM_BFCFrontColors, GL_QUADS);
+			glCullFace (GL_FRONT);
+			drawVBOs (VBOSF_Triangles, VBOCM_BFCBackColors, GL_TRIANGLES);
+			drawVBOs (VBOSF_Quads, VBOCM_BFCBackColors, GL_QUADS);
+			glDisable (GL_CULL_FACE);
+		}
+		else
 		{
-			if (obj->isHidden())
-				continue;
+			drawVBOs (VBOSF_Triangles, VBOCM_NormalColors, GL_TRIANGLES);
+			drawVBOs (VBOSF_Quads, VBOCM_NormalColors, GL_QUADS);
+		}
+
+		drawVBOs (VBOSF_Lines, VBOCM_NormalColors, GL_LINES);
+		drawVBOs (VBOSF_CondLines, VBOCM_NormalColors, GL_LINES);
 
-			glCallList (obj->glLists[list]);
+		if (gl_axes)
+		{
+			glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_VBO);
+			glVertexPointer (3, GL_FLOAT, 0, NULL);
+			glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_VBO);
+			glColorPointer (3, GL_FLOAT, 0, NULL);
+			glDrawArrays (GL_LINES, 0, 6);
+			checkGLError();
 		}
 	}
 
-	if (gl_axes && !isPicking() && !isDrawOnly())
-		glCallList (m_axeslist);
-
 	glPopMatrix();
+	glBindBuffer (GL_ARRAY_BUFFER, 0);
+	glDisableClientState (GL_VERTEX_ARRAY);
+	glDisableClientState (GL_COLOR_ARRAY);
+	checkGLError();
+	glDisable (GL_CULL_FACE);
 	glMatrixMode (GL_MODELVIEW);
 	glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
 }
 
 // =============================================================================
 //
+void GLRenderer::drawVBOs (EVBOSurface surface, EVBOComplement colors, GLenum type)
+{
+	int surfacenum = m_compiler->vboNumber (surface, VBOCM_Surfaces);
+	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->vboCount (surfacevbo);
+
+	if (count > 0)
+	{
+		glBindBuffer (GL_ARRAY_BUFFER, surfacevbo);
+		glVertexPointer (3, GL_FLOAT, 0, null);
+		checkGLError();
+		glBindBuffer (GL_ARRAY_BUFFER, colorvbo);
+		glColorPointer (4, GL_FLOAT, 0, null);
+		checkGLError();
+		glDrawArrays (type, 0, count);
+		checkGLError();
+	}
+}
+
+// =============================================================================
 // This converts a 2D point on the screen to a 3D point in the model. If 'snap'
 // is true, the 3D point will snap to the current grid.
 //
@@ -510,7 +492,7 @@
 	assert (camera() != EFreeCamera);
 
 	Vertex pos3d;
-	const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera];
+	const LDFixedCameraInfo* cam = &g_FixedCameras[camera()];
 	const Axis axisX = cam->axisX;
 	const Axis axisY = cam->axisY;
 	const int negXFac = cam->negX ? -1 : 1,
@@ -547,7 +529,7 @@
 QPoint GLRenderer::coordconv3_2 (const Vertex& pos3d) const
 {
 	GLfloat m[16];
-	const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera];
+	const LDFixedCameraInfo* cam = &g_FixedCameras[camera()];
 	const Axis axisX = cam->axisX;
 	const Axis axisY = cam->axisY;
 	const int negXFac = cam->negX ? -1 : 1,
@@ -593,15 +575,27 @@
 	if (isDrawOnly())
 		return;
 
-	if (m_camera != EFreeCamera && !isPicking())
+#ifndef RELEASE
+	if (isPicking() == false)
+	{
+		QString text = format ("Rotation: (%1, %2, %3)\nPanning: (%4, %5), Zoom: %6",
+			rot(X), rot(Y), rot(Z), pan(X), pan(Y), zoom());
+		QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
+
+		paint.drawText ((width() - textSize.width()) / 2, height() - textSize.height(), textSize.width(),
+			textSize.height(), Qt::AlignCenter, text);
+	}
+#endif
+
+	if (camera() != EFreeCamera && !isPicking())
 	{
 		// Paint the overlay image if we have one
-		const LDGLOverlay& overlay = currentDocumentData().overlays[m_camera];
+		const LDGLOverlay& overlay = currentDocumentData().overlays[camera()];
 
 		if (overlay.img != null)
 		{
-			QPoint v0 = coordconv3_2 (currentDocumentData().overlays[m_camera].v0),
-					   v1 = coordconv3_2 (currentDocumentData().overlays[m_camera].v1);
+			QPoint v0 = coordconv3_2 (currentDocumentData().overlays[camera()].v0),
+					   v1 = coordconv3_2 (currentDocumentData().overlays[camera()].v1);
 
 			QRect targRect (v0.x(), v0.y(), abs (v1.x() - v0.x()), abs (v1.y() - v0.y())),
 				  srcRect (0, 0, overlay.img->width(), overlay.img->height());
@@ -894,133 +888,6 @@
 
 // =============================================================================
 //
-void GLRenderer::compileAllObjects()
-{
-	if (!document())
-		return;
-
-	// Compiling all is a big job, use a busy cursor
-	setCursor (Qt::BusyCursor);
-
-	m_knownVerts.clear();
-
-	for (LDObject* obj : document()->objects())
-		compileObject (obj);
-
-	// Compile axes
-	glDeleteLists (m_axeslist, 1);
-	m_axeslist = glGenLists (1);
-	glNewList (m_axeslist, GL_COMPILE);
-	glBegin (GL_LINES);
-
-	for (const LDGLAxis& ax : g_GLAxes)
-	{
-		qglColor (ax.col);
-		compileVertex (ax.vert);
-		compileVertex (-ax.vert);
-	}
-
-	glEnd();
-	glEndList();
-
-	setCursor (Qt::ArrowCursor);
-}
-
-// =============================================================================
-//
-void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype)
-{
-	glBegin (gltype);
-
-	const int numverts = (obj->type() != LDObject::ECondLine) ? obj->vertices() : 2;
-
-	if (g_glInvert == false)
-		for (int i = 0; i < numverts; ++i)
-			compileVertex (obj->vertex (i));
-	else
-		for (int i = numverts - 1; i >= 0; --i)
-			compileVertex (obj->vertex (i));
-
-	glEnd();
-}
-
-// =============================================================================
-//
-void GLRenderer::compileList (LDObject* obj, const GLRenderer::ListType list)
-{
-	setObjectColor (obj, list);
-
-	switch (obj->type())
-	{
-		case LDObject::ELine:
-		{
-			compileSubObject (obj, GL_LINES);
-		} break;
-
-		case LDObject::ECondLine:
-		{
-			// Draw conditional lines with a dash pattern - however, use a full
-			// line when drawing a pick list to make selecting them easier.
-			if (list != GL::PickList)
-			{
-				glLineStipple (1, 0x6666);
-				glEnable (GL_LINE_STIPPLE);
-			}
-
-			compileSubObject (obj, GL_LINES);
-
-			glDisable (GL_LINE_STIPPLE);
-		} break;
-
-		case LDObject::ETriangle:
-		{
-			compileSubObject (obj, GL_TRIANGLES);
-		} break;
-
-		case LDObject::EQuad:
-		{
-			compileSubObject (obj, GL_QUADS);
-		} break;
-
-		case LDObject::ESubfile:
-		{
-			LDSubfile* ref = static_cast<LDSubfile*> (obj);
-			LDObjectList objs;
-
-			objs = ref->inlineContents (LDSubfile::DeepCacheInline | LDSubfile::RendererInline);
-			bool oldinvert = g_glInvert;
-
-			if (ref->transform().getDeterminant() < 0)
-				g_glInvert = !g_glInvert;
-
-			LDObject* prev = ref->previous();
-
-			if (prev && prev->type() == LDObject::EBFC && static_cast<LDBFC*> (prev)->statement() == LDBFC::InvertNext)
-				g_glInvert = !g_glInvert;
-
-			for (LDObject* obj : objs)
-			{
-				compileList (obj, list);
-				obj->destroy();
-			}
-
-			g_glInvert = oldinvert;
-		} break;
-
-		default:
-			break;
-	}
-}
-
-// =============================================================================
-//
-void GLRenderer::compileVertex (const Vertex& vrt)
-{
-	glVertex3d (vrt[X], -vrt[Y], -vrt[Z]);
-}
-
-// =============================================================================
-//
 void GLRenderer::clampAngle (double& angle) const
 {
 	while (angle < 0)
@@ -1375,13 +1242,14 @@
 	{
 		qint32 idx =
 			(*(pixelptr + 0) * 0x10000) +
-			(*(pixelptr + 1) * 0x00100) +
-			(*(pixelptr + 2) * 0x00001);
+			(*(pixelptr + 1) * 0x100) +
+			*(pixelptr + 2);
 		pixelptr += 4;
 
 		if (idx == 0xFFFFFF)
 			continue; // White is background; skip
 
+		dprint ("id: %1\n", idx);
 		LDObject* obj = LDObject::fromID (idx);
 		assert (obj != null);
 
@@ -1441,7 +1309,7 @@
 		case ECircleMode:
 		{
 			// Cannot draw into the free camera - use top instead.
-			if (m_camera == EFreeCamera)
+			if (camera() == EFreeCamera)
 				setCamera (ETopCamera);
 
 			// Disable the context menu - we need the right mouse button
@@ -1711,7 +1579,7 @@
 //
 void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const
 {
-	const LDFixedCameraInfo* cam = &g_FixedCameras[m_camera];
+	const LDFixedCameraInfo* cam = &g_FixedCameras[camera()];
 	relX = cam->axisX;
 	relY = cam->axisY;
 }
@@ -1726,10 +1594,11 @@
 	{
 		for (int i = 0; i < obj->vertices(); ++i)
 			verts << obj->vertex (i);
-	} elif (obj->type() == LDObject::ESubfile)
+	}
+	elif (obj->type() == LDObject::ESubfile)
 	{
 		LDSubfile* ref = static_cast<LDSubfile*> (obj);
-		LDObjectList objs = ref->inlineContents (LDSubfile::DeepCacheInline);
+		LDObjectList objs = ref->inlineContents (true, false);
 
 		for (LDObject* obj : objs)
 		{
@@ -1745,32 +1614,27 @@
 //
 void GLRenderer::compileObject (LDObject* obj)
 {
-	deleteLists (obj);
-
-	for (const GL::ListType listType : g_glListTypes)
-	{
-		if (isDrawOnly() && listType != GL::NormalList)
-			continue;
-
-		GLuint list = glGenLists (1);
-		glNewList (list, GL_COMPILE);
-
-		obj->glLists[listType] = list;
-		compileList (obj, listType);
-
-		glEndList();
-	}
+	compiler()->stageForCompilation (obj);
 
 	// Mark in known vertices of this object
+	/*
 	QList<Vertex> verts = getVertices (obj);
 	m_knownVerts << verts;
 	removeDuplicates (m_knownVerts);
+	*/
 
 	obj->setGLInit (true);
 }
 
 // =============================================================================
 //
+void GLRenderer::forgetObject (LDObject* obj)
+{
+	compiler()->dropObject (obj);
+}
+
+// =============================================================================
+//
 uchar* GLRenderer::getScreencap (int& w, int& h)
 {
 	w = m_width;
@@ -1794,7 +1658,7 @@
 	// We come here if the cursor has stayed in one place for longer than a
 	// a second. Check if we're holding it over a camera icon - if so, draw
 	// a tooltip.
-for (CameraIcon & icon : m_cameraIcons)
+	for (CameraIcon & icon : m_cameraIcons)
 	{
 		if (icon.destRect.contains (m_pos))
 		{
@@ -1808,24 +1672,10 @@
 
 // =============================================================================
 //
-void GLRenderer::deleteLists (LDObject* obj)
-{
-	// Delete the lists but only if they have been initialized
-	if (!obj->isGLInit())
-		return;
-
-	for (const GL::ListType listType : g_glListTypes)
-		glDeleteLists (obj->glLists[listType], 1);
-
-	obj->setGLInit (false);
-}
-
-// =============================================================================
-//
 Axis GLRenderer::getCameraAxis (bool y, GLRenderer::EFixedCamera camid)
 {
 	if (camid == (GL::EFixedCamera) - 1)
-		camid = m_camera;
+		camid = camera();
 
 	const LDFixedCameraInfo* cam = &g_FixedCameras[camid];
 	return (y) ? cam->axisY : cam->axisX;
@@ -1841,6 +1691,7 @@
 	if (img->isNull())
 	{
 		critical (tr ("Failed to load overlay image!"));
+		currentDocumentData().overlays[cam].invalid = true;
 		delete img;
 		return false;
 	}
@@ -1853,6 +1704,7 @@
 	info.ox = x;
 	info.oy = y;
 	info.img = img;
+	info.invalid = false;
 
 	if (info.lw == 0)
 		info.lw = (info.lh * img->width()) / img->height();
@@ -1872,11 +1724,11 @@
 
 	// Set alpha of all pixels to 0.5
 	for (long i = 0; i < img->width(); ++i)
-		for (long j = 0; j < img->height(); ++j)
-		{
-			uint32 pixel = img->pixel (i, j);
-			img->setPixel (i, j, 0x80000000 | (pixel & 0x00FFFFFF));
-		}
+	for (long j = 0; j < img->height(); ++j)
+	{
+		uint32 pixel = img->pixel (i, j);
+		img->setPixel (i, j, 0x80000000 | (pixel & 0x00FFFFFF));
+	}
 
 	updateOverlayObjects();
 	return true;
@@ -1940,9 +1792,12 @@
 //
 void GLRenderer::zoomToFit()
 {
+	print ("zooming %1 to fit..\n", camera());
+	zoom() = 30.0f;
+
 	if (document() == null || m_width == -1 || m_height == -1)
 	{
-		zoom() = 30.0f;
+		print ("document is invalid!\n");
 		return;
 	}
 
@@ -1984,22 +1839,23 @@
 			if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white)
 			{
 				filled = true;
-				goto endOfLoop;
+				break;
 			}
 		}
 
 		// Left and right edges
-		for (int i = 0; i < h; ++i)
+		if (filled == false)
 		{
-			if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white)
+			for (int i = 0; i < h; ++i)
 			{
-				filled = true;
-				goto endOfLoop;
+				if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white)
+				{
+					filled = true;
+					break;
+				}
 			}
 		}
 
-endOfLoop:
-
 		delete[] cap;
 
 		if (firstrun)
@@ -2033,6 +1889,7 @@
 
 	setBackground();
 	setPicking (false);
+	print ("zoom to fit done.\n");
 }
 
 // =============================================================================
@@ -2140,7 +1997,7 @@
 			delete meta.img;
 			meta.img = null;
 		}
-		elif (ovlobj && (!meta.img || meta.fname != ovlobj->fileName()))
+		elif (ovlobj && (meta.img == null || meta.fname != ovlobj->fileName()) && meta.invalid == false)
 			setupOverlay (cam, ovlobj->fileName(), ovlobj->x(),
 				ovlobj->y(), ovlobj->width(), ovlobj->height());
 	}
@@ -2158,7 +2015,7 @@
 		LDGLOverlay& meta = currentDocumentData().overlays[cam];
 		LDOverlay* ovlobj = findOverlayObject (cam);
 
-		if (!meta.img && ovlobj)
+		if (meta.img == null && ovlobj != null)
 		{
 			// If this is the last overlay image, we need to remove the empty space after it as well.
 			LDObject* nextobj = ovlobj->next();
@@ -2169,7 +2026,8 @@
 			// If the overlay object was there and the overlay itself is
 			// not, remove the object.
 			ovlobj->destroy();
-		} elif (meta.img && !ovlobj)
+		}
+		elif (meta.img != null && ovlobj == null)
 		{
 			// Inverse case: image is there but the overlay object is
 			// not, thus create the object.
--- a/src/glRenderer.h	Tue Apr 08 11:07:25 2014 +0300
+++ b/src/glRenderer.h	Tue Apr 08 11:07:47 2014 +0300
@@ -22,7 +22,9 @@
 #include "macros.h"
 #include "ldObject.h"
 #include "ldDocument.h"
+#include "glShared.h"
 
+class GLCompiler;
 class MessageManager;
 class QDialogButtonBox;
 class RadioGroup;
@@ -49,6 +51,7 @@
 					lh;
 	QString			fname;
 	QImage*			img;
+	bool			invalid;
 };
 
 struct LDFixedCameraInfo
@@ -77,12 +80,23 @@
 
 	LDGLData()
 	{
-		for (int i = 0; i < 6; ++i)
+		for (int i = 0; i < 7; ++i)
 		{
-			overlays[i].img = null;
-			depthValues[i] = 0.0f;
+			if (i < 6)
+			{
+				overlays[i].img = null;
+				overlays[i].invalid = false;
+				depthValues[i] = 0.0f;
+			}
+
+			zoom[i] = 30.0;
+			panX[i] = 0.0;
+			panY[i] = 0.0;
 		}
 
+		rotX = 0;
+		rotY = 0;
+		rotZ = 0;
 		init = false;
 	}
 };
@@ -119,10 +133,10 @@
 		// and Qt doesn't like that.
 		struct CameraIcon
 		{
-			QPixmap*			img;
-			QRect				srcRect,
-								destRect,
-								selRect;
+			QPixmap*		img;
+			QRect			srcRect,
+							destRect,
+							selRect;
 			EFixedCamera	cam;
 		};
 
@@ -132,6 +146,7 @@
 		PROPERTY (private,	bool,				isPicking,	setPicking,		STOCK_WRITE)
 		PROPERTY (public,	LDDocument*,		document,	setDocument,	CUSTOM_WRITE)
 		PROPERTY (public,	EditMode,			editMode,	setEditMode,	CUSTOM_WRITE)
+		PROPERTY (private,	GLCompiler*,		compiler,	setCompiler,	STOCK_WRITE)
 
 	public:
 		GLRenderer (QWidget* parent = null);
@@ -144,13 +159,12 @@
 
 		void           clearOverlay();
 		void           compileObject (LDObject* obj);
-		void           compileAllObjects();
 		void           drawGLScene();
 		void           endDraw (bool accept);
+		void           forgetObject (LDObject* obj);
 		Axis           getCameraAxis (bool y, EFixedCamera camid = (EFixedCamera) - 1);
 		const char*    getCameraName() const;
 		double         getDepthValue() const;
-		QColor         getMainColor();
 		LDGLOverlay&   getOverlay (int newcam);
 		uchar*         getScreencap (int& w, int& h);
 		void           hardRefresh();
@@ -169,6 +183,7 @@
 		void           zoomAllToFit();
 
 		static void    deleteLists (LDObject* obj);
+		static QColor  getMainColor();
 
 	protected:
 		void           contextMenuEvent (QContextMenuEvent* ev);
@@ -243,6 +258,12 @@
 		// Convert a 2D point to a 3D point
 		Vertex         coordconv2_3 (const QPoint& pos2d, bool snap) const;
 
+		// Draw a VBO array
+		void           drawVBOs (EVBOSurface surface, EVBOComplement colors, GLenum type);
+
+		// Determine which color to draw text with
+		QColor         getTextPen() const;
+
 		// Convert a 3D point to a 2D point
 		QPoint         coordconv3_2 (const Vertex& pos3d) const;
 
@@ -294,6 +315,7 @@
 
 	private slots:
 		void           slot_toolTipTimer();
+		void initializeAxes();
 };
 
 // Alias for short namespaces
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/glShared.h	Tue Apr 08 11:07:47 2014 +0300
@@ -0,0 +1,73 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013, 2014 Santeri Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LDFORGE_GLSHARED_H
+#define LDFORGE_GLSHARED_H
+#include <QString>
+
+class LDObject;
+
+struct LDPolygon
+{
+	char		num;
+	Vertex		vertices[4];
+	int			id;
+	int			color;
+	QString		origin;
+
+	inline int numVertices() const
+	{
+		return (num == 5) ? 4 : num;
+	}
+};
+
+enum EVBOSurface
+{
+	VBOSF_Lines,
+	VBOSF_Triangles,
+	VBOSF_Quads,
+	VBOSF_CondLines,
+	VBOSF_NumSurfaces
+};
+
+enum EVBOComplement
+{
+	VBOCM_Surfaces,
+	VBOCM_NormalColors,
+	VBOCM_PickColors,
+	VBOCM_BFCFrontColors,
+	VBOCM_BFCBackColors,
+	VBOCM_NumComplements
+};
+
+// KDevelop doesn't seem to understand some VBO stuff
+#ifdef IN_IDE_PARSER
+using GLint = int;
+using GLsizei = int;
+using GLenum = unsigned int;
+using GLuint = unsigned int;
+void glBindBuffer (GLenum, GLuint);
+void glGenBuffers (GLuint, GLuint*);
+void glDeleteBuffers (GLuint, GLuint*);
+void glBufferData (GLuint, GLuint, void*, GLuint);
+void glBufferSubData (GLenum, GLint, GLsizei, void*);
+#endif
+
+static const int g_numVBOs = VBOSF_NumSurfaces * VBOCM_NumComplements;
+
+#endif // LDFORGE_GLSHARED_H
\ No newline at end of file
--- a/src/ldDocument.cc	Tue Apr 08 11:07:25 2014 +0300
+++ b/src/ldDocument.cc	Tue Apr 08 11:07:47 2014 +0300
@@ -19,7 +19,9 @@
 #include <QMessageBox>
 #include <QFileDialog>
 #include <QDir>
+#include <QTime>
 #include <QApplication>
+
 #include "main.h"
 #include "configuration.h"
 #include "ldDocument.h"
@@ -29,6 +31,7 @@
 #include "dialogs.h"
 #include "glRenderer.h"
 #include "misc/invokeLater.h"
+#include "glCompiler.h"
 
 cfg (String, io_ldpath, "");
 cfg (List, io_recentfiles, {});
@@ -128,6 +131,7 @@
 	setTabIndex (-1);
 	setHistory (new History);
 	history()->setDocument (this);
+	m_needsGLReInit = true;
 }
 
 // =============================================================================
@@ -144,11 +148,7 @@
 	for (LDObject* obj : objects())
 		obj->destroy();
 
-	// Clear the cache as well
-	for (LDObject* obj : cache())
-		obj->destroy();
-
-	delete m_history;
+	delete history();
 	delete m_gldata;
 
 	// If we just closed the current file, we need to set the current
@@ -1073,6 +1073,7 @@
 #endif
 
 	obj->setDocument (this);
+	g_win->R()->compileObject (obj);
 	return getObjectCount() - 1;
 }
 
@@ -1092,6 +1093,7 @@
 	history()->add (new AddHistory (pos, obj));
 	m_objects.insert (pos, obj);
 	obj->setDocument (this);
+	g_win->R()->compileObject (obj);
 
 #ifdef DEBUG
 	if (!isImplicit())
@@ -1142,6 +1144,7 @@
 	m_objects[idx]->unselect();
 	m_objects[idx]->setDocument (null);
 	obj->setDocument (this);
+	g_win->R()->compileObject (obj);
 	m_objects[idx] = obj;
 }
 
@@ -1199,81 +1202,76 @@
 
 // =============================================================================
 //
-LDObjectList LDDocument::inlineContents (LDSubfile::InlineFlags flags)
+void LDDocument::initializeGLData()
+{
+	print (getDisplayName() + ": Initializing GL data");
+	LDObjectList objs = inlineContents (true, true);
+
+	for (LDObject* obj : objs)
+	{
+		assert (obj->type() != LDObject::ESubfile);
+		LDPolygon* data = obj->getPolygon();
+
+		if (data != null)
+		{
+			m_polygonData << *data;
+			delete data;
+		}
+
+		obj->destroy();
+	}
+
+	m_needsGLReInit = false;
+}
+
+// =============================================================================
+//
+QList<LDPolygon> LDDocument::inlinePolygons()
+{
+	if (m_needsGLReInit == true)
+		initializeGLData();
+
+	return polygonData();
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
+LDObjectList LDDocument::inlineContents (bool deep, bool renderinline)
 {
 	// Possibly substitute with logoed studs:
 	// stud.dat -> stud-logo.dat
 	// stud2.dat -> stud-logo2.dat
-	if (gl_logostuds && (flags & LDSubfile::RendererInline))
+	if (gl_logostuds && renderinline)
 	{
 		// Ensure logoed studs are loaded first
 		loadLogoedStuds();
 
-		if (name() == "stud.dat" && g_logoedStud)
-			return g_logoedStud->inlineContents (flags);
-		elif (name() == "stud2.dat" && g_logoedStud2)
-			return g_logoedStud2->inlineContents (flags);
+		if (name() == "stud.dat" && g_logoedStud != null)
+			return g_logoedStud->inlineContents (deep, renderinline);
+		elif (name() == "stud2.dat" && g_logoedStud2 != null)
+			return g_logoedStud2->inlineContents (deep, renderinline);
 	}
 
 	LDObjectList objs, objcache;
 
-	bool deep = flags & LDSubfile::DeepInline,
-		 doCache = flags & LDSubfile::CacheInline;
-
-	if (m_needsCache)
-	{
-		m_cache.clear();
-		doCache = true;
-	}
-
-	// If we have this cached, just create a copy of that
-	if (deep && cache().isEmpty() == false)
+	for (LDObject* obj : objects())
 	{
-		for (LDObject* obj : cache())
-			objs << obj->createCopy();
-	}
-	else
-	{
-		if (!deep)
-			doCache = false;
-
-		for (LDObject* obj : objects())
-		{
-			// Skip those without scemantic meaning
-			if (!obj->isScemantic())
-				continue;
+		// Skip those without scemantic meaning
+		if (!obj->isScemantic())
+			continue;
 
-			// Got another sub-file reference, inline it if we're deep-inlining. If not,
-			// just add it into the objects normally. Also, we only cache immediate
-			// subfiles and this is not one. Yay, recursion!
-			if (deep && obj->type() == LDObject::ESubfile)
-			{
-				LDSubfile* ref = static_cast<LDSubfile*> (obj);
-
-				// We only want to cache immediate subfiles, so shed the caching
-				// flag when recursing deeper in hierarchy.
-				LDObjectList otherobjs = ref->inlineContents (flags & ~ (LDSubfile::CacheInline));
+		// Got another sub-file reference, inline it if we're deep-inlining. If not,
+		// just add it into the objects normally. Yay, recursion!
+		if (deep == true && obj->type() == LDObject::ESubfile)
+		{
+			LDSubfile* ref = static_cast<LDSubfile*> (obj);
+			LDObjectList otherobjs = ref->inlineContents (deep, renderinline);
 
-				for (LDObject* otherobj : otherobjs)
-				{
-					// Cache this object, if desired
-					if (doCache)
-						objcache << otherobj->createCopy();
-
-					objs << otherobj;
-				}
-			}
-			else
-			{
-				if (doCache)
-					objcache << obj->createCopy();
-
-				objs << obj->createCopy();
-			}
+			for (LDObject* otherobj : otherobjs)
+				objs << otherobj;
 		}
-
-		if (doCache)
-			setCache (objcache);
+		else
+			objs << obj->createCopy();
 	}
 
 	return objs;
@@ -1309,6 +1307,7 @@
 		g_win->updateTitle();
 		g_win->R()->setDocument (f);
 		g_win->R()->repaint();
+		g_win->R()->compiler()->needMerge();
 		print ("Changed file to %1", f->getDisplayName());
 	}
 }
--- a/src/ldDocument.h	Tue Apr 08 11:07:25 2014 +0300
+++ b/src/ldDocument.h	Tue Apr 08 11:07:47 2014 +0300
@@ -21,11 +21,13 @@
 #include "main.h"
 #include "ldObject.h"
 #include "editHistory.h"
+#include "glShared.h"
 
 class History;
 class OpenProgressDialog;
 class LDDocumentPointer;
 struct LDGLData;
+class GLCompiler;
 
 namespace LDPaths
 {
@@ -66,6 +68,7 @@
 		PROPERTY (public,	bool,			isImplicit,		setImplicit,		STOCK_WRITE)
 		PROPERTY (public,	long,			savePosition,	setSavePosition,	STOCK_WRITE)
 		PROPERTY (public,	int,			tabIndex,		setTabIndex,		STOCK_WRITE)
+		PROPERTY (public,	QList<LDPolygon>,	polygonData,	setPolygonData,	STOCK_WRITE)
 
 	public:
 		LDDocument();
@@ -78,7 +81,8 @@
 		QString getDisplayName();
 		const LDObjectList& getSelection() const;
 		bool hasUnsavedChanges() const; // Does this document have unsaved changes?
-		LDObjectList inlineContents (LDSubfile::InlineFlags flags);
+		void initializeGLData();
+		LDObjectList inlineContents (bool deep, bool renderinline);
 		void insertObj (int pos, LDObject* obj);
 		int getObjectCount() const;
 		LDObject* getObject (int pos) const;
@@ -88,6 +92,7 @@
 		void setObject (int idx, LDObject* obj);
 		void addReference (LDDocumentPointer* ptr);
 		void removeReference (LDDocumentPointer* ptr);
+		QList<LDPolygon> inlinePolygons();
 
 		inline LDDocument& operator<< (LDObject* obj)
 		{
@@ -145,9 +150,9 @@
 		LDObjectList			m_sel;
 		LDGLData*				m_gldata;
 
-		// If set to true, next inline of this document discards the cache and
-		// re-builds it.
-		bool					m_needsCache;
+		// If set to true, next polygon inline of this document discards the
+		// stored polygon data and re-builds it.
+		bool					m_needsGLReInit;
 
 		static LDDocument*		m_curdoc;
 };
--- a/src/ldObject.cc	Tue Apr 08 11:07:25 2014 +0300
+++ b/src/ldObject.cc	Tue Apr 08 11:07:47 2014 +0300
@@ -16,6 +16,7 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+
 #include "main.h"
 #include "ldObject.h"
 #include "ldDocument.h"
@@ -24,6 +25,7 @@
 #include "editHistory.h"
 #include "glRenderer.h"
 #include "colors.h"
+#include "glCompiler.h"
 
 cfg (String, ld_defaultname, "");
 cfg (String, ld_defaultuser, "");
@@ -269,7 +271,7 @@
 		document()->forgetObject (this);
 
 	// Delete the GL lists
-	GL::deleteLists (this);
+	g_win->R()->forgetObject (this);
 
 	// Remove this object from the list of LDObjects
 	g_LDObjects.removeOne (this);
@@ -318,10 +320,10 @@
 }
 
 // =============================================================================
-//
-LDObjectList LDSubfile::inlineContents (InlineFlags flags)
+// -----------------------------------------------------------------------------
+LDObjectList LDSubfile::inlineContents (bool deep, bool render)
 {
-	LDObjectList objs = fileInfo()->inlineContents (flags);
+	LDObjectList objs = fileInfo()->inlineContents (deep, render);
 
 	// Transform the objects
 	for (LDObject* obj : objs)
@@ -336,6 +338,45 @@
 
 // =============================================================================
 //
+LDPolygon* LDObject::getPolygon()
+{
+	Type ot = type();
+	int num =
+		(ot == LDObject::ELine)		?	2 :
+		(ot == LDObject::ETriangle)	?	3 :
+		(ot == LDObject::EQuad)		?	4 :
+		(ot == LDObject::ECondLine)	?	5 :
+										0;
+	if (num == 0)
+		return null;
+
+	LDPolygon* data = new LDPolygon;
+	data->id = id();
+	data->num = num;
+	data->color = color();
+	data->origin = origin();
+
+	for (int i = 0; i < data->numVertices(); ++i)
+		data->vertices[i] = vertex (i);
+
+	return data;
+}
+
+// =============================================================================
+//
+QList<LDPolygon> LDSubfile::inlinePolygons()
+{
+	QList<LDPolygon> data = fileInfo()->inlinePolygons();
+
+	for (LDPolygon& entry : data)
+		for (int i = 0; i < entry.numVertices(); ++i)
+			entry.vertices[i].transform (transform(), position());
+
+	return data;
+}
+
+// =============================================================================
+// -----------------------------------------------------------------------------
 long LDObject::lineNumber() const
 {
 	assert (document() != null);
@@ -646,6 +687,7 @@
 void LDOverlay::invert() {}
 
 // =============================================================================
+//
 // Hook the set accessors of certain properties to this changeProperty function.
 // It takes care of history management so we can capture low-level changes, this
 // makes history stuff work out of the box.
@@ -657,14 +699,17 @@
 	if (*ptr == val)
 		return;
 
-	if (obj->document() && (idx = obj->lineNumber()) != -1)
+	if (obj->document() != null && (idx = obj->lineNumber()) != -1)
 	{
 		QString before = obj->asText();
 		*ptr = val;
 		QString after = obj->asText();
 
 		if (before != after)
+		{
 			obj->document()->addToHistory (new EditHistory (idx, before, after));
+			g_win->R()->compileObject (obj);
+		}
 	}
 	else
 		*ptr = val;
@@ -821,5 +866,28 @@
 	*/
 
 	LDObject* copy = parseLine (asText());
+
+	if (origin().isEmpty() == false)
+		copy->setOrigin (origin());
+	elif (document() != null)
+		copy->setOrigin (document()->getDisplayName() + ":" + QString::number (lineNumber()));
+
 	return copy;
-}
\ No newline at end of file
+}
+
+// =============================================================================
+//
+void LDSubfile::setFileInfo (const LDDocumentPointer& a)
+{
+	m_fileInfo = a;
+
+	// If it's an immediate subfile reference (i.e. this subfile belongs in an
+	// explicit file), we need to pre-compile the GL polygons for the document
+	// if they don't exist already.
+	if (a != null &&
+		a->isImplicit() == false &&
+		a->polygonData().isEmpty())
+	{
+		a->initializeGLData();
+	}
+};
--- a/src/ldObject.h	Tue Apr 08 11:07:25 2014 +0300
+++ b/src/ldObject.h	Tue Apr 08 11:07:47 2014 +0300
@@ -20,6 +20,7 @@
 #include "main.h"
 #include "basics.h"
 #include "misc/documentPointer.h"
+#include "glShared.h"
 
 #define LDOBJ(T)										\
 protected:												\
@@ -72,6 +73,7 @@
 	PROPERTY (private,		int,			id,				setID,			STOCK_WRITE)
 	PROPERTY (public,		int,			color,			setColor,		CUSTOM_WRITE)
 	PROPERTY (public,		bool,			isGLInit,		setGLInit,		STOCK_WRITE)
+	PROPERTY (public,		QString,		origin,			setOrigin,		STOCK_WRITE)
 
 	public:
 		// Object type codes.
@@ -172,6 +174,7 @@
 		// Get a description of a list of LDObjects
 		static QString describeObjects (const LDObjectList& objs);
 		static LDObject* fromID (int id);
+		LDPolygon* getPolygon();
 
 		// TODO: make these private!
 		// OpenGL list for this object
@@ -381,7 +384,7 @@
 	LDOBJ_COLORED
 	LDOBJ_SCEMANTIC
 	LDOBJ_HAS_MATRIX
-	PROPERTY (public, LDDocumentPointer, fileInfo, setFileInfo, STOCK_WRITE)
+	PROPERTY (public, LDDocumentPointer, fileInfo, setFileInfo, CUSTOM_WRITE)
 
 	public:
 		enum InlineFlag
@@ -401,7 +404,8 @@
 
 		// Inlines this subfile. Note that return type is an array of heap-allocated
 		// LDObject copies, they must be deleted manually.
-		LDObjectList inlineContents (InlineFlags flags);
+		LDObjectList inlineContents (bool deep, bool render);
+		QList<LDPolygon> inlinePolygons();
 
 	protected:
 		~LDSubfile();
--- a/src/macros.h	Tue Apr 08 11:07:25 2014 +0300
+++ b/src/macros.h	Tue Apr 08 11:07:47 2014 +0300
@@ -17,6 +17,7 @@
  */
 
 #pragma once
+#include <type_traits>
 
 #ifndef __GNUC__
 # define __attribute__(X)
@@ -24,6 +25,20 @@
 
 // =============================================================================
 //
+#define countof(A) (safeCountOf<std::is_array<decltype(A)>::value, (sizeof A / sizeof *A)>())
+
+template<bool isArray, size_t elems>
+inline constexpr int safeCountOf() __attribute__ ((always_inline));
+
+template<bool isArray, size_t elems>
+inline constexpr int safeCountOf()
+{
+	static_assert (isArray, "parameter to countof must be an array");
+	return elems;
+}
+
+// =============================================================================
+//
 #define PROPERTY(ACCESS, TYPE, READ, WRITE, WRITETYPE)			\
 private:														\
 	TYPE m_##READ;												\

mercurial