diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 3c5555eff..d1089bebe 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -713,6 +713,8 @@ set (PCH_SOURCES
 	glbackend/glbackend.cpp
 	glbackend/gl_palmanager.cpp
 	glbackend/gl_texture.cpp
+	glbackend/gl_buffers.cpp
+	glbackend/hw_draw2d.cpp
 	
 	mact/src/animlib.cpp
 	mact/src/control.cpp
diff --git a/source/common/2d/renderstyle.h b/source/common/2d/renderstyle.h
index ac4419ee1..12ac15191 100644
--- a/source/common/2d/renderstyle.h
+++ b/source/common/2d/renderstyle.h
@@ -108,6 +108,8 @@ enum ERenderAlpha
 	STYLEALPHA_InvSrcCol,	// Blend factor is 1.0 - color (HWR only)
 	STYLEALPHA_DstCol,		// Blend factor is dest. color (HWR only)
 	STYLEALPHA_InvDstCol,	// Blend factor is 1.0 - dest. color (HWR only)
+	STYLEALPHA_Dst,			// Blend factor is dest. alpha
+	STYLEALPHA_InvDst,		// Blend factor is 1.0 - dest. alpha
 	STYLEALPHA_MAX
 };
 
diff --git a/source/common/2d/v_2ddrawer.cpp b/source/common/2d/v_2ddrawer.cpp
index ccbc752ed..bdc0da2f5 100644
--- a/source/common/2d/v_2ddrawer.cpp
+++ b/source/common/2d/v_2ddrawer.cpp
@@ -247,7 +247,7 @@ void F2DDrawer::AddTexture(FTexture *img, DrawParms &parms)
 	dg.mVertCount = 4;
 	dg.mTexture = img;
 
-	dg.mTranslation = 0;
+	dg.mRemapIndex = parms.remap;
 	SetStyle(img, parms, vertexcolor, dg);
 
 	u1 = parms.srcx;
diff --git a/source/common/2d/v_2ddrawer.h b/source/common/2d/v_2ddrawer.h
index e658dcdc0..8e2d52e6e 100644
--- a/source/common/2d/v_2ddrawer.h
+++ b/source/common/2d/v_2ddrawer.h
@@ -67,7 +67,7 @@ public:
 		int mIndexCount;
 
 		FTexture *mTexture;
-		FRemapTable *mTranslation;
+		int mRemapIndex;
 		PalEntry mSpecialColormap[2];
 		int mScissor[4];
 		int mDesaturate;
@@ -86,7 +86,7 @@ public:
 		{
 			return mTexture == other.mTexture &&
 				mType == other.mType &&
-				mTranslation == other.mTranslation &&
+				mRemapIndex == other.mRemapIndex &&
 				mSpecialColormap[0].d == other.mSpecialColormap[0].d &&
 				mSpecialColormap[1].d == other.mSpecialColormap[1].d &&
 				!memcmp(mScissor, other.mScissor, sizeof(mScissor)) &&
diff --git a/source/common/2d/v_draw.cpp b/source/common/2d/v_draw.cpp
index e2057f91e..0b44e95d8 100644
--- a/source/common/2d/v_draw.cpp
+++ b/source/common/2d/v_draw.cpp
@@ -265,7 +265,7 @@ bool ParseDrawTextureTags(FTexture *img, double x, double y, uint32_t tag, Va_Li
 	parms->burn = false;
 	parms->monospace = EMonospacing::Off;
 	parms->spacing = 0;
-	parms->remap = 0;
+	parms->remap = -1;
 
 	// Parse the tag list for attributes. (For floating point attributes,
 	// consider that the C ABI dictates that all floats be promoted to
diff --git a/source/common/2d/v_drawtext.cpp b/source/common/2d/v_drawtext.cpp
index 9e379ca68..59b2cf753 100644
--- a/source/common/2d/v_drawtext.cpp
+++ b/source/common/2d/v_drawtext.cpp
@@ -126,7 +126,7 @@ void DrawTextCommon(F2DDrawer* drawer, FFont *font, int normalcolor, double x, d
 	double 		cx;
 	double 		cy;
 	int			boldcolor;
-	FRemapTable *range;
+	int range;
 	int			kerning;
 	FTexture *pic;
 
diff --git a/source/common/fonts/font.cpp b/source/common/fonts/font.cpp
index bc913a83d..bb1653544 100644
--- a/source/common/fonts/font.cpp
+++ b/source/common/fonts/font.cpp
@@ -52,6 +52,7 @@
 #include "myiswalpha.h"
 #include "fontchars.h"
 #include "imagehelpers.h"
+#include "glbackend/glbackend.h"
 
 #include "fontinternals.h"
 
@@ -219,7 +220,7 @@ void FFont::SetDefaultTranslation(uint32_t *othercolors)
 	SimpleTranslation(mycolors, mytranslation, myreverse, myluminosity);
 	SimpleTranslation(othercolors, othertranslation, otherreverse, otherluminosity);
 
-	FRemapTable remap(ActiveColors);
+	FRemapTable remap;
 	remap.Palette[0] = 0;
 
 	for (unsigned l = 1; l < myluminosity.Size(); l++)
@@ -247,7 +248,7 @@ void FFont::SetDefaultTranslation(uint32_t *othercolors)
 			}
 		}
 	}
-	Ranges[CR_UNTRANSLATED] = remap;
+	Ranges[CR_UNTRANSLATED] = GLInterface.GetPaletteIndex(remap.Palette);
 	forceremap = true;
 }
 
@@ -355,7 +356,7 @@ void FFont::BuildTranslations (const double *luminosity, const uint8_t *identity
 	int i, j;
 	const TranslationParm *parmstart = (const TranslationParm *)ranges;
 
-	FRemapTable remap(total_colors);
+	FRemapTable remap;
 
 	// Create different translations for different color ranges
 	Ranges.Clear();
@@ -380,15 +381,14 @@ void FFont::BuildTranslations (const double *luminosity, const uint8_t *identity
 			}
 			else
 			{
-				remap = Ranges[0];
 			}
-			Ranges.Push(remap);
+			Ranges.Push(GLInterface.GetPaletteIndex(remap.Palette));
 			continue;
 		}
 
 		assert(parmstart->RangeStart >= 0);
 
-		remap.Palette[255] = 0;
+		remap.Palette[0] = 0;
 
 		for (j = 0; j < ActiveColors; j++)
 		{
@@ -414,7 +414,8 @@ void FFont::BuildTranslations (const double *luminosity, const uint8_t *identity
 			b = clamp(b, 0, 255);
 			remap.Palette[j] = PalEntry(255,r,g,b);
 		}
-		Ranges.Push(remap);
+
+		Ranges.Push(GLInterface.GetPaletteIndex(remap.Palette));
 
 		// Advance to the next color range.
 		while (parmstart[1].RangeStart > parmstart[0].RangeEnd)
@@ -431,7 +432,7 @@ void FFont::BuildTranslations (const double *luminosity, const uint8_t *identity
 //
 //==========================================================================
 
-FRemapTable *FFont::GetColorTranslation (EColorRange range, PalEntry *color) const
+int FFont::GetColorTranslation (EColorRange range, PalEntry *color) const
 {
 	if (noTranslate)
 	{
@@ -444,11 +445,11 @@ FRemapTable *FFont::GetColorTranslation (EColorRange range, PalEntry *color) con
 		if (color != nullptr) *color = retcolor;
 	}
 	if (ActiveColors == 0)
-		return nullptr;
+		return -1;
 	else if (range >= NumTextColors)
 		range = CR_UNTRANSLATED;
 	//if (range == CR_UNTRANSLATED && !translateUntranslated) return nullptr;
-	return &Ranges[range];
+	return Ranges[range];
 }
 
 //==========================================================================
diff --git a/source/common/fonts/hexfont.cpp b/source/common/fonts/hexfont.cpp
index 74ac0d433..82bac7354 100644
--- a/source/common/fonts/hexfont.cpp
+++ b/source/common/fonts/hexfont.cpp
@@ -376,8 +376,8 @@ public:
 
 		SimpleTranslation(colors, othertranslation, otherreverse, otherluminosity);
 
-		FRemapTable remap(ActiveColors);
-		remap.Palette[255] = 0;
+		FRemapTable remap;
+		remap.Palette[0] = 0;
 
 		for (unsigned l = 1; l < 18; l++)
 		{
diff --git a/source/common/fonts/v_font.h b/source/common/fonts/v_font.h
index 055c61c2d..8c805d57f 100644
--- a/source/common/fonts/v_font.h
+++ b/source/common/fonts/v_font.h
@@ -99,7 +99,7 @@ public:
 
 	virtual FTexture *GetChar (int code, int translation, int *const width, bool *redirected = nullptr) const;
 	virtual int GetCharWidth (int code) const;
-	FRemapTable *GetColorTranslation (EColorRange range, PalEntry *color = nullptr) const;
+	int GetColorTranslation (EColorRange range, PalEntry *color = nullptr) const;
 	int GetSpaceWidth () const { return SpaceWidth; }
 	int GetHeight () const { return FontHeight; }
 	int GetDefaultKerning () const { return GlobalKerning; }
@@ -164,7 +164,7 @@ protected:
 	};
 	TArray<CharData> Chars;
 	int ActiveColors;
-	TArray<FRemapTable> Ranges;
+	TArray<int> Ranges;
 	uint8_t PatchRemap[256];
 
 	FName FontName = NAME_None;
@@ -192,17 +192,7 @@ char* CleanseString(char* str);
 
 struct FRemapTable
 {
-	FRemapTable(int count = 256) {}
-	~FRemapTable() { delete Palette; }
-
-	PalEntry *Palette = nullptr;			// The ideal palette this maps to
-	int PalIndex;
-	int NumEntries;				// # of elements in this table (usually 256)
-	bool Inactive;				// This table is inactive and should be treated as if it was passed as NULL
-
-private:
-	void Free();
-	void Alloc(int count);
+	PalEntry Palette[256] = { };			// The ideal palette this maps to
 };
 
 
diff --git a/source/glad/include/glad/glad.h b/source/glad/include/glad/glad.h
index 03272f78f..8f4cd9f9a 100644
--- a/source/glad/include/glad/glad.h
+++ b/source/glad/include/glad/glad.h
@@ -1,3 +1,4 @@
+#pragma once
 /*
 
     OpenGL loader generated by glad 0.1.33 on Mon Sep 16 17:51:07 2019.
diff --git a/source/glbackend/buffers.h b/source/glbackend/buffers.h
new file mode 100644
index 000000000..51110c72c
--- /dev/null
+++ b/source/glbackend/buffers.h
@@ -0,0 +1,83 @@
+#pragma once
+
+#include <stddef.h>
+#include <assert.h>
+
+class FRenderState;
+
+// The low level code needs to know which attributes exist.
+// OpenGL needs to change the state of all of them per buffer binding.
+// VAOs are mostly useless for this because they lump buffer and binding state together which the model code does not want.
+enum
+{
+	VATTR_VERTEX,
+	VATTR_TEXCOORD,
+	VATTR_COLOR,
+	VATTR_VERTEX2,
+	VATTR_NORMAL,
+	VATTR_NORMAL2,
+	
+	VATTR_MAX
+};
+
+enum EVertexAttributeFormat
+{
+	VFmt_Float4,
+	VFmt_Float3,
+	VFmt_Float2,
+	VFmt_Float,
+	VFmt_Byte4,
+	VFmt_Packed_A2R10G10B10,
+};
+
+struct FVertexBufferAttribute
+{
+	int binding;
+	int location;
+	int format;
+	int offset;
+};
+
+class IBuffer
+{
+protected:
+	size_t buffersize = 0;
+	void *map = nullptr;
+public:
+	IBuffer() = default;
+	IBuffer(const IBuffer &) = delete;
+	IBuffer &operator=(const IBuffer &) = delete;
+	virtual ~IBuffer() = default;
+
+	virtual void SetData(size_t size, const void *data, bool staticdata = true) = 0;
+	virtual void SetSubData(size_t offset, size_t size, const void *data) = 0;
+	virtual void *Lock(unsigned int size) = 0;
+	virtual void Unlock() = 0;
+	virtual void Resize(size_t newsize) = 0;
+	virtual void Map() {}		// Only needed by old OpenGL but this needs to be in the interface.
+	virtual void Unmap() {}
+	void *Memory() { assert(map); return map; }
+	size_t Size() { return buffersize; }
+};
+
+class IVertexBuffer : virtual public IBuffer
+{
+public:
+	virtual void SetFormat(int numBindingPoints, int numAttributes, size_t stride, const FVertexBufferAttribute *attrs) = 0;
+};
+
+// This merely exists to have a dedicated type for index buffers to inherit from.
+class IIndexBuffer : virtual public IBuffer
+{
+	// Element size is fixed to 4, thanks to OpenGL requiring this info to be coded into the glDrawElements call.
+	// This mostly prohibits a more flexible buffer setup but GZDoom doesn't use any other format anyway.
+	// Ob Vulkam, element size is a buffer property and of no concern to the drawing functions (as it should be.)
+};
+
+class IDataBuffer : virtual public IBuffer
+{
+	// Can be either uniform or shader storage buffer, depending on its needs.
+public:
+	virtual void BindRange(FRenderState *state, size_t start, size_t length) = 0;
+
+};
diff --git a/source/glbackend/gl_buffers.cpp b/source/glbackend/gl_buffers.cpp
new file mode 100644
index 000000000..d33678451
--- /dev/null
+++ b/source/glbackend/gl_buffers.cpp
@@ -0,0 +1,218 @@
+// 
+//---------------------------------------------------------------------------
+//
+// Copyright(C) 2018 Christoph Oelckers
+// All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see http://www.gnu.org/licenses/
+//
+//--------------------------------------------------------------------------
+//
+/*
+** Low level vertex buffer class
+**
+**/
+
+#include <algorithm>
+#include "glad/glad.h"
+#include "glbackend.h"
+#include "gl_buffers.h"
+
+namespace OpenGLRenderer
+{
+
+//==========================================================================
+//
+// basic buffer implementation
+//
+//==========================================================================
+
+GLBuffer::GLBuffer(int usetype)
+	: mUseType(usetype)
+{
+	glGenBuffers(1, &mBufferId);
+}
+
+GLBuffer::~GLBuffer()
+{
+	if (mBufferId != 0)
+	{
+		glBindBuffer(mUseType, mBufferId);
+		glUnmapBuffer(mUseType);
+		glBindBuffer(mUseType, 0);
+		glDeleteBuffers(1, &mBufferId);
+	}
+}
+
+void GLBuffer::Bind()
+{
+	glBindBuffer(mUseType, mBufferId);
+}
+
+
+void GLBuffer::SetData(size_t size, const void *data, bool staticdata)
+{
+	Bind();
+	if (data != nullptr)
+	{
+		glBufferData(mUseType, size, data, staticdata? GL_STATIC_DRAW : GL_STREAM_DRAW);
+	}
+	else
+	{
+		mPersistent = /*screen->BuffersArePersistent() &&*/ !staticdata;
+		if (mPersistent)
+		{
+			glBufferStorage(mUseType, size, nullptr, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
+			map = glMapBufferRange(mUseType, 0, size, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
+		}
+		else
+		{
+			glBufferData(mUseType, size, nullptr, staticdata ? GL_STATIC_DRAW : GL_STREAM_DRAW);
+			map = nullptr;
+		}
+		if (!staticdata) nomap = false;
+	}
+	buffersize = size;
+}
+
+void GLBuffer::SetSubData(size_t offset, size_t size, const void *data)
+{
+	Bind();
+	glBufferSubData(mUseType, offset, size, data);
+}
+
+void GLBuffer::Map()
+{
+	assert(nomap == false);	// do not allow mapping of static buffers. Vulkan cannot do that so it should be blocked in OpenGL, too.
+	if (!mPersistent && !nomap)
+	{
+		Bind();
+		map = glMapBufferRange(mUseType, 0, buffersize, GL_MAP_WRITE_BIT|GL_MAP_UNSYNCHRONIZED_BIT);
+	}
+}
+
+void GLBuffer::Unmap()
+{
+	assert(nomap == false);
+	if (!mPersistent && map != nullptr)
+	{
+		Bind();
+		glUnmapBuffer(mUseType);
+		map = nullptr;
+	}
+}
+
+void *GLBuffer::Lock(unsigned int size)
+{
+	// This initializes this buffer as a static object with no data.
+	SetData(size, nullptr, true);
+	return glMapBufferRange(mUseType, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
+}
+
+void GLBuffer::Unlock()
+{
+	Bind();
+	glUnmapBuffer(mUseType);
+}
+
+void GLBuffer::Resize(size_t newsize)
+{
+	assert(!nomap);	// only mappable buffers can be resized. 
+	if (newsize > buffersize && !nomap)
+	{
+		// reallocate the buffer with twice the size
+		unsigned int oldbuffer = mBufferId;
+
+		// first unmap the old buffer
+		Bind();
+		glUnmapBuffer(mUseType);
+
+		glGenBuffers(1, &mBufferId);
+		SetData(newsize, nullptr, false);
+		glBindBuffer(GL_COPY_READ_BUFFER, oldbuffer);
+
+		// copy contents and delete the old buffer.
+		glCopyBufferSubData(GL_COPY_READ_BUFFER, mUseType, 0, 0, buffersize);
+		glBindBuffer(GL_COPY_READ_BUFFER, 0);
+		glDeleteBuffers(1, &oldbuffer);
+		buffersize = newsize;
+	}
+}
+
+
+//===========================================================================
+//
+// Vertex buffer implementation
+//
+//===========================================================================
+
+void GLVertexBuffer::SetFormat(int numBindingPoints, int numAttributes, size_t stride, const FVertexBufferAttribute *attrs)
+{
+	static int VFmtToGLFmt[] = { GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_UNSIGNED_BYTE, GL_INT_2_10_10_10_REV };
+	static uint8_t VFmtToSize[] = {4, 3, 2, 1, 4, 4};
+	
+	mStride = stride;
+	mNumBindingPoints = numBindingPoints;
+	
+	for(int i = 0; i < numAttributes; i++)
+	{
+		if (attrs[i].location >= 0 && attrs[i].location < VATTR_MAX)
+		{
+			auto & attrinf = mAttributeInfo[attrs[i].location];
+			attrinf.format = VFmtToGLFmt[attrs[i].format];
+			attrinf.size = VFmtToSize[attrs[i].format];
+			attrinf.offset = attrs[i].offset;
+			attrinf.bindingpoint = attrs[i].binding;
+		}
+	}
+}
+
+void GLVertexBuffer::Bind(int *offsets)
+{
+	int i = 0;
+
+	// This is what gets called from RenderState.Apply. It shouldn't be called anywhere else if the render state is in use
+	GLBuffer::Bind();
+	for(auto &attrinf : mAttributeInfo)
+	{
+		if (attrinf.size == 0)
+		{
+			glDisableVertexAttribArray(i);
+		}
+		else
+		{
+			glEnableVertexAttribArray(i);
+			size_t ofs = offsets == nullptr ? attrinf.offset : attrinf.offset + mStride * offsets[attrinf.bindingpoint];
+			glVertexAttribPointer(i, attrinf.size, attrinf.format, attrinf.format != GL_FLOAT, (GLsizei)mStride, (void*)(intptr_t)ofs);
+		}
+		i++;
+	}
+}
+
+void GLDataBuffer::BindRange(FRenderState *state, size_t start, size_t length)
+{
+	glBindBufferRange(mUseType, mBindingPoint, mBufferId, start, length);
+}
+
+void GLDataBuffer::BindBase()
+{
+	glBindBufferBase(mUseType, mBindingPoint, mBufferId);
+}
+
+
+GLVertexBuffer::GLVertexBuffer() : GLBuffer(GL_ARRAY_BUFFER) {}
+GLIndexBuffer::GLIndexBuffer() : GLBuffer(GL_ELEMENT_ARRAY_BUFFER) {}
+GLDataBuffer::GLDataBuffer(int bindingpoint, bool is_ssbo) : GLBuffer(is_ssbo ? GL_SHADER_STORAGE_BUFFER : GL_UNIFORM_BUFFER), mBindingPoint(bindingpoint) {}
+
+}
diff --git a/source/glbackend/gl_buffers.h b/source/glbackend/gl_buffers.h
new file mode 100644
index 000000000..950556ff9
--- /dev/null
+++ b/source/glbackend/gl_buffers.h
@@ -0,0 +1,73 @@
+#pragma once
+
+#include "buffers.h"
+
+#ifdef _MSC_VER
+// silence bogus warning C4250: 'GLVertexBuffer': inherits 'GLBuffer::GLBuffer::SetData' via dominance
+// According to internet infos, the warning is erroneously emitted in this case.
+#pragma warning(disable:4250) 
+#endif
+
+namespace OpenGLRenderer
+{
+
+class GLBuffer : virtual public IBuffer
+{
+protected:
+	const int mUseType;
+	unsigned int mBufferId;
+	int mAllocationSize = 0;
+	bool mPersistent = false;
+	bool nomap = true;
+
+	GLBuffer(int usetype);
+	~GLBuffer();
+	void SetData(size_t size, const void *data, bool staticdata) override;
+	void SetSubData(size_t offset, size_t size, const void *data) override;
+	void Map() override;
+	void Unmap() override;
+	void Resize(size_t newsize) override;
+	void *Lock(unsigned int size) override;
+	void Unlock() override;
+public:
+	void Bind();
+};
+
+
+class GLVertexBuffer : public IVertexBuffer, public GLBuffer
+{
+	// If this could use the modern (since GL 4.3) binding system, things would be simpler... :(
+	struct GLVertexBufferAttribute
+	{
+		int bindingpoint;
+		int format;
+		int size;
+		int offset;
+	};
+
+	int mNumBindingPoints;
+	GLVertexBufferAttribute mAttributeInfo[VATTR_MAX] = {};	// Thanks to OpenGL's state system this needs to contain info about every attribute that may ever be in use throughout the entire renderer.
+	size_t mStride = 0;
+
+public:
+	GLVertexBuffer();
+	void SetFormat(int numBindingPoints, int numAttributes, size_t stride, const FVertexBufferAttribute *attrs) override;
+	void Bind(int *offsets);
+};
+
+class GLIndexBuffer : public IIndexBuffer, public GLBuffer
+{
+public:
+	GLIndexBuffer();
+};
+
+class GLDataBuffer : public IDataBuffer, public GLBuffer
+{
+	int mBindingPoint;
+public:
+	GLDataBuffer(int bindingpoint, bool is_ssbo);
+	void BindRange(FRenderState* state, size_t start, size_t length);
+	void BindBase();
+};
+
+}
\ No newline at end of file
diff --git a/source/glbackend/gl_palmanager.cpp b/source/glbackend/gl_palmanager.cpp
index 4a02ee4e2..3e27855c6 100644
--- a/source/glbackend/gl_palmanager.cpp
+++ b/source/glbackend/gl_palmanager.cpp
@@ -40,6 +40,7 @@
 #include "baselayer.h"
 #include "resourcefile.h"
 #include "imagehelpers.h"
+#include "v_font.h"
 
 //===========================================================================
 //
diff --git a/source/glbackend/gl_renderstate.h b/source/glbackend/gl_renderstate.h
index b860e27a7..20ed3afba 100644
--- a/source/glbackend/gl_renderstate.h
+++ b/source/glbackend/gl_renderstate.h
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "PalEntry.h"
+#include "gl_buffers.h"
 class PolymostShader;
 
 enum PRSFlags
@@ -34,6 +35,10 @@ struct PolymostRenderState
     float NPOTEmulationXOffset;
     float Brightness = 1.f;
 	PalEntry FogColor;
+
+	IVertexBuffer* VertexBuffer = nullptr;
+	int VB_Offset[2] = {};
+	IIndexBuffer* IndexBuffer = nullptr;
  	
 	void Apply(PolymostShader *shader);
 };
diff --git a/source/glbackend/gl_texture.cpp b/source/glbackend/gl_texture.cpp
index 24125f576..28bee5704 100644
--- a/source/glbackend/gl_texture.cpp
+++ b/source/glbackend/gl_texture.cpp
@@ -39,6 +39,7 @@
 #include "polymost.h"
 #include "textures.h"
 #include "bitmap.h"
+#include "v_font.h"
 #include "../../glbackend/glbackend.h"
 
 // Test CVARs.
@@ -155,10 +156,6 @@ FHardwareTexture* GLInstance::LoadTexture(FTexture* tex, int textype, int palid)
 
 bool GLInstance::SetTextureInternal(int picnum, FTexture* tex, int palette, int method, int sampleroverride, float xpanning, float ypanning, FTexture *det, float detscale, FTexture *glow)
 {
-	if (picnum == 3692)
-	{
-		int a = 0;
-	}
 	if (tex->GetWidth() <= 0 || tex->GetHeight() <= 0) return false;
 	int usepalette = fixpalette >= 1 ? fixpalette - 1 : curbasepal;
 	int usepalswap = fixpalswap >= 1 ? fixpalswap - 1 : palette;
@@ -180,7 +177,7 @@ bool GLInstance::SetTextureInternal(int picnum, FTexture* tex, int palette, int
 	else
 	{
 		// Only look up the palette if we really want to use it (i.e. when creating a true color texture of an ART tile.)
-		if (!hw_useindexedcolortextures) lookuppal = palmanager.LookupPalette(usepalette, usepalswap, false);
+		if (TextureType == TT_TRUECOLOR) lookuppal = palmanager.LookupPalette(usepalette, usepalswap, false);
 	}
 
 	// Load the main texture
@@ -291,3 +288,21 @@ bool GLInstance::SetTextureInternal(int picnum, FTexture* tex, int palette, int
 }
 
 
+//===========================================================================
+// 
+// Sets a named texture for 2D rendering. In this case the palette is
+// a direct index into the palette map.
+//
+//===========================================================================
+
+bool GLInstance::SetNamedTexture(FTexture* tex, int palette, int sampler)
+{
+	auto mtex = LoadTexture(tex, palette>= 0? TT_TRUECOLOR : TT_HICREPLACE, palette);
+	if (!mtex) return false;
+
+	renderState.Flags &= ~RF_UsePalette;
+	BindTexture(0, mtex, sampler);
+	return true;
+}
+
+
diff --git a/source/glbackend/glbackend.cpp b/source/glbackend/glbackend.cpp
index d19a569c3..4748224b3 100644
--- a/source/glbackend/glbackend.cpp
+++ b/source/glbackend/glbackend.cpp
@@ -208,15 +208,33 @@ void GLInstance::Draw(EDrawType type, size_t start, size_t count)
 	if (activeShader == polymostShader)
 	{
 		renderState.Apply(polymostShader);
+		if (renderState.VertexBuffer != LastVertexBuffer || LastVB_Offset[0] != renderState.VB_Offset[0] || LastVB_Offset[1] != renderState.VB_Offset[1])
+		{
+			static_cast<OpenGLRenderer::GLVertexBuffer*>(renderState.VertexBuffer)->Bind(renderState.VB_Offset);
+			LastVertexBuffer = renderState.VertexBuffer;
+			LastVB_Offset[0] = renderState.VB_Offset[0];
+			LastVB_Offset[1] = renderState.VB_Offset[1];
+		}
+		if (renderState.IndexBuffer != LastIndexBuffer)
+		{
+			static_cast<OpenGLRenderer::GLIndexBuffer*>(renderState.IndexBuffer)->Bind();
+		}
 	}
-	glBegin(primtypes[type]);
-	auto p = &Buffer[start];
-	for (size_t i = 0; i < count; i++, p++)
+	if (!LastVertexBuffer)
 	{
-		glVertexAttrib2f(1, p->u, p->v);
-		glVertexAttrib3f(0, p->x, p->y, p->z);
+		glBegin(primtypes[type]);
+		auto p = &Buffer[start];
+		for (size_t i = 0; i < count; i++, p++)
+		{
+			glVertexAttrib2f(1, p->u, p->v);
+			glVertexAttrib3f(0, p->x, p->y, p->z);
+		}
+		glEnd();
+	}
+	else
+	{
+		glDrawElements(primtypes[type], count, GL_UNSIGNED_INT, (void*)(intptr_t)(start * sizeof(uint32_t)));
 	}
-	glEnd();
 	if (MatrixChange) RestoreTextureProps();
 }
 
@@ -281,6 +299,12 @@ void GLInstance::EnableDepthTest(bool on)
 	else glDisable (GL_DEPTH_TEST);
 }
 
+void GLInstance::EnableMultisampling(bool on)
+{
+	if (on) glEnable(GL_MULTISAMPLE);
+	else glDisable(GL_MULTISAMPLE);
+}
+
 void GLInstance::SetMatrix(int num, const VSMatrix *mat)
 {
 	matrices[num] = *mat;
diff --git a/source/glbackend/glbackend.h b/source/glbackend/glbackend.h
index 8767adefb..10b0c4d31 100644
--- a/source/glbackend/glbackend.h
+++ b/source/glbackend/glbackend.h
@@ -8,6 +8,7 @@
 #include "gl_renderstate.h"
 #include "matrix.h"
 #include "palentry.h"
+#include "renderstyle.h"
 
 class FSamplerManager;
 class FShader;
@@ -64,7 +65,6 @@ class PaletteManager
 
 	//OpenGLRenderer::GLDataBuffer* palswapBuffer = nullptr;
 
-	unsigned FindPalette(const uint8_t* paldata);
 	unsigned FindPalswap(const uint8_t* paldata);
 
 public:
@@ -80,6 +80,8 @@ public:
 	int ActivePalswap() const { return lastsindex; }
 	int LookupPalette(int palette, int palswap, bool brightmap);
 	const PalEntry *GetPaletteData(int palid) const { return palettes[palid].colors; }
+	unsigned FindPalette(const uint8_t* paldata);
+
 };
 
 
@@ -157,27 +159,6 @@ enum EDepthFunc
 	Depth_LessEqual
 };
 
-enum ERenderAlpha
-{
-	STYLEALPHA_Zero,		// Blend factor is 0.0
-	STYLEALPHA_One,			// Blend factor is 1.0
-	STYLEALPHA_Src,			// Blend factor is alpha
-	STYLEALPHA_InvSrc,		// Blend factor is 1.0 - alpha
-	STYLEALPHA_SrcCol,		// Blend factor is color (HWR only)
-	STYLEALPHA_InvSrcCol,	// Blend factor is 1.0 - color (HWR only)
-	STYLEALPHA_DstCol,		// Blend factor is dest. color (HWR only)
-	STYLEALPHA_InvDstCol,	// Blend factor is 1.0 - dest. color (HWR only)
-	STYLEALPHA_Dst,			// Blend factor is dest. alpha
-	STYLEALPHA_InvDst,		// Blend factor is 1.0 - dest. alpha
-	STYLEALPHA_MAX
-};
-
-enum ERenderOp
-{
-	STYLEOP_Add,			// Add source to destination
-	STYLEOP_Sub,			// Subtract source from destination
-	STYLEOP_RevSub,			// Subtract destination from source
-};
 
 enum EWinding
 {
@@ -212,6 +193,10 @@ class GLInstance
 	int TextureType;
 	int MatrixChange = 0;
 
+	IVertexBuffer* LastVertexBuffer = nullptr;
+	int LastVB_Offset[2] = {};
+	IIndexBuffer* LastIndexBuffer = nullptr;
+
 
 	VSMatrix matrices[NUMMATRICES];
 	PolymostRenderState renderState;
@@ -251,6 +236,17 @@ public:
 	void EnableBlend(bool on);
 	void EnableAlphaTest(bool on);
 	void EnableDepthTest(bool on);
+	void EnableMultisampling(bool on);
+	void SetVertexBuffer(IVertexBuffer* vb, int offset1, int offset2)
+	{
+		renderState.VertexBuffer = vb;
+		renderState.VB_Offset[0] = offset1;
+		renderState.VB_Offset[1] = offset2;
+	}
+	void SetIndexBuffer(IIndexBuffer* vb)
+	{
+		renderState.IndexBuffer = vb;
+	}
 	const VSMatrix &GetMatrix(int num)
 	{
 		return matrices[num];
@@ -396,12 +392,18 @@ public:
 	{
 		// not yet implemented - only relevant for hires replacements.
 	}
+
+	int GetPaletteIndex(PalEntry* palette)
+	{
+		return palmanager.FindPalette((uint8_t*)palette);
+	}
 	
 	FHardwareTexture* CreateIndexedTexture(FTexture* tex);
 	FHardwareTexture* CreateTrueColorTexture(FTexture* tex, int palid, bool checkfulltransparency = false, bool rgb8bit = false);
 	FHardwareTexture *LoadTexture(FTexture* tex, int texturetype, int palid);
 	bool SetTextureInternal(int globalpicnum, FTexture* tex, int palette, int method, int sampleroverride, float xpanning, float ypanning, FTexture *det, float detscale, FTexture *glow);
 
+	bool SetNamedTexture(FTexture* tex, int palette, int sampleroverride);
 
 	bool SetTexture(int globalpicnum, FTexture* tex, int palette, int method, int sampleroverride)
 	{
diff --git a/source/glbackend/hw_draw2d.cpp b/source/glbackend/hw_draw2d.cpp
new file mode 100644
index 000000000..e00f4a404
--- /dev/null
+++ b/source/glbackend/hw_draw2d.cpp
@@ -0,0 +1,196 @@
+// 
+//---------------------------------------------------------------------------
+//
+// Copyright(C) 2018 Christoph Oelckers
+// All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program.  If not, see http://www.gnu.org/licenses/
+//
+//--------------------------------------------------------------------------
+//
+/*
+** 2d drawer
+** Renderer interface
+**
+*/
+
+#include "cmdlib.h"
+#include "gl_buffers.h"
+#include "v_2ddrawer.h"
+#include "c_cvars.h"
+#include "glbackend.h"
+#include "v_draw.h"
+
+//===========================================================================
+// 
+// Vertex buffer for 2D drawer
+//
+//===========================================================================
+
+class F2DVertexBuffer
+{
+	IVertexBuffer *mVertexBuffer;
+	IIndexBuffer *mIndexBuffer;
+
+
+public:
+
+	F2DVertexBuffer()
+	{
+		mVertexBuffer = new OpenGLRenderer::GLVertexBuffer();
+		mIndexBuffer = new OpenGLRenderer::GLIndexBuffer();
+
+		static const FVertexBufferAttribute format[] = {
+			{ 0, VATTR_VERTEX, VFmt_Float3, (int)myoffsetof(F2DDrawer::TwoDVertex, x) },
+			{ 0, VATTR_TEXCOORD, VFmt_Float2, (int)myoffsetof(F2DDrawer::TwoDVertex, u) },
+			{ 0, VATTR_COLOR, VFmt_Byte4, (int)myoffsetof(F2DDrawer::TwoDVertex, color0) }
+		};
+		mVertexBuffer->SetFormat(1, 3, sizeof(F2DDrawer::TwoDVertex), format);
+	}
+	~F2DVertexBuffer()
+	{
+		delete mIndexBuffer;
+		delete mVertexBuffer;
+	}
+
+	void UploadData(F2DDrawer::TwoDVertex *vertices, int vertcount, int *indices, int indexcount)
+	{
+		mVertexBuffer->SetData(vertcount * sizeof(*vertices), vertices, false);
+		mIndexBuffer->SetData(indexcount * sizeof(unsigned int), indices, false);
+	}
+
+	std::pair<IVertexBuffer *, IIndexBuffer *> GetBufferObjects() const
+	{
+		return std::make_pair(mVertexBuffer, mIndexBuffer);
+	}
+};
+
+//===========================================================================
+// 
+// Draws the 2D stuff. This is the version for OpenGL 3 and later.
+//
+//===========================================================================
+
+void Draw2D(F2DDrawer *drawer, FRenderState &state)
+{
+	VSMatrix mat(0);
+	GLInterface.SetMatrix(Matrix_View, mat.get());
+	GLInterface.SetMatrix(Matrix_ModelView, mat.get());
+	GLInterface.SetMatrix(Matrix_Detail, mat.get());
+	mat.ortho(0, xdim, ydim, 0, -1, 1);
+	GLInterface.SetMatrix(Matrix_Projection, mat.get());
+	GLInterface.SetViewport(0, 0, xdim, ydim);
+	GLInterface.EnableDepthTest(false);
+	GLInterface.EnableMultisampling(false);
+
+	auto &vertices = drawer->mVertices;
+	auto &indices = drawer->mIndices;
+	auto &commands = drawer->mData;
+
+	if (commands.Size() == 0)
+	{
+		return;
+	}
+
+	if (drawer->mIsFirstPass)
+	{
+		for (auto &v : vertices)
+		{
+			// Change from BGRA to RGBA
+			std::swap(v.color0.r, v.color0.b);
+		}
+	}
+	F2DVertexBuffer vb;
+	vb.UploadData(&vertices[0], vertices.Size(), &indices[0], indices.Size());
+	GLInterface.SetVertexBuffer(vb.GetBufferObjects().first, 0, 0);
+	GLInterface.SetIndexBuffer(vb.GetBufferObjects().second);
+	GLInterface.SetFadeDisable(true);
+
+	for(auto &cmd : commands)
+	{
+
+		int gltrans = -1;
+		//state.SetRenderStyle(cmd.mRenderStyle);
+		//state.EnableBrightmap(!(cmd.mRenderStyle.Flags & STYLEF_ColorIsFixed));
+		//state.SetTextureMode(cmd.mDrawMode);
+
+		int sciX, sciY, sciW, sciH;
+		if (cmd.mFlags & F2DDrawer::DTF_Scissor)
+		{
+			// scissor test doesn't use the current viewport for the coordinates, so use real screen coordinates
+			// Note that the origin here is the lower left corner!
+			sciX = /*screen->ScreenToWindowX*/(cmd.mScissor[0]);
+			sciY = /*screen->ScreenToWindowY*/(cmd.mScissor[3]);
+			sciW = /*screen->ScreenToWindowX*/(cmd.mScissor[2]) - sciX;
+			sciH = /*screen->ScreenToWindowY*/(cmd.mScissor[1]) - sciY;
+		}
+		else
+		{
+			sciX = sciY = sciW = sciH = -1;
+		}
+		//GLInterface.SetScissor(sciX, sciY, sciW, sciH);
+
+		//state.SetFog(cmd.mColor1, 0);
+		GLInterface.SetColor(1, 1, 1);
+		//state.SetColor(1, 1, 1, 1, cmd.mDesaturate); 
+
+		GLInterface.SetAlphaThreshold(0.0f);
+
+		if (cmd.mTexture != nullptr)
+		{
+			auto tex = cmd.mTexture;
+			GLInterface.SetNamedTexture(cmd.mTexture, cmd.mRemapIndex, cmd.mFlags & F2DDrawer::DTF_Wrap ? SamplerRepeat : SamplerClampXY);
+			GLInterface.UseColorOnly(false);
+		}
+		else
+		{
+			GLInterface.UseColorOnly(true);
+		}
+
+		switch (cmd.mType)
+		{
+		case F2DDrawer::DrawTypeTriangles:
+			GLInterface.Draw(DT_TRIANGLES, cmd.mIndexIndex, cmd.mIndexCount);
+			break;
+
+		case F2DDrawer::DrawTypeLines:
+			GLInterface.Draw(DT_LINES, cmd.mVertIndex, cmd.mVertCount);
+			break;
+
+		case F2DDrawer::DrawTypePoints:
+			//GLInterface.Draw(DT_POINTS, cmd.mVertIndex, cmd.mVertCount);
+			break;
+
+		}
+		/*
+		state.SetObjectColor(0xffffffff);
+		state.SetObjectColor2(0);
+		state.SetAddColor(0);
+		state.EnableTextureMatrix(false);
+		state.SetEffect(EFF_NONE);
+		*/
+
+	}
+	//state.SetScissor(-1, -1, -1, -1);
+
+	//state.SetRenderStyle(STYLE_Translucent);
+	GLInterface.SetVertexBuffer(nullptr, 0, 0);
+	GLInterface.SetIndexBuffer(nullptr);
+	GLInterface.UseColorOnly(false);
+	//state.EnableBrightmap(true);
+	//state.SetTextureMode(TM_NORMAL);
+	GLInterface.SetFadeDisable(false);
+	GLInterface.SetColor(1, 1, 1);
+	//drawer->mIsFirstPass = false;
+}