about summary refs log tree commit diff
path: root/src/engine/client
diff options
context:
space:
mode:
authorTeetime <TeetimeTW@yahoo.de>2012-02-17 19:31:43 +0100
committerTeetime <TeetimeTW@yahoo.de>2012-02-17 19:31:43 +0100
commitfc99167e687ba688ce9306bbec7e801450c917be (patch)
treeb440f2568a98b4b6a8db17925a299142adead7fa /src/engine/client
parent206e9adb9140c3c8963661f534703de33f8abe05 (diff)
parent44a47d4253a829abcf50dac5586fd9a351f0c66b (diff)
downloadzcatch-fc99167e687ba688ce9306bbec7e801450c917be.tar.gz
zcatch-fc99167e687ba688ce9306bbec7e801450c917be.zip
Merge branch 'master' into zCatch-Exp
Conflicts:
	src/engine/server.h
	src/engine/server/server.cpp
	src/engine/server/server.h
	src/engine/shared/network.h
	src/engine/shared/network_server.cpp
	src/game/server/player.h
Diffstat (limited to 'src/engine/client')
-rw-r--r--src/engine/client/backend_sdl.cpp515
-rw-r--r--src/engine/client/backend_sdl.h198
-rw-r--r--src/engine/client/client.cpp219
-rw-r--r--src/engine/client/client.h17
-rw-r--r--src/engine/client/graphics.cpp102
-rw-r--r--src/engine/client/graphics.h12
-rw-r--r--src/engine/client/graphics_threaded.cpp905
-rw-r--r--src/engine/client/graphics_threaded.h453
-rw-r--r--src/engine/client/serverbrowser.cpp37
-rw-r--r--src/engine/client/sound.cpp92
-rw-r--r--src/engine/client/text.cpp58
11 files changed, 2400 insertions, 208 deletions
diff --git a/src/engine/client/backend_sdl.cpp b/src/engine/client/backend_sdl.cpp
new file mode 100644
index 00000000..e7975aca
--- /dev/null
+++ b/src/engine/client/backend_sdl.cpp
@@ -0,0 +1,515 @@
+
+#include "SDL.h"
+#include "SDL_opengl.h"
+
+#include <base/tl/threading.h>
+
+#include "graphics_threaded.h"
+#include "backend_sdl.h"
+
+// ------------ CGraphicsBackend_Threaded
+
+void CGraphicsBackend_Threaded::ThreadFunc(void *pUser)
+{
+	CGraphicsBackend_Threaded *pThis = (CGraphicsBackend_Threaded *)pUser;
+
+	while(!pThis->m_Shutdown)
+	{
+		pThis->m_Activity.wait();
+		if(pThis->m_pBuffer)
+		{
+			pThis->m_pProcessor->RunBuffer(pThis->m_pBuffer);
+			sync_barrier();
+			pThis->m_pBuffer = 0x0;
+			pThis->m_BufferDone.signal();
+		}
+	}
+}
+
+CGraphicsBackend_Threaded::CGraphicsBackend_Threaded()
+{
+	m_pBuffer = 0x0;
+	m_pProcessor = 0x0;
+	m_pThread = 0x0;
+}
+
+void CGraphicsBackend_Threaded::StartProcessor(ICommandProcessor *pProcessor)
+{
+	m_Shutdown = false;
+	m_pProcessor = pProcessor;
+	m_pThread = thread_create(ThreadFunc, this);
+	m_BufferDone.signal();
+}
+
+void CGraphicsBackend_Threaded::StopProcessor()
+{
+	m_Shutdown = true;
+	m_Activity.signal();
+	thread_wait(m_pThread);
+	thread_destroy(m_pThread);
+}
+
+void CGraphicsBackend_Threaded::RunBuffer(CCommandBuffer *pBuffer)
+{
+	WaitForIdle();
+	m_pBuffer = pBuffer;
+	m_Activity.signal();
+}
+
+bool CGraphicsBackend_Threaded::IsIdle() const
+{
+	return m_pBuffer == 0x0;
+}
+
+void CGraphicsBackend_Threaded::WaitForIdle()
+{
+	while(m_pBuffer != 0x0)
+		m_BufferDone.wait();
+}
+
+
+// ------------ CCommandProcessorFragment_General
+
+void CCommandProcessorFragment_General::Cmd_Signal(const CCommandBuffer::SCommand_Signal *pCommand)
+{
+	pCommand->m_pSemaphore->signal();
+}
+
+bool CCommandProcessorFragment_General::RunCommand(const CCommandBuffer::SCommand * pBaseCommand)
+{
+	switch(pBaseCommand->m_Cmd)
+	{
+	case CCommandBuffer::CMD_NOP: break;
+	case CCommandBuffer::CMD_SIGNAL: Cmd_Signal(static_cast<const CCommandBuffer::SCommand_Signal *>(pBaseCommand)); break;
+	default: return false;
+	}
+
+	return true;
+}
+
+// ------------ CCommandProcessorFragment_OpenGL
+
+int CCommandProcessorFragment_OpenGL::TexFormatToOpenGLFormat(int TexFormat)
+{
+	if(TexFormat == CCommandBuffer::TEXFORMAT_RGB) return GL_RGB;
+	if(TexFormat == CCommandBuffer::TEXFORMAT_ALPHA) return GL_ALPHA;
+	if(TexFormat == CCommandBuffer::TEXFORMAT_RGBA) return GL_RGBA;
+	return GL_RGBA;
+}
+
+void CCommandProcessorFragment_OpenGL::SetState(const CCommandBuffer::SState &State)
+{
+	// blend
+	switch(State.m_BlendMode)
+	{
+	case CCommandBuffer::BLEND_NONE:
+		glDisable(GL_BLEND);
+		break;
+	case CCommandBuffer::BLEND_ALPHA:
+		glEnable(GL_BLEND);
+		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+		break;
+	case CCommandBuffer::BLEND_ADDITIVE:
+		glEnable(GL_BLEND);
+		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+		break;
+	default:
+		dbg_msg("render", "unknown blendmode %d\n", State.m_BlendMode);
+	};
+
+	// clip
+	if(State.m_ClipEnable)
+	{
+		glScissor(State.m_ClipX, State.m_ClipY, State.m_ClipW, State.m_ClipH);
+		glEnable(GL_SCISSOR_TEST);
+	}
+	else
+		glDisable(GL_SCISSOR_TEST);
+	
+	// texture
+	if(State.m_Texture >= 0 && State.m_Texture < CCommandBuffer::MAX_TEXTURES)
+	{
+		glEnable(GL_TEXTURE_2D);
+		glBindTexture(GL_TEXTURE_2D, m_aTextures[State.m_Texture]);
+	}
+	else
+		glDisable(GL_TEXTURE_2D);
+
+	switch(State.m_WrapMode)
+	{
+	case CCommandBuffer::WRAP_REPEAT:
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+		break;
+	case CCommandBuffer::WRAP_CLAMP:
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+		break;
+	default:
+		dbg_msg("render", "unknown wrapmode %d\n", State.m_WrapMode);
+	};
+
+	// screen mapping
+	glMatrixMode(GL_PROJECTION);
+	glLoadIdentity();
+	glOrtho(State.m_ScreenTL.x, State.m_ScreenBR.x, State.m_ScreenBR.y, State.m_ScreenTL.y, 1.0f, 10.f);
+}
+
+void CCommandProcessorFragment_OpenGL::Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand)
+{
+	glBindTexture(GL_TEXTURE_2D, m_aTextures[pCommand->m_Slot]);
+	glTexSubImage2D(GL_TEXTURE_2D, 0, pCommand->m_X, pCommand->m_Y, pCommand->m_Width, pCommand->m_Height,
+		TexFormatToOpenGLFormat(pCommand->m_Format), GL_UNSIGNED_BYTE, pCommand->m_pData);
+	mem_free(pCommand->m_pData);
+}
+
+void CCommandProcessorFragment_OpenGL::Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand)
+{
+	glDeleteTextures(1, &m_aTextures[pCommand->m_Slot]);
+}
+
+void CCommandProcessorFragment_OpenGL::Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand)
+{
+	int Oglformat = TexFormatToOpenGLFormat(pCommand->m_Format);
+	int StoreOglformat = TexFormatToOpenGLFormat(pCommand->m_StoreFormat);
+
+	glGenTextures(1, &m_aTextures[pCommand->m_Slot]);
+	glBindTexture(GL_TEXTURE_2D, m_aTextures[pCommand->m_Slot]);
+
+	if(pCommand->m_Flags&CCommandBuffer::TEXFLAG_NOMIPMAPS)
+	{
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, pCommand->m_Width, pCommand->m_Height, 0, Oglformat, GL_UNSIGNED_BYTE, pCommand->m_pData);
+	}
+	else
+	{
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+		gluBuild2DMipmaps(GL_TEXTURE_2D, StoreOglformat, pCommand->m_Width, pCommand->m_Height, Oglformat, GL_UNSIGNED_BYTE, pCommand->m_pData);
+	}
+
+	mem_free(pCommand->m_pData);
+}
+
+void CCommandProcessorFragment_OpenGL::Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand)
+{
+	glClearColor(pCommand->m_Color.r, pCommand->m_Color.g, pCommand->m_Color.b, 0.0f);
+	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+void CCommandProcessorFragment_OpenGL::Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand)
+{
+	SetState(pCommand->m_State);
+	
+	glVertexPointer(3, GL_FLOAT, sizeof(CCommandBuffer::SVertex), (char*)pCommand->m_pVertices);
+	glTexCoordPointer(2, GL_FLOAT, sizeof(CCommandBuffer::SVertex), (char*)pCommand->m_pVertices + sizeof(float)*3);
+	glColorPointer(4, GL_FLOAT, sizeof(CCommandBuffer::SVertex), (char*)pCommand->m_pVertices + sizeof(float)*5);
+	glEnableClientState(GL_VERTEX_ARRAY);
+	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+	glEnableClientState(GL_COLOR_ARRAY);
+
+	switch(pCommand->m_PrimType)
+	{
+	case CCommandBuffer::PRIMTYPE_QUADS:
+		glDrawArrays(GL_QUADS, 0, pCommand->m_PrimCount*4);
+		break;
+	case CCommandBuffer::PRIMTYPE_LINES:
+		glDrawArrays(GL_LINES, 0, pCommand->m_PrimCount*2);
+		break;
+	default:
+		dbg_msg("render", "unknown primtype %d\n", pCommand->m_Cmd);
+	};
+}
+
+void CCommandProcessorFragment_OpenGL::Cmd_Screenshot(const CCommandBuffer::SCommand_Screenshot *pCommand)
+{
+	// fetch image data
+	GLint aViewport[4] = {0,0,0,0};
+	glGetIntegerv(GL_VIEWPORT, aViewport);
+
+	int w = aViewport[2];
+	int h = aViewport[3];
+
+	// we allocate one more row to use when we are flipping the texture
+	unsigned char *pPixelData = (unsigned char *)mem_alloc(w*(h+1)*3, 1);
+	unsigned char *pTempRow = pPixelData+w*h*3;
+
+	// fetch the pixels
+	GLint Alignment;
+	glGetIntegerv(GL_PACK_ALIGNMENT, &Alignment);
+	glPixelStorei(GL_PACK_ALIGNMENT, 1);
+	glReadPixels(0,0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pPixelData);
+	glPixelStorei(GL_PACK_ALIGNMENT, Alignment);
+
+	// flip the pixel because opengl works from bottom left corner
+	for(int y = 0; y < h/2; y++)
+	{
+		mem_copy(pTempRow, pPixelData+y*w*3, w*3);
+		mem_copy(pPixelData+y*w*3, pPixelData+(h-y-1)*w*3, w*3);
+		mem_copy(pPixelData+(h-y-1)*w*3, pTempRow,w*3);
+	}
+
+	// fill in the information
+	pCommand->m_pImage->m_Width = w;
+	pCommand->m_pImage->m_Height = h;
+	pCommand->m_pImage->m_Format = CImageInfo::FORMAT_RGB;
+	pCommand->m_pImage->m_pData = pPixelData;
+}
+
+CCommandProcessorFragment_OpenGL::CCommandProcessorFragment_OpenGL()
+{
+	mem_zero(m_aTextures, sizeof(m_aTextures));
+}
+
+bool CCommandProcessorFragment_OpenGL::RunCommand(const CCommandBuffer::SCommand * pBaseCommand)
+{
+	switch(pBaseCommand->m_Cmd)
+	{
+	case CCommandBuffer::CMD_TEXTURE_CREATE: Cmd_Texture_Create(static_cast<const CCommandBuffer::SCommand_Texture_Create *>(pBaseCommand)); break;
+	case CCommandBuffer::CMD_TEXTURE_DESTROY: Cmd_Texture_Destroy(static_cast<const CCommandBuffer::SCommand_Texture_Destroy *>(pBaseCommand)); break;
+	case CCommandBuffer::CMD_TEXTURE_UPDATE: Cmd_Texture_Update(static_cast<const CCommandBuffer::SCommand_Texture_Update *>(pBaseCommand)); break;
+	case CCommandBuffer::CMD_CLEAR: Cmd_Clear(static_cast<const CCommandBuffer::SCommand_Clear *>(pBaseCommand)); break;
+	case CCommandBuffer::CMD_RENDER: Cmd_Render(static_cast<const CCommandBuffer::SCommand_Render *>(pBaseCommand)); break;
+	case CCommandBuffer::CMD_SCREENSHOT: Cmd_Screenshot(static_cast<const CCommandBuffer::SCommand_Screenshot *>(pBaseCommand)); break;
+	default: return false;
+	}
+
+	return true;
+}
+
+
+// ------------ CCommandProcessorFragment_SDL
+
+void CCommandProcessorFragment_SDL::Cmd_Init(const SCommand_Init *pCommand)
+{
+	m_GLContext = pCommand->m_Context;
+	GL_MakeCurrent(m_GLContext);
+
+	// set some default settings
+	glEnable(GL_BLEND);
+	glDisable(GL_CULL_FACE);
+	glDisable(GL_DEPTH_TEST);
+	glMatrixMode(GL_MODELVIEW);
+	glLoadIdentity();
+
+	glAlphaFunc(GL_GREATER, 0);
+	glEnable(GL_ALPHA_TEST);
+	glDepthMask(0);
+}
+
+void CCommandProcessorFragment_SDL::Cmd_Shutdown(const SCommand_Shutdown *pCommand)
+{
+	GL_ReleaseContext(m_GLContext);
+}
+
+void CCommandProcessorFragment_SDL::Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand)
+{
+	GL_SwapBuffers(m_GLContext);
+
+	if(pCommand->m_Finish)
+		glFinish();
+}
+
+void CCommandProcessorFragment_SDL::Cmd_VideoModes(const CCommandBuffer::SCommand_VideoModes *pCommand)
+{
+	// TODO: fix this code on osx or windows
+	SDL_Rect **ppModes = SDL_ListModes(NULL, SDL_OPENGL|SDL_GL_DOUBLEBUFFER|SDL_FULLSCREEN);
+	if(ppModes == NULL)
+	{
+		// no modes
+		*pCommand->m_pNumModes = 0;
+	}
+	else if(ppModes == (SDL_Rect**)-1)
+	{
+		// no modes
+		*pCommand->m_pNumModes = 0;
+	}
+	else
+	{
+		int NumModes = 0;
+		for(int i = 0; ppModes[i]; ++i)
+		{
+			if(NumModes == pCommand->m_MaxModes)
+				break;
+			pCommand->m_pModes[NumModes].m_Width = ppModes[i]->w;
+			pCommand->m_pModes[NumModes].m_Height = ppModes[i]->h;
+			pCommand->m_pModes[NumModes].m_Red = 8;
+			pCommand->m_pModes[NumModes].m_Green = 8;
+			pCommand->m_pModes[NumModes].m_Blue = 8;
+			NumModes++;
+		}
+
+		*pCommand->m_pNumModes = NumModes;
+	}
+}
+
+CCommandProcessorFragment_SDL::CCommandProcessorFragment_SDL()
+{
+}
+
+bool CCommandProcessorFragment_SDL::RunCommand(const CCommandBuffer::SCommand *pBaseCommand)
+{
+	switch(pBaseCommand->m_Cmd)
+	{
+	case CCommandBuffer::CMD_SWAP: Cmd_Swap(static_cast<const CCommandBuffer::SCommand_Swap *>(pBaseCommand)); break;
+	case CCommandBuffer::CMD_VIDEOMODES: Cmd_VideoModes(static_cast<const CCommandBuffer::SCommand_VideoModes *>(pBaseCommand)); break;
+	case CMD_INIT: Cmd_Init(static_cast<const SCommand_Init *>(pBaseCommand)); break;
+	case CMD_SHUTDOWN: Cmd_Shutdown(static_cast<const SCommand_Shutdown *>(pBaseCommand)); break;
+	default: return false;
+	}
+
+	return true;
+}
+
+// ------------ CCommandProcessor_SDL_OpenGL
+
+void CCommandProcessor_SDL_OpenGL::RunBuffer(CCommandBuffer *pBuffer)
+{
+	unsigned CmdIndex = 0;
+	while(1)
+	{
+		const CCommandBuffer::SCommand *pBaseCommand = pBuffer->GetCommand(&CmdIndex);
+		if(pBaseCommand == 0x0)
+			break;
+		
+		if(m_OpenGL.RunCommand(pBaseCommand))
+			continue;
+		
+		if(m_SDL.RunCommand(pBaseCommand))
+			continue;
+
+		if(m_General.RunCommand(pBaseCommand))
+			continue;
+		
+		dbg_msg("graphics", "unknown command %d", pBaseCommand->m_Cmd);
+	}
+}
+
+// ------------ CGraphicsBackend_SDL_OpenGL
+
+int CGraphicsBackend_SDL_OpenGL::Init(const char *pName, int Width, int Height, int FsaaSamples, int Flags)
+{
+	if(!SDL_WasInit(SDL_INIT_VIDEO))
+	{
+		if(SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
+		{
+			dbg_msg("gfx", "unable to init SDL video: %s", SDL_GetError());
+			return -1;
+		}
+
+		#ifdef CONF_FAMILY_WINDOWS
+			if(!getenv("SDL_VIDEO_WINDOW_POS") && !getenv("SDL_VIDEO_CENTERED")) // ignore_convention
+				putenv("SDL_VIDEO_WINDOW_POS=8,27"); // ignore_convention
+		#endif
+	}
+
+	const SDL_VideoInfo *pInfo = SDL_GetVideoInfo();
+	SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); // prevent stuck mouse cursor sdl-bug when loosing fullscreen focus in windows
+
+	// set flags
+	int SdlFlags = SDL_OPENGL;
+	if(Flags&IGraphicsBackend::INITFLAG_RESIZABLE)
+		SdlFlags |= SDL_RESIZABLE;
+
+	if(pInfo->hw_available) // ignore_convention
+		SdlFlags |= SDL_HWSURFACE;
+	else
+		SdlFlags |= SDL_SWSURFACE;
+
+	if(pInfo->blit_hw) // ignore_convention
+		SdlFlags |= SDL_HWACCEL;
+
+	if(Flags&IGraphicsBackend::INITFLAG_FULLSCREEN)
+		SdlFlags |= SDL_FULLSCREEN;
+
+	// set gl attributes
+	if(FsaaSamples)
+	{
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, FsaaSamples);
+	}
+	else
+	{
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
+	}
+
+	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+	SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, Flags&CCommandBuffer::INITFLAG_VSYNC ? 1 : 0);
+
+	// set caption
+	SDL_WM_SetCaption(pName, pName);
+
+	// create window
+	m_pScreenSurface = SDL_SetVideoMode(Width, Height, 0, SdlFlags);
+	if(!m_pScreenSurface)
+	{
+		dbg_msg("gfx", "unable to set video mode: %s", SDL_GetError());
+		//*pCommand->m_pResult = -1;
+		return -1;
+	}		
+
+	SDL_ShowCursor(0);
+
+	// fetch gl contexts and release the context from this thread
+	m_GLContext = GL_GetCurrentContext();
+	GL_ReleaseContext(m_GLContext);
+
+	// start the command processor
+	m_pProcessor = new CCommandProcessor_SDL_OpenGL;
+	StartProcessor(m_pProcessor);
+
+	// issue a init command
+	CCommandBuffer CmdBuffer(1024, 512);
+	CCommandProcessorFragment_SDL::SCommand_Init Cmd;
+	Cmd.m_Context = m_GLContext;
+	CmdBuffer.AddCommand(Cmd);
+	RunBuffer(&CmdBuffer);
+	WaitForIdle();
+
+	// return
+	return 0;
+}
+
+int CGraphicsBackend_SDL_OpenGL::Shutdown()
+{
+	// issue a shutdown command
+	CCommandBuffer CmdBuffer(1024, 512);
+	CCommandProcessorFragment_SDL::SCommand_Shutdown Cmd;
+	CmdBuffer.AddCommand(Cmd);
+	RunBuffer(&CmdBuffer);
+	WaitForIdle();
+			
+	// stop and delete the processor
+	StopProcessor();
+	delete m_pProcessor;
+	m_pProcessor = 0;
+
+	SDL_QuitSubSystem(SDL_INIT_VIDEO);
+	return 0;
+}
+
+void CGraphicsBackend_SDL_OpenGL::Minimize()
+{
+	SDL_WM_IconifyWindow();
+}
+
+void CGraphicsBackend_SDL_OpenGL::Maximize()
+{
+	// TODO: SDL
+}
+
+int CGraphicsBackend_SDL_OpenGL::WindowActive()
+{
+	return SDL_GetAppState()&SDL_APPINPUTFOCUS;
+}
+
+int CGraphicsBackend_SDL_OpenGL::WindowOpen()
+{
+	return SDL_GetAppState()&SDL_APPACTIVE;
+
+}
+
+
+IGraphicsBackend *CreateGraphicsBackend() { return new CGraphicsBackend_SDL_OpenGL; }
diff --git a/src/engine/client/backend_sdl.h b/src/engine/client/backend_sdl.h
new file mode 100644
index 00000000..c6c2255a
--- /dev/null
+++ b/src/engine/client/backend_sdl.h
@@ -0,0 +1,198 @@
+
+#include "SDL.h"
+#include "SDL_opengl.h"
+
+#include "graphics_threaded.h"
+
+
+
+// platform dependent implementations for transfering render context from the main thread to the graphics thread
+// TODO: when SDL 1.3 comes, this can be removed
+#if defined(CONF_FAMILY_WINDOWS)
+	struct SGLContext
+	{
+		HDC m_hDC;
+		HGLRC m_hGLRC;
+	};
+
+	static SGLContext GL_GetCurrentContext()
+	{
+		SGLContext Context;
+		Context.m_hDC = wglGetCurrentDC();
+		Context.m_hGLRC = wglGetCurrentContext();
+		return Context;
+	}
+
+	static void GL_MakeCurrent(const SGLContext &Context) { wglMakeCurrent(Context.m_hDC, Context.m_hGLRC); }
+	static void GL_ReleaseContext(const SGLContext &Context) { wglMakeCurrent(NULL, NULL); }
+	static void GL_SwapBuffers(const SGLContext &Context) { SwapBuffers(Context.m_hDC); }
+#elif defined(CONF_PLATFORM_MACOSX)
+
+	#include <AGL/agl.h>
+
+	struct SGLContext
+	{
+		AGLContext m_Context;
+	};
+
+	static SGLContext GL_GetCurrentContext()
+	{
+		SGLContext Context;
+		Context.m_Context = aglGetCurrentContext();
+		return Context;
+	}
+
+	static void GL_MakeCurrent(const SGLContext &Context) { aglSetCurrentContext(Context.m_Context); }
+	static void GL_ReleaseContext(const SGLContext &Context) { aglSetCurrentContext(NULL); }
+	static void GL_SwapBuffers(const SGLContext &Context) { aglSwapBuffers(Context.m_Context); }
+		
+#elif defined(CONF_FAMILY_UNIX)
+
+	#include <GL/glx.h>
+
+	struct SGLContext
+	{
+		Display *m_pDisplay;
+		GLXDrawable m_Drawable;
+		GLXContext m_Context;
+	};
+
+	static SGLContext GL_GetCurrentContext()
+	{
+		SGLContext Context;
+		Context.m_pDisplay = glXGetCurrentDisplay();
+		Context.m_Drawable = glXGetCurrentDrawable();
+		Context.m_Context = glXGetCurrentContext();
+		return Context;
+	}
+
+	static void GL_MakeCurrent(const SGLContext &Context) { glXMakeCurrent(Context.m_pDisplay, Context.m_Drawable, Context.m_Context); }
+	static void GL_ReleaseContext(const SGLContext &Context) { glXMakeCurrent(Context.m_pDisplay, None, 0x0); }
+	static void GL_SwapBuffers(const SGLContext &Context) { glXSwapBuffers(Context.m_pDisplay, Context.m_Drawable); }
+#else
+	#error missing implementation
+#endif
+
+
+// basic threaded backend, abstract, missing init and shutdown functions
+class CGraphicsBackend_Threaded : public IGraphicsBackend
+{
+public:
+	// constructed on the main thread, the rest of the functions is runned on the render thread
+	class ICommandProcessor
+	{
+	public:
+		virtual ~ICommandProcessor() {}
+		virtual void RunBuffer(CCommandBuffer *pBuffer) = 0;
+	};
+
+	CGraphicsBackend_Threaded();
+
+	virtual void RunBuffer(CCommandBuffer *pBuffer);
+	virtual bool IsIdle() const;
+	virtual void WaitForIdle();
+		
+protected:
+	void StartProcessor(ICommandProcessor *pProcessor);
+	void StopProcessor();
+
+private:
+	ICommandProcessor *m_pProcessor;
+	CCommandBuffer * volatile m_pBuffer;
+	volatile bool m_Shutdown;
+	semaphore m_Activity;
+	semaphore m_BufferDone;
+	void *m_pThread;
+
+	static void ThreadFunc(void *pUser);
+};
+
+// takes care of implementation independent operations
+class CCommandProcessorFragment_General
+{
+	void Cmd_Nop();
+	void Cmd_Signal(const CCommandBuffer::SCommand_Signal *pCommand);
+public:
+	bool RunCommand(const CCommandBuffer::SCommand * pBaseCommand);
+};
+
+// takes care of opengl related rendering
+class CCommandProcessorFragment_OpenGL
+{
+	GLuint m_aTextures[CCommandBuffer::MAX_TEXTURES];
+	static int TexFormatToOpenGLFormat(int TexFormat);
+
+	void SetState(const CCommandBuffer::SState &State);
+
+	void Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand);
+	void Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand);
+	void Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand);
+	void Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand);
+	void Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand);
+	void Cmd_Screenshot(const CCommandBuffer::SCommand_Screenshot *pCommand);
+
+public:
+	CCommandProcessorFragment_OpenGL();
+
+	bool RunCommand(const CCommandBuffer::SCommand * pBaseCommand);
+};
+
+// takes care of sdl related commands
+class CCommandProcessorFragment_SDL
+{
+	// SDL stuff
+	SGLContext m_GLContext;
+public:
+	enum
+	{
+		CMD_INIT = CCommandBuffer::CMDGROUP_PLATFORM,
+		CMD_SHUTDOWN,
+	};
+
+	struct SCommand_Init : public CCommandBuffer::SCommand
+	{
+		SCommand_Init() : SCommand(CMD_INIT) {}
+		SGLContext m_Context;
+	};
+
+	struct SCommand_Shutdown : public CCommandBuffer::SCommand
+	{
+		SCommand_Shutdown() : SCommand(CMD_SHUTDOWN) {}
+	};
+
+private:
+	void Cmd_Init(const SCommand_Init *pCommand);
+	void Cmd_Shutdown(const SCommand_Shutdown *pCommand);
+	void Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand);
+	void Cmd_VideoModes(const CCommandBuffer::SCommand_VideoModes *pCommand);
+public:
+	CCommandProcessorFragment_SDL();
+
+	bool RunCommand(const CCommandBuffer::SCommand *pBaseCommand);
+};
+
+// command processor impelementation, uses the fragments to combine into one processor
+class CCommandProcessor_SDL_OpenGL : public CGraphicsBackend_Threaded::ICommandProcessor
+{
+ 	CCommandProcessorFragment_OpenGL m_OpenGL;
+ 	CCommandProcessorFragment_SDL m_SDL;
+ 	CCommandProcessorFragment_General m_General;
+ public:
+	virtual void RunBuffer(CCommandBuffer *pBuffer);
+};
+
+// graphics backend implemented with SDL and OpenGL
+class CGraphicsBackend_SDL_OpenGL : public CGraphicsBackend_Threaded
+{
+	SDL_Surface *m_pScreenSurface;
+	ICommandProcessor *m_pProcessor;
+	SGLContext m_GLContext;
+public:
+	virtual int Init(const char *pName, int Width, int Height, int FsaaSamples, int Flags);
+	virtual int Shutdown();
+
+	virtual void Minimize();
+	virtual void Maximize();
+	virtual int WindowActive();
+	virtual int WindowOpen();
+};
diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index bec7d4d6..d5da647b 100644
--- a/src/engine/client/client.cpp
+++ b/src/engine/client/client.cpp
@@ -7,6 +7,7 @@
 
 #include <base/math.h>
 #include <base/system.h>
+#include <base/tl/threading.h>
 
 #include <engine/client.h>
 #include <engine/config.h>
@@ -35,6 +36,8 @@
 #include <engine/shared/ringbuffer.h>
 #include <engine/shared/snapshot.h>
 
+#include <game/version.h>
+
 #include <mastersrv/mastersrv.h>
 #include <versionsrv/versionsrv.h>
 
@@ -48,6 +51,10 @@
 	#include <windows.h>
 #endif
 
+#include "SDL.h"
+#ifdef main
+#undef main
+#endif
 
 void CGraph::Init(float Min, float Max)
 {
@@ -243,10 +250,11 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta), m_DemoRecorder(&m_SnapshotD
 	m_pMap = 0;
 	m_pConsole = 0;
 
-	m_FrameTime = 0.0001f;
-	m_FrameTimeLow = 1.0f;
-	m_FrameTimeHigh = 0.0f;
-	m_Frames = 0;
+	m_RenderFrameTime = 0.0001f;
+	m_RenderFrameTimeLow = 1.0f;
+	m_RenderFrameTimeHigh = 0.0f;
+	m_RenderFrames = 0;
+	m_LastRenderTime = time_get();
 
 	m_GameTickSpeed = SERVER_TICK_SPEED;
 
@@ -455,6 +463,9 @@ int *CClient::GetInput(int Tick)
 // ------ state handling -----
 void CClient::SetState(int s)
 {
+	if(m_State == IClient::STATE_QUITING)
+		return;
+
 	int Old = m_State;
 	if(g_Config.m_Debug)
 	{
@@ -546,6 +557,7 @@ void CClient::DisconnectWithReason(const char *pReason)
 
 	//
 	m_RconAuthed = 0;
+	m_UseTempRconCommands = 0;
 	m_pConsole->DeregisterTempAll();
 	m_NetClient.Disconnect(pReason);
 	SetState(IClient::STATE_OFFLINE);
@@ -680,13 +692,13 @@ void CClient::DebugRender()
 		udp = 8
 		total = 42
 	*/
-	FrameTimeAvg = FrameTimeAvg*0.9f + m_FrameTime*0.1f;
+	FrameTimeAvg = FrameTimeAvg*0.9f + m_RenderFrameTime*0.1f;
 	str_format(aBuffer, sizeof(aBuffer), "ticks: %8d %8d mem %dk %d gfxmem: %dk fps: %3d",
 		m_CurGameTick, m_PredTick,
 		mem_stats()->allocated/1024,
 		mem_stats()->total_allocations,
 		Graphics()->MemoryUsage()/1024,
-		(int)(1.0f/FrameTimeAvg));
+		(int)(1.0f/FrameTimeAvg + 0.5f));
 	Graphics()->QuadsText(2, 2, 16, 1,1,1,1, aBuffer);
 
 
@@ -849,23 +861,26 @@ void CClient::ProcessConnlessPacket(CNetChunk *pPacket)
 	if(m_VersionInfo.m_State == CVersionInfo::STATE_READY && net_addr_comp(&pPacket->m_Address, &m_VersionInfo.m_VersionServeraddr.m_Addr) == 0)
 	{
 		// version info
-		if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(VERSION_DATA)) &&
+		if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(GAME_RELEASE_VERSION)) &&
 			mem_comp(pPacket->m_pData, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0)
 
 		{
-			unsigned char *pVersionData = (unsigned char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION);
-			int VersionMatch = !mem_comp(pVersionData, VERSION_DATA, sizeof(VERSION_DATA));
+			char *pVersionData = (char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION);
+			int VersionMatch = !mem_comp(pVersionData, GAME_RELEASE_VERSION, sizeof(GAME_RELEASE_VERSION));
+
+			char aVersion[sizeof(GAME_RELEASE_VERSION)];
+			str_copy(aVersion, pVersionData, sizeof(aVersion));
 
 			char aBuf[256];
-			str_format(aBuf, sizeof(aBuf), "version does %s (%d.%d.%d)",
+			str_format(aBuf, sizeof(aBuf), "version does %s (%s)",
 				VersionMatch ? "match" : "NOT match",
-				pVersionData[1], pVersionData[2], pVersionData[3]);
+				aVersion);
 			m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/version", aBuf);
 
 			// assume version is out of date when version-data doesn't match
-			if (!VersionMatch)
+			if(!VersionMatch)
 			{
-				str_format(m_aVersionStr, sizeof(m_aVersionStr), "%d.%d.%d", pVersionData[1], pVersionData[2], pVersionData[3]);
+				str_copy(m_aVersionStr, aVersion, sizeof(m_aVersionStr));
 			}
 
 			// request the map version list now
@@ -964,7 +979,7 @@ void CClient::ProcessConnlessPacket(CNetChunk *pPacket)
 			Info.m_NumPlayers < 0 || Info.m_NumPlayers > Info.m_NumClients || Info.m_MaxPlayers < 0 || Info.m_MaxPlayers > Info.m_MaxClients)
 			return;
 
-		net_addr_str(&pPacket->m_Address, Info.m_aAddress, sizeof(Info.m_aAddress));
+		net_addr_str(&pPacket->m_Address, Info.m_aAddress, sizeof(Info.m_aAddress), true);
 
 		for(int i = 0; i < Info.m_NumClients; i++)
 		{
@@ -1153,9 +1168,12 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket)
 			int Result = Unpacker.GetInt();
 			if(Unpacker.Error() == 0)
 				m_RconAuthed = Result;
+			int Old = m_UseTempRconCommands;
 			m_UseTempRconCommands = Unpacker.GetInt();
 			if(Unpacker.Error() != 0)
 				m_UseTempRconCommands = 0;
+			if(Old != 0 && m_UseTempRconCommands == 0)
+				m_pConsole->DeregisterTempAll();
 		}
 		else if(Msg == NETMSG_RCON_LINE)
 		{
@@ -1675,7 +1693,7 @@ void CClient::InitInterfaces()
 	// fetch interfaces
 	m_pEngine = Kernel()->RequestInterface<IEngine>();
 	m_pEditor = Kernel()->RequestInterface<IEditor>();
-	m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
+	//m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
 	m_pSound = Kernel()->RequestInterface<IEngineSound>();
 	m_pGameClient = Kernel()->RequestInterface<IGameClient>();
 	m_pInput = Kernel()->RequestInterface<IEngineInput>();
@@ -1690,21 +1708,49 @@ void CClient::InitInterfaces()
 
 void CClient::Run()
 {
-	int64 ReportTime = time_get();
-	int64 ReportInterval = time_freq()*1;
-
 	m_LocalStartTime = time_get();
 	m_SnapshotParts = 0;
 
+	// init SDL
+	{
+		if(SDL_Init(0) < 0)
+		{
+			dbg_msg("client", "unable to init SDL base: %s", SDL_GetError());
+			return;
+		}
+
+		atexit(SDL_Quit); // ignore_convention
+	}
+
 	// init graphics
-	if(m_pGraphics->Init() != 0)
-		return;
+	{
+		if(g_Config.m_GfxThreaded)
+			m_pGraphics = CreateEngineGraphicsThreaded();
+		else
+			m_pGraphics = CreateEngineGraphics();
+
+		bool RegisterFail = false;
+		RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast<IEngineGraphics*>(m_pGraphics)); // register graphics as both
+		RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast<IGraphics*>(m_pGraphics));
+
+		if(RegisterFail || m_pGraphics->Init() != 0)
+		{
+			dbg_msg("client", "couldn't init graphics");
+			return;
+		}
+	}
+
+	// init sound, allowed to fail
+	m_SoundInitFailed = Sound()->Init() != 0;
 
 	// open socket
 	{
 		NETADDR BindAddr;
-		mem_zero(&BindAddr, sizeof(BindAddr));
-		BindAddr.type = NETTYPE_ALL;
+		if(g_Config.m_Bindaddr[0] == 0 || net_host_lookup(g_Config.m_Bindaddr, &BindAddr, NETTYPE_ALL) != 0)
+		{
+			mem_zero(&BindAddr, sizeof(BindAddr));
+			BindAddr.type = NETTYPE_ALL;
+		}
 		if(!m_NetClient.Open(BindAddr, 0))
 		{
 			dbg_msg("client", "couldn't start network");
@@ -1724,14 +1770,13 @@ void CClient::Run()
 	// init the editor
 	m_pEditor->Init();
 
-	// init sound, allowed to fail
-	m_SoundInitFailed = Sound()->Init() != 0;
 
 	// load data
 	if(!LoadData())
 		return;
 
 	GameClient()->OnInit();
+
 	char aBuf[256];
 	str_format(aBuf, sizeof(aBuf), "version %s", GameClient()->NetVersion());
 	m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
@@ -1756,9 +1801,6 @@ void CClient::Run()
 
 	while (1)
 	{
-		int64 FrameStartTime = time_get();
-		m_Frames++;
-
 		//
 		VersionUpdate();
 
@@ -1808,7 +1850,10 @@ void CClient::Run()
 
 		// panic quit button
 		if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyPressed('q'))
+		{
+			Quit();
 			break;
+		}
 
 		if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown('d'))
 			g_Config.m_Debug ^= 1;
@@ -1828,39 +1873,61 @@ void CClient::Run()
 		*/
 
 		// render
-		if(g_Config.m_ClEditor)
 		{
-			if(!m_EditorActive)
+			if(g_Config.m_ClEditor)
 			{
-				GameClient()->OnActivateEditor();
-				m_EditorActive = true;
+				if(!m_EditorActive)
+				{
+					GameClient()->OnActivateEditor();
+					m_EditorActive = true;
+				}
 			}
-
-			Update();
-			m_pEditor->UpdateAndRender();
-			DebugRender();
-			m_pGraphics->Swap();
-		}
-		else
-		{
-			if(m_EditorActive)
+			else if(m_EditorActive)
 				m_EditorActive = false;
 
 			Update();
-
-			if(g_Config.m_DbgStress)
+			
+			if(!g_Config.m_GfxAsyncRender || m_pGraphics->IsIdle())
 			{
-				if((m_Frames%10) == 0)
+				m_RenderFrames++;
+
+				// update frametime
+				int64 Now = time_get();
+				m_RenderFrameTime = (Now - m_LastRenderTime) / (float)time_freq();
+				if(m_RenderFrameTime < m_RenderFrameTimeLow)
+					m_RenderFrameTimeLow = m_RenderFrameTime;
+				if(m_RenderFrameTime > m_RenderFrameTimeHigh)
+					m_RenderFrameTimeHigh = m_RenderFrameTime;
+				m_FpsGraph.Add(1.0f/m_RenderFrameTime, 1,1,1);
+
+				m_LastRenderTime = Now;
+
+				if(g_Config.m_DbgStress)
 				{
-					Render();
+					if((m_RenderFrames%10) == 0)
+					{
+						if(!m_EditorActive)
+							Render();
+						else
+						{
+							m_pEditor->UpdateAndRender();
+							DebugRender();
+						}
+						m_pGraphics->Swap();
+					}
+				}
+				else
+				{
+					if(!m_EditorActive)
+						Render();
+					else
+					{
+						m_pEditor->UpdateAndRender();
+						DebugRender();
+					}
 					m_pGraphics->Swap();
 				}
 			}
-			else
-			{
-				Render();
-				m_pGraphics->Swap();
-			}
 		}
 
 		AutoScreenshot_Cleanup();
@@ -1881,32 +1948,25 @@ void CClient::Run()
 			g_Config.m_DbgHitch = 0;
 		}
 
+		/*
 		if(ReportTime < time_get())
 		{
 			if(0 && g_Config.m_Debug)
 			{
 				dbg_msg("client/report", "fps=%.02f (%.02f %.02f) netstate=%d",
 					m_Frames/(float)(ReportInterval/time_freq()),
-					1.0f/m_FrameTimeHigh,
-					1.0f/m_FrameTimeLow,
+					1.0f/m_RenderFrameTimeHigh,
+					1.0f/m_RenderFrameTimeLow,
 					m_NetClient.State());
 			}
-			m_FrameTimeLow = 1;
-			m_FrameTimeHigh = 0;
-			m_Frames = 0;
+			m_RenderFrameTimeLow = 1;
+			m_RenderFrameTimeHigh = 0;
+			m_RenderFrames = 0;
 			ReportTime += ReportInterval;
-		}
-
-		// update frametime
-		m_FrameTime = (time_get()-FrameStartTime)/(float)time_freq();
-		if(m_FrameTime < m_FrameTimeLow)
-			m_FrameTimeLow = m_FrameTime;
-		if(m_FrameTime > m_FrameTimeHigh)
-			m_FrameTimeHigh = m_FrameTime;
+		}*/
 
+		// update local time
 		m_LocalTime = (time_get()-m_LocalStartTime)/(float)time_freq();
-
-		m_FpsGraph.Add(1.0f/m_FrameTime, 1,1,1);
 	}
 
 	GameClient()->OnShutdown();
@@ -1914,6 +1974,11 @@ void CClient::Run()
 
 	m_pGraphics->Shutdown();
 	m_pSound->Shutdown();
+
+	// shutdown SDL
+	{
+		SDL_Quit();
+	}
 }
 
 
@@ -2104,6 +2169,11 @@ void CClient::DemoRecorder_Stop()
 	m_DemoRecorder.Stop();
 }
 
+void CClient::DemoRecorder_AddDemoMarker()
+{
+	m_DemoRecorder.AddDemoMarker();
+}
+
 void CClient::Con_Record(IConsole::IResult *pResult, void *pUserData)
 {
 	CClient *pSelf = (CClient *)pUserData;
@@ -2119,6 +2189,12 @@ void CClient::Con_StopRecord(IConsole::IResult *pResult, void *pUserData)
 	pSelf->DemoRecorder_Stop();
 }
 
+void CClient::Con_AddDemoMarker(IConsole::IResult *pResult, void *pUserData)
+{
+	CClient *pSelf = (CClient *)pUserData;
+	pSelf->DemoRecorder_AddDemoMarker();
+}
+
 void CClient::ServerBrowserUpdate()
 {
 	m_ResortServerBrowser = true;
@@ -2154,9 +2230,10 @@ void CClient::RegisterCommands()
 	m_pConsole->Register("screenshot", "", CFGFLAG_CLIENT, Con_Screenshot, this, "Take a screenshot");
 	m_pConsole->Register("rcon", "r", CFGFLAG_CLIENT, Con_Rcon, this, "Send specified command to rcon");
 	m_pConsole->Register("rcon_auth", "s", CFGFLAG_CLIENT, Con_RconAuth, this, "Authenticate to rcon");
-	m_pConsole->Register("play", "r", CFGFLAG_CLIENT, Con_Play, this, "Play the file specified");
+	m_pConsole->Register("play", "r", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Play, this, "Play the file specified");
 	m_pConsole->Register("record", "?s", CFGFLAG_CLIENT, Con_Record, this, "Record to the file");
 	m_pConsole->Register("stoprecord", "", CFGFLAG_CLIENT, Con_StopRecord, this, "Stop recording");
+	m_pConsole->Register("add_demomarker", "", CFGFLAG_CLIENT, Con_AddDemoMarker, this, "Add demo timeline marker");
 	m_pConsole->Register("add_favorite", "s", CFGFLAG_CLIENT, Con_AddFavorite, this, "Add a server as a favorite");
 	m_pConsole->Register("remove_favorite", "s", CFGFLAG_CLIENT, Con_RemoveFavorite, this, "Remove a server from favorites");
 
@@ -2186,11 +2263,13 @@ static CClient *CreateClient()
 */
 
 #if defined(CONF_PLATFORM_MACOSX)
-extern "C" int SDL_main(int argc, const char **argv) // ignore_convention
+extern "C" int SDL_main(int argc, char **argv_) // ignore_convention
+{
+	const char **argv = const_cast<const char **>(argv_);
 #else
 int main(int argc, const char **argv) // ignore_convention
-#endif
 {
+#endif
 #if defined(CONF_FAMILY_WINDOWS)
 	for(int i = 1; i < argc; i++) // ignore_convention
 	{
@@ -2210,9 +2289,8 @@ int main(int argc, const char **argv) // ignore_convention
 	// create the components
 	IEngine *pEngine = CreateEngine("Teeworlds");
 	IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT);
-	IStorage *pStorage = CreateStorage("Teeworlds", argc, argv); // ignore_convention
+	IStorage *pStorage = CreateStorage("Teeworlds", IStorage::STORAGETYPE_CLIENT, argc, argv); // ignore_convention
 	IConfig *pConfig = CreateConfig();
-	IEngineGraphics *pEngineGraphics = CreateEngineGraphics();
 	IEngineSound *pEngineSound = CreateEngineSound();
 	IEngineInput *pEngineInput = CreateEngineInput();
 	IEngineTextRender *pEngineTextRender = CreateEngineTextRender();
@@ -2226,9 +2304,6 @@ int main(int argc, const char **argv) // ignore_convention
 		RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole);
 		RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfig);
 
-		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineGraphics*>(pEngineGraphics)); // register graphics as both
-		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IGraphics*>(pEngineGraphics));
-
 		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineSound*>(pEngineSound)); // register as both
 		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<ISound*>(pEngineSound));
 
diff --git a/src/engine/client/client.h b/src/engine/client/client.h
index 1504a4e4..d958b49a 100644
--- a/src/engine/client/client.h
+++ b/src/engine/client/client.h
@@ -84,9 +84,12 @@ class CClient : public IClient, public CDemoPlayer::IListner
 	int64 m_LocalStartTime;
 
 	int m_DebugFont;
-	float m_FrameTimeLow;
-	float m_FrameTimeHigh;
-	int m_Frames;
+	
+	int64 m_LastRenderTime;
+	float m_RenderFrameTimeLow;
+	float m_RenderFrameTimeHigh;
+	int m_RenderFrames;
+
 	NETADDR m_ServerAddress;
 	int m_WindowMustRefocus;
 	int m_SnapCrcErrors;
@@ -172,6 +175,12 @@ class CClient : public IClient, public CDemoPlayer::IListner
 		class CHostLookup m_VersionServeraddr;
 	} m_VersionInfo;
 
+	semaphore m_GfxRenderSemaphore;
+	semaphore m_GfxStateSemaphore;
+	volatile int m_GfxState;
+	static void GraphicsThreadProxy(void *pThis) { ((CClient*)pThis)->GraphicsThread(); }
+	void GraphicsThread();
+
 public:
 	IEngine *Engine() { return m_pEngine; }
 	IEngineGraphics *Graphics() { return m_pGraphics; }
@@ -280,6 +289,7 @@ public:
 	static void Con_Play(IConsole::IResult *pResult, void *pUserData);
 	static void Con_Record(IConsole::IResult *pResult, void *pUserData);
 	static void Con_StopRecord(IConsole::IResult *pResult, void *pUserData);
+	static void Con_AddDemoMarker(IConsole::IResult *pResult, void *pUserData);
 	static void ConchainServerBrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
 
 	void RegisterCommands();
@@ -288,6 +298,7 @@ public:
 	void DemoRecorder_Start(const char *pFilename, bool WithTimestamp);
 	void DemoRecorder_HandleAutoStart();
 	void DemoRecorder_Stop();
+	void DemoRecorder_AddDemoMarker();
 
 	void AutoScreenshot_Start();
 	void AutoScreenshot_Cleanup();
diff --git a/src/engine/client/graphics.cpp b/src/engine/client/graphics.cpp
index d1f0b8a8..2111703e 100644
--- a/src/engine/client/graphics.cpp
+++ b/src/engine/client/graphics.cpp
@@ -3,21 +3,10 @@
 
 #include <base/detect.h>
 #include <base/math.h>
+#include <base/tl/threading.h>
 
 #include "SDL.h"
-
-#ifdef CONF_FAMILY_WINDOWS
-	#define WIN32_LEAN_AND_MEAN
-	#include <windows.h>
-#endif
-
-#ifdef CONF_PLATFORM_MACOSX
-	#include <OpenGL/gl.h>
-	#include <OpenGL/glu.h>
-#else
-	#include <GL/gl.h>
-	#include <GL/glu.h>
-#endif
+#include "SDL_opengl.h"
 
 #include <base/system.h>
 #include <engine/external/pnglite/pnglite.h>
@@ -32,13 +21,6 @@
 
 #include "graphics.h"
 
-// compressed textures
-#define GL_COMPRESSED_RGB_ARB 0x84ED
-#define GL_COMPRESSED_RGBA_ARB 0x84EE
-#define GL_COMPRESSED_ALPHA_ARB 0x84E9
-
-#define TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
-
 
 static CVideoMode g_aFakeModes[] = {
 	{320,240,8,8,8}, {400,300,8,8,8}, {640,480,8,8,8},
@@ -216,6 +198,18 @@ void CGraphics_OpenGL::BlendAdditive()
 	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
 }
 
+void CGraphics_OpenGL::WrapNormal()
+{
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+}
+
+void CGraphics_OpenGL::WrapClamp()
+{
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+}
+
 int CGraphics_OpenGL::MemoryUsage() const
 {
 	return m_TextureMemoryUsage;
@@ -289,6 +283,18 @@ int CGraphics_OpenGL::UnloadTexture(int Index)
 	return 0;
 }
 
+int CGraphics_OpenGL::LoadTextureRawSub(int TextureID, int x, int y, int Width, int Height, int Format, const void *pData)
+{
+	int Oglformat = GL_RGBA;
+	if(Format == CImageInfo::FORMAT_RGB)
+		Oglformat = GL_RGB;
+	else if(Format == CImageInfo::FORMAT_ALPHA)
+		Oglformat = GL_ALPHA;
+
+	glBindTexture(GL_TEXTURE_2D, m_aTextures[TextureID].m_Tex);
+	glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, Width, Height, Oglformat, GL_UNSIGNED_BYTE, pData);
+	return 0;
+}
 
 int CGraphics_OpenGL::LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags)
 {
@@ -355,9 +361,19 @@ int CGraphics_OpenGL::LoadTextureRaw(int Width, int Height, int Format, const vo
 
 	glGenTextures(1, &m_aTextures[Tex].m_Tex);
 	glBindTexture(GL_TEXTURE_2D, m_aTextures[Tex].m_Tex);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
-	gluBuild2DMipmaps(GL_TEXTURE_2D, StoreOglformat, Width, Height, Oglformat, GL_UNSIGNED_BYTE, pTexData);
+
+	if(Flags&TEXLOAD_NOMIPMAPS)
+	{
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, Width, Height, 0, Oglformat, GL_UNSIGNED_BYTE, pData);
+	}
+	else
+	{
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+		gluBuild2DMipmaps(GL_TEXTURE_2D, StoreOglformat, Width, Height, Oglformat, GL_UNSIGNED_BYTE, pTexData);
+	}
 
 	// calculate memory usage
 	{
@@ -400,6 +416,8 @@ int CGraphics_OpenGL::LoadTexture(const char *pFilename, int StorageType, int St
 
 		ID = LoadTextureRaw(Img.m_Width, Img.m_Height, Img.m_Format, Img.m_pData, StoreFormat, Flags);
 		mem_free(Img.m_pData);
+		if(ID != m_InvalidTexture && g_Config.m_Debug)
+			dbg_msg("graphics/texture", "loaded %s", pFilename);
 		return ID;
 	}
 
@@ -702,7 +720,7 @@ void CGraphics_OpenGL::QuadsText(float x, float y, float Size, float r, float g,
 	QuadsEnd();
 }
 
-bool CGraphics_OpenGL::Init()
+int CGraphics_OpenGL::Init()
 {
 	m_pStorage = Kernel()->RequestInterface<IStorage>();
 	m_pConsole = Kernel()->RequestInterface<IConsole>();
@@ -738,24 +756,19 @@ bool CGraphics_OpenGL::Init()
 
 	m_InvalidTexture = LoadTextureRaw(4,4,CImageInfo::FORMAT_RGBA,aNullTextureData,CImageInfo::FORMAT_RGBA,TEXLOAD_NORESAMPLE);
 
-	return true;
+	return 0;
 }
 
 int CGraphics_SDL::TryInit()
 {
-	const SDL_VideoInfo *pInfo;
-	int Flags = SDL_OPENGL;
-
 	m_ScreenWidth = g_Config.m_GfxScreenWidth;
 	m_ScreenHeight = g_Config.m_GfxScreenHeight;
 
-	pInfo = SDL_GetVideoInfo();
-	SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
+	const SDL_VideoInfo *pInfo = SDL_GetVideoInfo();
+	SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); // prevent stuck mouse cursor sdl-bug when loosing fullscreen focus in windows
 
 	// set flags
-	Flags = SDL_OPENGL;
-	Flags |= SDL_GL_DOUBLEBUFFER;
-	Flags |= SDL_HWPALETTE;
+	int Flags = SDL_OPENGL;
 	if(g_Config.m_DbgResizable)
 		Flags |= SDL_RESIZABLE;
 
@@ -841,7 +854,7 @@ CGraphics_SDL::CGraphics_SDL()
 	m_pScreenSurface = 0;
 }
 
-bool CGraphics_SDL::Init()
+int CGraphics_SDL::Init()
 {
 	{
 		int Systems = SDL_INIT_VIDEO;
@@ -855,7 +868,7 @@ bool CGraphics_SDL::Init()
 		if(SDL_Init(Systems) < 0)
 		{
 			dbg_msg("gfx", "unable to init SDL: %s", SDL_GetError());
-			return true;
+			return -1;
 		}
 	}
 
@@ -867,14 +880,14 @@ bool CGraphics_SDL::Init()
 	#endif
 
 	if(InitWindow() != 0)
-		return true;
+		return -1;
 
 	SDL_ShowCursor(0);
 
 	CGraphics_OpenGL::Init();
 
 	MapScreen(0,0,g_Config.m_GfxScreenWidth, g_Config.m_GfxScreenHeight);
-	return false;
+	return 0;
 }
 
 void CGraphics_SDL::Shutdown()
@@ -972,4 +985,19 @@ int CGraphics_SDL::GetVideoModes(CVideoMode *pModes, int MaxModes)
 	return NumModes;
 }
 
+// syncronization
+void CGraphics_SDL::InsertSignal(semaphore *pSemaphore)
+{
+	pSemaphore->signal();
+}
+
+bool CGraphics_SDL::IsIdle()
+{
+	return true;
+}
+
+void CGraphics_SDL::WaitForIdle()
+{
+}
+
 extern IEngineGraphics *CreateEngineGraphics() { return new CGraphics_SDL(); }
diff --git a/src/engine/client/graphics.h b/src/engine/client/graphics.h
index 95e9769a..fdd83aa7 100644
--- a/src/engine/client/graphics.h
+++ b/src/engine/client/graphics.h
@@ -78,6 +78,9 @@ public:
 	virtual void BlendNormal();
 	virtual void BlendAdditive();
 
+	virtual void WrapNormal();
+	virtual void WrapClamp();
+
 	virtual int MemoryUsage() const;
 
 	virtual void MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY);
@@ -89,6 +92,7 @@ public:
 
 	virtual int UnloadTexture(int Index);
 	virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags);
+	virtual int LoadTextureRawSub(int TextureID, int x, int y, int Width, int Height, int Format, const void *pData);
 
 	// simple uncompressed RGBA loaders
 	virtual int LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags);
@@ -117,7 +121,7 @@ public:
 	virtual void QuadsDrawFreeform(const CFreeformItem *pArray, int Num);
 	virtual void QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText);
 
-	virtual bool Init();
+	virtual int Init();
 };
 
 class CGraphics_SDL : public CGraphics_OpenGL
@@ -129,7 +133,7 @@ class CGraphics_SDL : public CGraphics_OpenGL
 public:
 	CGraphics_SDL();
 
-	virtual bool Init();
+	virtual int Init();
 	virtual void Shutdown();
 
 	virtual void Minimize();
@@ -143,6 +147,10 @@ public:
 
 	virtual int GetVideoModes(CVideoMode *pModes, int MaxModes);
 
+	// syncronization
+	virtual void InsertSignal(semaphore *pSemaphore);
+	virtual bool IsIdle();
+	virtual void WaitForIdle();
 };
 
 #endif
diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp
new file mode 100644
index 00000000..b19e8a83
--- /dev/null
+++ b/src/engine/client/graphics_threaded.cpp
@@ -0,0 +1,905 @@
+/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
+/* If you are missing that file, acquire a complete release at teeworlds.com.                */
+
+#include <base/detect.h>
+#include <base/math.h>
+#include <base/tl/threading.h>
+
+#include <base/system.h>
+#include <engine/external/pnglite/pnglite.h>
+
+#include <engine/shared/config.h>
+#include <engine/graphics.h>
+#include <engine/storage.h>
+#include <engine/keys.h>
+#include <engine/console.h>
+
+#include <math.h> // cosf, sinf
+
+#include "graphics_threaded.h"
+
+static CVideoMode g_aFakeModes[] = {
+	{320,240,8,8,8}, {400,300,8,8,8}, {640,480,8,8,8},
+	{720,400,8,8,8}, {768,576,8,8,8}, {800,600,8,8,8},
+	{1024,600,8,8,8}, {1024,768,8,8,8}, {1152,864,8,8,8},
+	{1280,768,8,8,8}, {1280,800,8,8,8}, {1280,960,8,8,8},
+	{1280,1024,8,8,8}, {1368,768,8,8,8}, {1400,1050,8,8,8},
+	{1440,900,8,8,8}, {1440,1050,8,8,8}, {1600,1000,8,8,8},
+	{1600,1200,8,8,8}, {1680,1050,8,8,8}, {1792,1344,8,8,8},
+	{1800,1440,8,8,8}, {1856,1392,8,8,8}, {1920,1080,8,8,8},
+	{1920,1200,8,8,8}, {1920,1440,8,8,8}, {1920,2400,8,8,8},
+	{2048,1536,8,8,8},
+
+	{320,240,5,6,5}, {400,300,5,6,5}, {640,480,5,6,5},
+	{720,400,5,6,5}, {768,576,5,6,5}, {800,600,5,6,5},
+	{1024,600,5,6,5}, {1024,768,5,6,5}, {1152,864,5,6,5},
+	{1280,768,5,6,5}, {1280,800,5,6,5}, {1280,960,5,6,5},
+	{1280,1024,5,6,5}, {1368,768,5,6,5}, {1400,1050,5,6,5},
+	{1440,900,5,6,5}, {1440,1050,5,6,5}, {1600,1000,5,6,5},
+	{1600,1200,5,6,5}, {1680,1050,5,6,5}, {1792,1344,5,6,5},
+	{1800,1440,5,6,5}, {1856,1392,5,6,5}, {1920,1080,5,6,5},
+	{1920,1200,5,6,5}, {1920,1440,5,6,5}, {1920,2400,5,6,5},
+	{2048,1536,5,6,5}
+};
+
+void CGraphics_Threaded::FlushVertices()
+{
+	if(m_NumVertices == 0)
+		return;
+
+	int NumVerts = m_NumVertices;
+	m_NumVertices = 0;
+
+	CCommandBuffer::SCommand_Render Cmd;
+	Cmd.m_State = m_State;
+
+	if(m_Drawing == DRAWING_QUADS)
+	{
+		Cmd.m_PrimType = CCommandBuffer::PRIMTYPE_QUADS;
+		Cmd.m_PrimCount = NumVerts/4;
+	}
+	else if(m_Drawing == DRAWING_LINES)
+	{
+		Cmd.m_PrimType = CCommandBuffer::PRIMTYPE_LINES;
+		Cmd.m_PrimCount = NumVerts/2;
+	}
+	else
+		return;
+
+	Cmd.m_pVertices = (CCommandBuffer::SVertex *)m_pCommandBuffer->AllocData(sizeof(CCommandBuffer::SVertex)*NumVerts);
+	if(Cmd.m_pVertices == 0x0)
+	{
+		// kick command buffer and try again
+		KickCommandBuffer();
+
+		Cmd.m_pVertices = (CCommandBuffer::SVertex *)m_pCommandBuffer->AllocData(sizeof(CCommandBuffer::SVertex)*NumVerts);
+		if(Cmd.m_pVertices == 0x0)
+		{
+			dbg_msg("graphics", "failed to allocate data for vertices");
+			return;
+		}
+	}
+
+	mem_copy(Cmd.m_pVertices, m_aVertices, sizeof(CCommandBuffer::SVertex)*NumVerts);
+	m_pCommandBuffer->AddCommand(Cmd);
+}
+
+void CGraphics_Threaded::AddVertices(int Count)
+{
+	m_NumVertices += Count;
+	if((m_NumVertices + Count) >= MAX_VERTICES)
+		FlushVertices();
+}
+
+void CGraphics_Threaded::Rotate4(const CCommandBuffer::SPoint &rCenter, CCommandBuffer::SVertex *pPoints)
+{
+	float c = cosf(m_Rotation);
+	float s = sinf(m_Rotation);
+	float x, y;
+	int i;
+
+	for(i = 0; i < 4; i++)
+	{
+		x = pPoints[i].m_Pos.x - rCenter.x;
+		y = pPoints[i].m_Pos.y - rCenter.y;
+		pPoints[i].m_Pos.x = x * c - y * s + rCenter.x;
+		pPoints[i].m_Pos.y = x * s + y * c + rCenter.y;
+	}
+}
+
+unsigned char CGraphics_Threaded::Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset, int ScaleW, int ScaleH, int Bpp)
+{
+	int Value = 0;
+	for(int x = 0; x < ScaleW; x++)
+		for(int y = 0; y < ScaleH; y++)
+			Value += pData[((v+y)*w+(u+x))*Bpp+Offset];
+	return Value/(ScaleW*ScaleH);
+}
+
+unsigned char *CGraphics_Threaded::Rescale(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData)
+{
+	unsigned char *pTmpData;
+	int ScaleW = Width/NewWidth;
+	int ScaleH = Height/NewHeight;
+
+	int Bpp = 3;
+	if(Format == CImageInfo::FORMAT_RGBA)
+		Bpp = 4;
+
+	pTmpData = (unsigned char *)mem_alloc(NewWidth*NewHeight*Bpp, 1);
+
+	int c = 0;
+	for(int y = 0; y < NewHeight; y++)
+		for(int x = 0; x < NewWidth; x++, c++)
+		{
+			pTmpData[c*Bpp] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 0, ScaleW, ScaleH, Bpp);
+			pTmpData[c*Bpp+1] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 1, ScaleW, ScaleH, Bpp);
+			pTmpData[c*Bpp+2] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 2, ScaleW, ScaleH, Bpp);
+			if(Bpp == 4)
+				pTmpData[c*Bpp+3] = Sample(Width, Height, pData, x*ScaleW, y*ScaleH, 3, ScaleW, ScaleH, Bpp);
+		}
+
+	return pTmpData;
+}
+
+CGraphics_Threaded::CGraphics_Threaded()
+{
+	m_State.m_ScreenTL.x = 0;
+	m_State.m_ScreenTL.y = 0;
+	m_State.m_ScreenBR.x = 0;
+	m_State.m_ScreenBR.y = 0;
+	m_State.m_ClipEnable = false;
+	m_State.m_ClipX = 0;
+	m_State.m_ClipY = 0;
+	m_State.m_ClipW = 0;
+	m_State.m_ClipH = 0;
+	m_State.m_Texture = -1;
+	m_State.m_BlendMode = CCommandBuffer::BLEND_NONE;
+	m_State.m_WrapMode = CCommandBuffer::WRAP_REPEAT;
+
+	m_CurrentCommandBuffer = 0;
+	m_pCommandBuffer = 0x0;
+	m_apCommandBuffers[0] = 0x0;
+	m_apCommandBuffers[1] = 0x0;
+
+	m_NumVertices = 0;
+
+	m_ScreenWidth = -1;
+	m_ScreenHeight = -1;
+
+	m_Rotation = 0;
+	m_Drawing = 0;
+	m_InvalidTexture = 0;
+
+	m_TextureMemoryUsage = 0;
+
+	m_RenderEnable = true;
+	m_DoScreenshot = false;
+}
+
+void CGraphics_Threaded::ClipEnable(int x, int y, int w, int h)
+{
+	if(x < 0)
+		w += x;
+	if(y < 0)
+		h += y;
+
+	x = clamp(x, 0, ScreenWidth());
+	y = clamp(y, 0, ScreenHeight());
+	w = clamp(w, 0, ScreenWidth()-x);
+	h = clamp(h, 0, ScreenHeight()-y);
+
+	m_State.m_ClipEnable = true;
+	m_State.m_ClipX = x;
+	m_State.m_ClipY = ScreenHeight()-(y+h);
+	m_State.m_ClipW = w;
+	m_State.m_ClipH = h;
+}
+
+void CGraphics_Threaded::ClipDisable()
+{
+	m_State.m_ClipEnable = false;
+}
+
+void CGraphics_Threaded::BlendNone()
+{
+	m_State.m_BlendMode = CCommandBuffer::BLEND_NONE;
+}
+
+void CGraphics_Threaded::BlendNormal()
+{
+	m_State.m_BlendMode = CCommandBuffer::BLEND_ALPHA;
+}
+
+void CGraphics_Threaded::BlendAdditive()
+{
+	m_State.m_BlendMode = CCommandBuffer::BLEND_ADDITIVE;
+}
+
+void CGraphics_Threaded::WrapNormal()
+{
+	m_State.m_WrapMode = CCommandBuffer::WRAP_REPEAT;
+}
+
+void CGraphics_Threaded::WrapClamp()
+{
+	m_State.m_WrapMode = CCommandBuffer::WRAP_CLAMP;
+}
+
+int CGraphics_Threaded::MemoryUsage() const
+{
+	return m_TextureMemoryUsage;
+}
+
+void CGraphics_Threaded::MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY)
+{
+	m_State.m_ScreenTL.x = TopLeftX;
+	m_State.m_ScreenTL.y = TopLeftY;
+	m_State.m_ScreenBR.x = BottomRightX;
+	m_State.m_ScreenBR.y = BottomRightY;
+}
+
+void CGraphics_Threaded::GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBottomRightX, float *pBottomRightY)
+{
+	*pTopLeftX = m_State.m_ScreenTL.x;
+	*pTopLeftY = m_State.m_ScreenTL.y;
+	*pBottomRightX = m_State.m_ScreenBR.x;
+	*pBottomRightY = m_State.m_ScreenBR.y;
+}
+
+void CGraphics_Threaded::LinesBegin()
+{
+	dbg_assert(m_Drawing == 0, "called Graphics()->LinesBegin twice");
+	m_Drawing = DRAWING_LINES;
+	SetColor(1,1,1,1);
+}
+
+void CGraphics_Threaded::LinesEnd()
+{
+	dbg_assert(m_Drawing == DRAWING_LINES, "called Graphics()->LinesEnd without begin");
+	FlushVertices();
+	m_Drawing = 0;
+}
+
+void CGraphics_Threaded::LinesDraw(const CLineItem *pArray, int Num)
+{
+	dbg_assert(m_Drawing == DRAWING_LINES, "called Graphics()->LinesDraw without begin");
+
+	for(int i = 0; i < Num; ++i)
+	{
+		m_aVertices[m_NumVertices + 2*i].m_Pos.x = pArray[i].m_X0;
+		m_aVertices[m_NumVertices + 2*i].m_Pos.y = pArray[i].m_Y0;
+		m_aVertices[m_NumVertices + 2*i].m_Tex = m_aTexture[0];
+		m_aVertices[m_NumVertices + 2*i].m_Color = m_aColor[0];
+
+		m_aVertices[m_NumVertices + 2*i + 1].m_Pos.x = pArray[i].m_X1;
+		m_aVertices[m_NumVertices + 2*i + 1].m_Pos.y = pArray[i].m_Y1;
+		m_aVertices[m_NumVertices + 2*i + 1].m_Tex = m_aTexture[1];
+		m_aVertices[m_NumVertices + 2*i + 1].m_Color = m_aColor[1];
+	}
+
+	AddVertices(2*Num);
+}
+
+int CGraphics_Threaded::UnloadTexture(int Index)
+{
+	if(Index == m_InvalidTexture)
+		return 0;
+
+	if(Index < 0)
+		return 0;
+
+	CCommandBuffer::SCommand_Texture_Destroy Cmd;
+	Cmd.m_Slot = Index;
+	m_pCommandBuffer->AddCommand(Cmd);
+
+	m_aTextures[Index].m_Next = m_FirstFreeTexture;
+	m_TextureMemoryUsage -= m_aTextures[Index].m_MemSize;
+	m_FirstFreeTexture = Index;
+	return 0;
+}
+
+static int ImageFormatToTexFormat(int Format)
+{
+	if(Format == CImageInfo::FORMAT_RGB) return CCommandBuffer::TEXFORMAT_RGB;
+	if(Format == CImageInfo::FORMAT_RGBA) return CCommandBuffer::TEXFORMAT_RGBA;
+	if(Format == CImageInfo::FORMAT_ALPHA) return CCommandBuffer::TEXFORMAT_ALPHA;
+	return CCommandBuffer::TEXFORMAT_RGBA;
+}
+
+
+int CGraphics_Threaded::LoadTextureRawSub(int TextureID, int x, int y, int Width, int Height, int Format, const void *pData)
+{
+	CCommandBuffer::SCommand_Texture_Update Cmd;
+	Cmd.m_Slot = TextureID;
+	Cmd.m_X = x;
+	Cmd.m_Y = y;
+	Cmd.m_Width = Width;
+	Cmd.m_Height = Height;
+	Cmd.m_Format = ImageFormatToTexFormat(Format);
+
+	// calculate memory usage
+	int PixelSize = 4;
+	if(Format == CImageInfo::FORMAT_RGB)
+		PixelSize = 3;
+	else if(Format == CImageInfo::FORMAT_ALPHA)
+		PixelSize = 1;
+
+	int MemSize = Width*Height*PixelSize;
+
+	// copy texture data
+	void *pTmpData = mem_alloc(MemSize, sizeof(void*));
+	mem_copy(pTmpData, pData, MemSize);
+	Cmd.m_pData = pTmpData;
+
+	//
+	m_pCommandBuffer->AddCommand(Cmd);
+	return 0;
+}
+
+int CGraphics_Threaded::LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags)
+{
+	// don't waste memory on texture if we are stress testing
+	if(g_Config.m_DbgStress)
+		return m_InvalidTexture;
+
+	// grab texture
+	int Tex = m_FirstFreeTexture;
+	m_FirstFreeTexture = m_aTextures[Tex].m_Next;
+	m_aTextures[Tex].m_Next = -1;
+
+	CCommandBuffer::SCommand_Texture_Create Cmd;
+	Cmd.m_Slot = Tex;
+	Cmd.m_Width = Width;
+	Cmd.m_Height = Height;
+	Cmd.m_Format = ImageFormatToTexFormat(Format);
+	Cmd.m_StoreFormat = ImageFormatToTexFormat(StoreFormat);
+
+	// flags
+	Cmd.m_Flags = 0;
+	if(Flags&IGraphics::TEXLOAD_NOMIPMAPS)
+		Cmd.m_Flags |= CCommandBuffer::TEXFLAG_NOMIPMAPS;
+
+	// calculate memory usage
+	int PixelSize = 4;
+	if(Format == CImageInfo::FORMAT_RGB)
+		PixelSize = 3;
+	else if(Format == CImageInfo::FORMAT_ALPHA)
+		PixelSize = 1;
+
+	int MemSize = Width*Height*PixelSize;
+
+	// copy texture data
+	void *pTmpData = mem_alloc(MemSize, sizeof(void*));
+	mem_copy(pTmpData, pData, MemSize);
+	Cmd.m_pData = pTmpData;
+
+	//
+	m_pCommandBuffer->AddCommand(Cmd);
+
+	// calculate memory usage
+	int MemUsage = MemSize;
+	while(Width > 2 && Height > 2)
+	{
+		Width>>=1;
+		Height>>=1;
+		MemUsage += Width*Height*PixelSize;
+	}
+
+	m_TextureMemoryUsage += MemUsage;
+	//mem_free(pTmpData);
+	return Tex;
+}
+
+// simple uncompressed RGBA loaders
+int CGraphics_Threaded::LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags)
+{
+	int l = str_length(pFilename);
+	int ID;
+	CImageInfo Img;
+
+	if(l < 3)
+		return -1;
+	if(LoadPNG(&Img, pFilename, StorageType))
+	{
+		if (StoreFormat == CImageInfo::FORMAT_AUTO)
+			StoreFormat = Img.m_Format;
+
+		ID = LoadTextureRaw(Img.m_Width, Img.m_Height, Img.m_Format, Img.m_pData, StoreFormat, Flags);
+		mem_free(Img.m_pData);
+		if(ID != m_InvalidTexture && g_Config.m_Debug)
+			dbg_msg("graphics/texture", "loaded %s", pFilename);
+		return ID;
+	}
+
+	return m_InvalidTexture;
+}
+
+int CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType)
+{
+	char aCompleteFilename[512];
+	unsigned char *pBuffer;
+	png_t Png; // ignore_convention
+
+	// open file for reading
+	png_init(0,0); // ignore_convention
+
+	IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType, aCompleteFilename, sizeof(aCompleteFilename));
+	if(File)
+		io_close(File);
+	else
+	{
+		dbg_msg("game/png", "failed to open file. filename='%s'", pFilename);
+		return 0;
+	}
+
+	int Error = png_open_file(&Png, aCompleteFilename); // ignore_convention
+	if(Error != PNG_NO_ERROR)
+	{
+		dbg_msg("game/png", "failed to open file. filename='%s'", aCompleteFilename);
+		if(Error != PNG_FILE_ERROR)
+			png_close_file(&Png); // ignore_convention
+		return 0;
+	}
+
+	if(Png.depth != 8 || (Png.color_type != PNG_TRUECOLOR && Png.color_type != PNG_TRUECOLOR_ALPHA)) // ignore_convention
+	{
+		dbg_msg("game/png", "invalid format. filename='%s'", aCompleteFilename);
+		png_close_file(&Png); // ignore_convention
+		return 0;
+	}
+
+	pBuffer = (unsigned char *)mem_alloc(Png.width * Png.height * Png.bpp, 1); // ignore_convention
+	png_get_data(&Png, pBuffer); // ignore_convention
+	png_close_file(&Png); // ignore_convention
+
+	pImg->m_Width = Png.width; // ignore_convention
+	pImg->m_Height = Png.height; // ignore_convention
+	if(Png.color_type == PNG_TRUECOLOR) // ignore_convention
+		pImg->m_Format = CImageInfo::FORMAT_RGB;
+	else if(Png.color_type == PNG_TRUECOLOR_ALPHA) // ignore_convention
+		pImg->m_Format = CImageInfo::FORMAT_RGBA;
+	pImg->m_pData = pBuffer;
+	return 1;
+}
+
+void CGraphics_Threaded::KickCommandBuffer()
+{
+	m_pBackend->RunBuffer(m_pCommandBuffer);
+
+	// swap buffer
+	m_CurrentCommandBuffer ^= 1;
+	m_pCommandBuffer = m_apCommandBuffers[m_CurrentCommandBuffer];
+	m_pCommandBuffer->Reset();
+}
+
+void CGraphics_Threaded::ScreenshotDirect(const char *pFilename)
+{
+	// add swap command
+	CImageInfo Image;
+	mem_zero(&Image, sizeof(Image));
+
+	CCommandBuffer::SCommand_Screenshot Cmd;
+	Cmd.m_pImage = &Image;
+	m_pCommandBuffer->AddCommand(Cmd);
+
+	// kick the buffer and wait for the result
+	KickCommandBuffer();
+	WaitForIdle();
+
+	if(Image.m_pData)
+	{
+		// find filename
+		char aWholePath[1024];
+		png_t Png; // ignore_convention
+
+		IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE, aWholePath, sizeof(aWholePath));
+		if(File)
+			io_close(File);
+
+		// save png
+		char aBuf[256];
+		str_format(aBuf, sizeof(aBuf), "saved screenshot to '%s'", aWholePath);
+		m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
+		png_open_file_write(&Png, aWholePath); // ignore_convention
+		png_set_data(&Png, Image.m_Width, Image.m_Height, 8, PNG_TRUECOLOR, (unsigned char *)Image.m_pData); // ignore_convention
+		png_close_file(&Png); // ignore_convention
+
+		mem_free(Image.m_pData);
+	}
+}
+
+void CGraphics_Threaded::TextureSet(int TextureID)
+{
+	dbg_assert(m_Drawing == 0, "called Graphics()->TextureSet within begin");
+	m_State.m_Texture = TextureID;
+}
+
+void CGraphics_Threaded::Clear(float r, float g, float b)
+{
+	CCommandBuffer::SCommand_Clear Cmd;
+	Cmd.m_Color.r = r;
+	Cmd.m_Color.g = g;
+	Cmd.m_Color.b = b;
+	Cmd.m_Color.a = 0;
+	m_pCommandBuffer->AddCommand(Cmd);
+}
+
+void CGraphics_Threaded::QuadsBegin()
+{
+	dbg_assert(m_Drawing == 0, "called Graphics()->QuadsBegin twice");
+	m_Drawing = DRAWING_QUADS;
+
+	QuadsSetSubset(0,0,1,1);
+	QuadsSetRotation(0);
+	SetColor(1,1,1,1);
+}
+
+void CGraphics_Threaded::QuadsEnd()
+{
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsEnd without begin");
+	FlushVertices();
+	m_Drawing = 0;
+}
+
+void CGraphics_Threaded::QuadsSetRotation(float Angle)
+{
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetRotation without begin");
+	m_Rotation = Angle;
+}
+
+void CGraphics_Threaded::SetColorVertex(const CColorVertex *pArray, int Num)
+{
+	dbg_assert(m_Drawing != 0, "called Graphics()->SetColorVertex without begin");
+
+	for(int i = 0; i < Num; ++i)
+	{
+		m_aColor[pArray[i].m_Index].r = pArray[i].m_R;
+		m_aColor[pArray[i].m_Index].g = pArray[i].m_G;
+		m_aColor[pArray[i].m_Index].b = pArray[i].m_B;
+		m_aColor[pArray[i].m_Index].a = pArray[i].m_A;
+	}
+}
+
+void CGraphics_Threaded::SetColor(float r, float g, float b, float a)
+{
+	dbg_assert(m_Drawing != 0, "called Graphics()->SetColor without begin");
+	CColorVertex Array[4] = {
+		CColorVertex(0, r, g, b, a),
+		CColorVertex(1, r, g, b, a),
+		CColorVertex(2, r, g, b, a),
+		CColorVertex(3, r, g, b, a)};
+	SetColorVertex(Array, 4);
+}
+
+void CGraphics_Threaded::QuadsSetSubset(float TlU, float TlV, float BrU, float BrV)
+{
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsSetSubset without begin");
+
+	m_aTexture[0].u = TlU;	m_aTexture[1].u = BrU;
+	m_aTexture[0].v = TlV;	m_aTexture[1].v = TlV;
+
+	m_aTexture[3].u = TlU;	m_aTexture[2].u = BrU;
+	m_aTexture[3].v = BrV;	m_aTexture[2].v = BrV;
+}
+
+void CGraphics_Threaded::QuadsSetSubsetFree(
+	float x0, float y0, float x1, float y1,
+	float x2, float y2, float x3, float y3)
+{
+	m_aTexture[0].u = x0; m_aTexture[0].v = y0;
+	m_aTexture[1].u = x1; m_aTexture[1].v = y1;
+	m_aTexture[2].u = x2; m_aTexture[2].v = y2;
+	m_aTexture[3].u = x3; m_aTexture[3].v = y3;
+}
+
+void CGraphics_Threaded::QuadsDraw(CQuadItem *pArray, int Num)
+{
+	for(int i = 0; i < Num; ++i)
+	{
+		pArray[i].m_X -= pArray[i].m_Width/2;
+		pArray[i].m_Y -= pArray[i].m_Height/2;
+	}
+
+	QuadsDrawTL(pArray, Num);
+}
+
+void CGraphics_Threaded::QuadsDrawTL(const CQuadItem *pArray, int Num)
+{
+	CCommandBuffer::SPoint Center;
+	Center.z = 0;
+
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsDrawTL without begin");
+
+	for(int i = 0; i < Num; ++i)
+	{
+		m_aVertices[m_NumVertices + 4*i].m_Pos.x = pArray[i].m_X;
+		m_aVertices[m_NumVertices + 4*i].m_Pos.y = pArray[i].m_Y;
+		m_aVertices[m_NumVertices + 4*i].m_Tex = m_aTexture[0];
+		m_aVertices[m_NumVertices + 4*i].m_Color = m_aColor[0];
+
+		m_aVertices[m_NumVertices + 4*i + 1].m_Pos.x = pArray[i].m_X + pArray[i].m_Width;
+		m_aVertices[m_NumVertices + 4*i + 1].m_Pos.y = pArray[i].m_Y;
+		m_aVertices[m_NumVertices + 4*i + 1].m_Tex = m_aTexture[1];
+		m_aVertices[m_NumVertices + 4*i + 1].m_Color = m_aColor[1];
+
+		m_aVertices[m_NumVertices + 4*i + 2].m_Pos.x = pArray[i].m_X + pArray[i].m_Width;
+		m_aVertices[m_NumVertices + 4*i + 2].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height;
+		m_aVertices[m_NumVertices + 4*i + 2].m_Tex = m_aTexture[2];
+		m_aVertices[m_NumVertices + 4*i + 2].m_Color = m_aColor[2];
+
+		m_aVertices[m_NumVertices + 4*i + 3].m_Pos.x = pArray[i].m_X;
+		m_aVertices[m_NumVertices + 4*i + 3].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height;
+		m_aVertices[m_NumVertices + 4*i + 3].m_Tex = m_aTexture[3];
+		m_aVertices[m_NumVertices + 4*i + 3].m_Color = m_aColor[3];
+
+		if(m_Rotation != 0)
+		{
+			Center.x = pArray[i].m_X + pArray[i].m_Width/2;
+			Center.y = pArray[i].m_Y + pArray[i].m_Height/2;
+
+			Rotate4(Center, &m_aVertices[m_NumVertices + 4*i]);
+		}
+	}
+
+	AddVertices(4*Num);
+}
+
+void CGraphics_Threaded::QuadsDrawFreeform(const CFreeformItem *pArray, int Num)
+{
+	dbg_assert(m_Drawing == DRAWING_QUADS, "called Graphics()->QuadsDrawFreeform without begin");
+
+	for(int i = 0; i < Num; ++i)
+	{
+		m_aVertices[m_NumVertices + 4*i].m_Pos.x = pArray[i].m_X0;
+		m_aVertices[m_NumVertices + 4*i].m_Pos.y = pArray[i].m_Y0;
+		m_aVertices[m_NumVertices + 4*i].m_Tex = m_aTexture[0];
+		m_aVertices[m_NumVertices + 4*i].m_Color = m_aColor[0];
+
+		m_aVertices[m_NumVertices + 4*i + 1].m_Pos.x = pArray[i].m_X1;
+		m_aVertices[m_NumVertices + 4*i + 1].m_Pos.y = pArray[i].m_Y1;
+		m_aVertices[m_NumVertices + 4*i + 1].m_Tex = m_aTexture[1];
+		m_aVertices[m_NumVertices + 4*i + 1].m_Color = m_aColor[1];
+
+		m_aVertices[m_NumVertices + 4*i + 2].m_Pos.x = pArray[i].m_X3;
+		m_aVertices[m_NumVertices + 4*i + 2].m_Pos.y = pArray[i].m_Y3;
+		m_aVertices[m_NumVertices + 4*i + 2].m_Tex = m_aTexture[3];
+		m_aVertices[m_NumVertices + 4*i + 2].m_Color = m_aColor[3];
+
+		m_aVertices[m_NumVertices + 4*i + 3].m_Pos.x = pArray[i].m_X2;
+		m_aVertices[m_NumVertices + 4*i + 3].m_Pos.y = pArray[i].m_Y2;
+		m_aVertices[m_NumVertices + 4*i + 3].m_Tex = m_aTexture[2];
+		m_aVertices[m_NumVertices + 4*i + 3].m_Color = m_aColor[2];
+	}
+
+	AddVertices(4*Num);
+}
+
+void CGraphics_Threaded::QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText)
+{
+	float StartX = x;
+
+	QuadsBegin();
+	SetColor(r,g,b,a);
+
+	while(*pText)
+	{
+		char c = *pText;
+		pText++;
+
+		if(c == '\n')
+		{
+			x = StartX;
+			y += Size;
+		}
+		else
+		{
+			QuadsSetSubset(
+				(c%16)/16.0f,
+				(c/16)/16.0f,
+				(c%16)/16.0f+1.0f/16.0f,
+				(c/16)/16.0f+1.0f/16.0f);
+
+			CQuadItem QuadItem(x, y, Size, Size);
+			QuadsDrawTL(&QuadItem, 1);
+			x += Size/2;
+		}
+	}
+
+	QuadsEnd();
+}
+
+int CGraphics_Threaded::IssueInit()
+{
+	int Flags = 0;
+	if(g_Config.m_GfxFullscreen) Flags |= IGraphicsBackend::INITFLAG_FULLSCREEN;
+	if(g_Config.m_GfxVsync) Flags |= IGraphicsBackend::INITFLAG_VSYNC;
+	if(g_Config.m_DbgResizable) Flags |= IGraphicsBackend::INITFLAG_RESIZABLE;
+
+	return m_pBackend->Init("Teeworlds", g_Config.m_GfxScreenWidth, g_Config.m_GfxScreenHeight, g_Config.m_GfxFsaaSamples, Flags);
+}
+
+int CGraphics_Threaded::InitWindow()
+{
+	if(IssueInit() == 0)
+		return 0;
+
+	// try disabling fsaa
+	while(g_Config.m_GfxFsaaSamples)
+	{
+		g_Config.m_GfxFsaaSamples--;
+
+		if(g_Config.m_GfxFsaaSamples)
+			dbg_msg("gfx", "lowering FSAA to %d and trying again", g_Config.m_GfxFsaaSamples);
+		else
+			dbg_msg("gfx", "disabling FSAA and trying again");
+
+		if(IssueInit() == 0)
+			return 0;
+	}
+
+	// try lowering the resolution
+	if(g_Config.m_GfxScreenWidth != 640 || g_Config.m_GfxScreenHeight != 480)
+	{
+		dbg_msg("gfx", "setting resolution to 640x480 and trying again");
+		g_Config.m_GfxScreenWidth = 640;
+		g_Config.m_GfxScreenHeight = 480;
+
+		if(IssueInit() == 0)
+			return 0;
+	}
+
+	dbg_msg("gfx", "out of ideas. failed to init graphics");
+
+	return -1;
+}
+
+int CGraphics_Threaded::Init()
+{
+	// fetch pointers
+	m_pStorage = Kernel()->RequestInterface<IStorage>();
+	m_pConsole = Kernel()->RequestInterface<IConsole>();
+
+	// Set all z to -5.0f
+	for(int i = 0; i < MAX_VERTICES; i++)
+		m_aVertices[i].m_Pos.z = -5.0f;
+
+	// init textures
+	m_FirstFreeTexture = 0;
+	for(int i = 0; i < MAX_TEXTURES; i++)
+		m_aTextures[i].m_Next = i+1;
+	m_aTextures[MAX_TEXTURES-1].m_Next = -1;
+
+	m_pBackend = CreateGraphicsBackend();
+	if(InitWindow() != 0)
+		return -1;
+
+	// fetch final resolusion
+	m_ScreenWidth = g_Config.m_GfxScreenWidth;
+	m_ScreenHeight = g_Config.m_GfxScreenHeight;
+
+	// create command buffers
+	for(int i = 0; i < NUM_CMDBUFFERS; i++)
+		m_apCommandBuffers[i] = new CCommandBuffer(128*1024, 2*1024*1024);
+	m_pCommandBuffer = m_apCommandBuffers[0];
+
+	// create null texture, will get id=0
+	static const unsigned char aNullTextureData[] = {
+		0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff,
+		0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0xff,0x00,0xff,
+		0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff,
+		0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0x00,0xff, 0xff,0xff,0x00,0xff,
+	};
+
+	m_InvalidTexture = LoadTextureRaw(4,4,CImageInfo::FORMAT_RGBA,aNullTextureData,CImageInfo::FORMAT_RGBA,TEXLOAD_NORESAMPLE);
+	return 0;
+}
+
+void CGraphics_Threaded::Shutdown()
+{
+	// shutdown the backend
+	m_pBackend->Shutdown();
+	delete m_pBackend;
+	m_pBackend = 0x0;
+
+	// delete the command buffers
+	for(int i = 0; i < NUM_CMDBUFFERS; i++)
+		delete m_apCommandBuffers[i];
+}
+
+void CGraphics_Threaded::Minimize()
+{
+	m_pBackend->Minimize();
+}
+
+void CGraphics_Threaded::Maximize()
+{
+	// TODO: SDL
+	m_pBackend->Maximize();
+}
+
+int CGraphics_Threaded::WindowActive()
+{
+	return m_pBackend->WindowActive();
+}
+
+int CGraphics_Threaded::WindowOpen()
+{
+	return m_pBackend->WindowOpen();
+
+}
+
+void CGraphics_Threaded::TakeScreenshot(const char *pFilename)
+{
+	// TODO: screenshot support
+	char aDate[20];
+	str_timestamp(aDate, sizeof(aDate));
+	str_format(m_aScreenshotName, sizeof(m_aScreenshotName), "screenshots/%s_%s.png", pFilename?pFilename:"screenshot", aDate);
+	m_DoScreenshot = true;
+}
+
+void CGraphics_Threaded::Swap()
+{
+	// TODO: screenshot support
+	if(m_DoScreenshot)
+	{
+		ScreenshotDirect(m_aScreenshotName);
+		m_DoScreenshot = false;
+	}
+
+	// add swap command
+	CCommandBuffer::SCommand_Swap Cmd;
+	Cmd.m_Finish = g_Config.m_GfxFinish;
+	m_pCommandBuffer->AddCommand(Cmd);
+
+	// kick the command buffer
+	KickCommandBuffer();
+}
+
+// syncronization
+void CGraphics_Threaded::InsertSignal(semaphore *pSemaphore)
+{
+	CCommandBuffer::SCommand_Signal Cmd;
+	Cmd.m_pSemaphore = pSemaphore;
+	m_pCommandBuffer->AddCommand(Cmd);
+}
+
+bool CGraphics_Threaded::IsIdle()
+{
+	return m_pBackend->IsIdle();
+}
+
+void CGraphics_Threaded::WaitForIdle()
+{
+	m_pBackend->WaitForIdle();
+}
+
+int CGraphics_Threaded::GetVideoModes(CVideoMode *pModes, int MaxModes)
+{
+	if(g_Config.m_GfxDisplayAllModes)
+	{
+		int Count = sizeof(g_aFakeModes)/sizeof(CVideoMode);
+		mem_copy(pModes, g_aFakeModes, sizeof(g_aFakeModes));
+		if(MaxModes < Count)
+			Count = MaxModes;
+		return Count;
+	}
+
+	// add videomodes command
+	CImageInfo Image;
+	mem_zero(&Image, sizeof(Image));
+
+	int NumModes = 0;
+	CCommandBuffer::SCommand_VideoModes Cmd;
+	Cmd.m_pModes = pModes;
+	Cmd.m_MaxModes = MaxModes;
+	Cmd.m_pNumModes = &NumModes;
+	m_pCommandBuffer->AddCommand(Cmd);
+
+	// kick the buffer and wait for the result and return it
+	KickCommandBuffer();
+	WaitForIdle();
+	return NumModes;
+}
+
+extern IEngineGraphics *CreateEngineGraphicsThreaded() { return new CGraphics_Threaded(); }
diff --git a/src/engine/client/graphics_threaded.h b/src/engine/client/graphics_threaded.h
new file mode 100644
index 00000000..f90f818d
--- /dev/null
+++ b/src/engine/client/graphics_threaded.h
@@ -0,0 +1,453 @@
+#pragma once
+
+#include <engine/graphics.h>
+
+class CCommandBuffer
+{
+	class CBuffer
+	{
+		unsigned char *m_pData;
+		unsigned m_Size;
+		unsigned m_Used;
+	public:
+		CBuffer(unsigned BufferSize)
+		{
+			m_Size = BufferSize;
+			m_pData = new unsigned char[m_Size];
+			m_Used = 0;
+		}
+
+		~CBuffer()
+		{
+			delete [] m_pData;
+			m_pData = 0x0;
+			m_Used = 0;
+			m_Size = 0;
+		}
+
+		void Reset()
+		{
+			m_Used = 0;
+		}
+
+		void *Alloc(unsigned Requested)
+		{
+			if(Requested + m_Used > m_Size)
+				return 0;
+			void *pPtr = &m_pData[m_Used];
+			m_Used += Requested;
+			return pPtr;
+		}
+
+		unsigned char *DataPtr() { return m_pData; }
+		unsigned DataSize() { return m_Size; }
+		unsigned DataUsed() { return m_Used; }
+	};
+
+public:
+	CBuffer m_CmdBuffer;
+	CBuffer m_DataBuffer;
+
+	enum
+	{
+		MAX_TEXTURES=1024*4,
+	};
+
+	enum
+	{
+		// commadn groups
+		CMDGROUP_CORE = 0, // commands that everyone has to implement
+		CMDGROUP_PLATFORM = 10000, // commands specific to a platform
+
+		//
+		CMD_NOP = CMDGROUP_CORE,
+
+		//
+		CMD_RUNBUFFER,
+
+		// syncronization
+		CMD_SIGNAL,
+
+		// texture commands
+		CMD_TEXTURE_CREATE,
+		CMD_TEXTURE_DESTROY,
+		CMD_TEXTURE_UPDATE,
+
+		// rendering
+		CMD_CLEAR,
+		CMD_RENDER,
+
+		// swap
+		CMD_SWAP,
+
+		// misc
+		CMD_SCREENSHOT,
+		CMD_VIDEOMODES,
+
+	};
+
+	enum
+	{
+		TEXFORMAT_INVALID = 0,
+		TEXFORMAT_RGB,
+		TEXFORMAT_RGBA,
+		TEXFORMAT_ALPHA,
+
+		TEXFLAG_NOMIPMAPS = 1,
+	};
+
+	enum
+	{
+		INITFLAG_FULLSCREEN = 1,
+		INITFLAG_VSYNC = 2,
+		INITFLAG_RESIZABLE = 4,
+	};
+
+	enum
+	{
+		//
+		PRIMTYPE_INVALID = 0,
+		PRIMTYPE_LINES,	
+		PRIMTYPE_QUADS,
+	};
+
+	enum
+	{
+		BLEND_NONE = 0,
+		BLEND_ALPHA,
+		BLEND_ADDITIVE,
+	};
+
+	enum
+	{
+		WRAP_REPEAT = 0,
+		WRAP_CLAMP,
+	};
+
+	struct SPoint { float x, y, z; };
+	struct STexCoord { float u, v; };
+	struct SColor { float r, g, b, a; };
+
+	struct SVertex
+	{
+		SPoint m_Pos;
+		STexCoord m_Tex;
+		SColor m_Color;
+	};
+
+	struct SCommand
+	{
+	public:
+		SCommand(unsigned Cmd) : m_Cmd(Cmd), m_Size(0) {}
+		unsigned m_Cmd;
+		unsigned m_Size;
+	};
+
+	struct SState
+	{
+		int m_BlendMode;
+		int m_WrapMode;
+		int m_Texture;
+		SPoint m_ScreenTL;
+		SPoint m_ScreenBR;
+
+		// clip
+		bool m_ClipEnable;
+		int m_ClipX;
+		int m_ClipY;
+		int m_ClipW;
+		int m_ClipH;
+	};
+		
+	struct SCommand_Clear : public SCommand
+	{
+		SCommand_Clear() : SCommand(CMD_CLEAR) {}
+		SColor m_Color;
+	};
+		
+	struct SCommand_Signal : public SCommand
+	{
+		SCommand_Signal() : SCommand(CMD_SIGNAL) {}
+		semaphore *m_pSemaphore;
+	};
+
+	struct SCommand_RunBuffer : public SCommand
+	{
+		SCommand_RunBuffer() : SCommand(CMD_RUNBUFFER) {}
+		CCommandBuffer *m_pOtherBuffer;
+	};
+
+	struct SCommand_Render : public SCommand
+	{
+		SCommand_Render() : SCommand(CMD_RENDER) {}
+		SState m_State;
+		unsigned m_PrimType;
+		unsigned m_PrimCount;
+		SVertex *m_pVertices; // you should use the command buffer data to allocate vertices for this command
+	};
+
+	struct SCommand_Screenshot : public SCommand
+	{
+		SCommand_Screenshot() : SCommand(CMD_SCREENSHOT) {}
+		CImageInfo *m_pImage; // processor will fill this out, the one who adds this command must free the data as well
+	};
+
+	struct SCommand_VideoModes : public SCommand
+	{
+		SCommand_VideoModes() : SCommand(CMD_VIDEOMODES) {}
+
+		CVideoMode *m_pModes; // processor will fill this in
+		int m_MaxModes; // maximum of modes the processor can write to the m_pModes
+		int *m_pNumModes; // processor will write to this pointer
+	};
+
+	struct SCommand_Swap : public SCommand
+	{
+		SCommand_Swap() : SCommand(CMD_SWAP) {}
+
+		int m_Finish;
+	};
+
+	struct SCommand_Texture_Create : public SCommand
+	{
+		SCommand_Texture_Create() : SCommand(CMD_TEXTURE_CREATE) {}
+
+		// texture information
+		int m_Slot;
+
+		int m_Width;
+		int m_Height;
+		int m_Format;
+		int m_StoreFormat;
+		int m_Flags;
+		void *m_pData; // will be freed by the command processor
+	};
+
+	struct SCommand_Texture_Update : public SCommand
+	{
+		SCommand_Texture_Update() : SCommand(CMD_TEXTURE_UPDATE) {}
+
+		// texture information
+		int m_Slot;
+
+		int m_X;
+		int m_Y;
+		int m_Width;
+		int m_Height;
+		int m_Format;
+		void *m_pData; // will be freed by the command processor
+	};
+
+
+	struct SCommand_Texture_Destroy : public SCommand
+	{
+		SCommand_Texture_Destroy() : SCommand(CMD_TEXTURE_DESTROY) {}
+
+		// texture information
+		int m_Slot;
+	};
+	
+	//
+	CCommandBuffer(unsigned CmdBufferSize, unsigned DataBufferSize)
+	: m_CmdBuffer(CmdBufferSize), m_DataBuffer(DataBufferSize)
+	{
+	}
+
+	void *AllocData(unsigned WantedSize)
+	{
+		return m_DataBuffer.Alloc(WantedSize);
+	}
+
+	template<class T>
+	bool AddCommand(const T &Command)
+	{
+		// make sure that we don't do something stupid like ->AddCommand(&Cmd);
+		(void)static_cast<const SCommand *>(&Command);
+
+		// allocate and copy the command into the buffer
+		SCommand *pCmd = (SCommand *)m_CmdBuffer.Alloc(sizeof(Command));
+		if(!pCmd)
+			return false;
+		mem_copy(pCmd, &Command, sizeof(Command));
+		pCmd->m_Size = sizeof(Command);
+		return true;
+	}
+
+	SCommand *GetCommand(unsigned *pIndex)
+	{
+		if(*pIndex >= m_CmdBuffer.DataUsed())
+			return NULL;
+
+		SCommand *pCommand = (SCommand *)&m_CmdBuffer.DataPtr()[*pIndex];
+		*pIndex += pCommand->m_Size;
+		return pCommand;
+	}
+
+	void Reset()
+	{
+		m_CmdBuffer.Reset();
+		m_DataBuffer.Reset();
+	}
+};
+
+// interface for the graphics backend
+// all these functions are called on the main thread
+class IGraphicsBackend
+{
+public:
+	enum
+	{
+		INITFLAG_FULLSCREEN = 1,
+		INITFLAG_VSYNC = 2,
+		INITFLAG_RESIZABLE = 4,
+	};
+
+	virtual int Init(const char *pName, int Width, int Height, int FsaaSamples, int Flags) = 0;
+	virtual int Shutdown() = 0;
+
+	virtual void Minimize() = 0;
+	virtual void Maximize() = 0;
+	virtual int WindowActive() = 0;
+	virtual int WindowOpen() = 0;
+
+	virtual void RunBuffer(CCommandBuffer *pBuffer) = 0;
+	virtual bool IsIdle() const = 0;
+	virtual void WaitForIdle() = 0;
+};
+
+class CGraphics_Threaded : public IEngineGraphics
+{
+	enum
+	{
+		NUM_CMDBUFFERS = 2,
+
+		MAX_VERTICES = 32*1024,
+		MAX_TEXTURES = 1024*4,
+		
+		DRAWING_QUADS=1,
+		DRAWING_LINES=2
+	};
+
+	CCommandBuffer::SState m_State;
+	IGraphicsBackend *m_pBackend;
+
+	CCommandBuffer *m_apCommandBuffers[NUM_CMDBUFFERS];
+	CCommandBuffer *m_pCommandBuffer;
+	unsigned m_CurrentCommandBuffer;
+
+	//
+	class IStorage *m_pStorage;
+	class IConsole *m_pConsole;
+
+	CCommandBuffer::SVertex m_aVertices[MAX_VERTICES];
+	int m_NumVertices;
+
+	CCommandBuffer::SColor m_aColor[4];
+	CCommandBuffer::STexCoord m_aTexture[4];
+
+	bool m_RenderEnable;
+
+	float m_Rotation;
+	int m_Drawing;
+	bool m_DoScreenshot;
+	char m_aScreenshotName[128];
+
+	int m_InvalidTexture;
+
+	struct CTexture
+	{
+		int m_State;
+		int m_MemSize;
+		int m_Flags;
+		int m_Next;
+	};
+
+	CTexture m_aTextures[MAX_TEXTURES];
+	int m_FirstFreeTexture;
+	int m_TextureMemoryUsage;
+
+	void FlushVertices();
+	void AddVertices(int Count);
+	void Rotate4(const CCommandBuffer::SPoint &rCenter, CCommandBuffer::SVertex *pPoints);
+
+	static unsigned char Sample(int w, int h, const unsigned char *pData, int u, int v, int Offset, int ScaleW, int ScaleH, int Bpp);
+	static unsigned char *Rescale(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData);
+
+	void KickCommandBuffer();
+
+	int IssueInit();
+	int InitWindow();
+public:
+	CGraphics_Threaded();
+
+	virtual void ClipEnable(int x, int y, int w, int h);
+	virtual void ClipDisable();
+
+	virtual void BlendNone();
+	virtual void BlendNormal();
+	virtual void BlendAdditive();
+
+	virtual void WrapNormal();
+	virtual void WrapClamp();
+
+	virtual int MemoryUsage() const;
+
+	virtual void MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY);
+	virtual void GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBottomRightX, float *pBottomRightY);
+
+	virtual void LinesBegin();
+	virtual void LinesEnd();
+	virtual void LinesDraw(const CLineItem *pArray, int Num);
+
+	virtual int UnloadTexture(int Index);
+	virtual int LoadTextureRaw(int Width, int Height, int Format, const void *pData, int StoreFormat, int Flags);
+	virtual int LoadTextureRawSub(int TextureID, int x, int y, int Width, int Height, int Format, const void *pData);
+
+	// simple uncompressed RGBA loaders
+	virtual int LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags);
+	virtual int LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType);
+
+	void ScreenshotDirect(const char *pFilename);
+
+	virtual void TextureSet(int TextureID);
+
+	virtual void Clear(float r, float g, float b);
+
+	virtual void QuadsBegin();
+	virtual void QuadsEnd();
+	virtual void QuadsSetRotation(float Angle);
+
+	virtual void SetColorVertex(const CColorVertex *pArray, int Num);
+	virtual void SetColor(float r, float g, float b, float a);
+
+	virtual void QuadsSetSubset(float TlU, float TlV, float BrU, float BrV);
+	virtual void QuadsSetSubsetFree(
+		float x0, float y0, float x1, float y1,
+		float x2, float y2, float x3, float y3);
+
+	virtual void QuadsDraw(CQuadItem *pArray, int Num);
+	virtual void QuadsDrawTL(const CQuadItem *pArray, int Num);
+	virtual void QuadsDrawFreeform(const CFreeformItem *pArray, int Num);
+	virtual void QuadsText(float x, float y, float Size, float r, float g, float b, float a, const char *pText);
+
+	virtual void Minimize();
+	virtual void Maximize();
+
+	virtual int WindowActive();
+	virtual int WindowOpen();
+
+	virtual int Init();
+	virtual void Shutdown();
+
+	virtual void TakeScreenshot(const char *pFilename);
+	virtual void Swap();
+
+	virtual int GetVideoModes(CVideoMode *pModes, int MaxModes);
+
+	// syncronization
+	virtual void InsertSignal(semaphore *pSemaphore);
+	virtual bool IsIdle();
+	virtual void WaitForIdle();
+};
+
+extern IGraphicsBackend *CreateGraphicsBackend();
diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp
index ddfc7597..4dda9da9 100644
--- a/src/engine/client/serverbrowser.cpp
+++ b/src/engine/client/serverbrowser.cpp
@@ -1,6 +1,6 @@
 /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
 /* If you are missing that file, acquire a complete release at teeworlds.com.                */
-#include <algorithm> // sort
+#include <algorithm> // sort  TODO: remove this
 
 #include <base/math.h>
 #include <base/system.h>
@@ -26,7 +26,7 @@ class SortWrap
 	CServerBrowser *m_pThis;
 public:
 	SortWrap(CServerBrowser *t, SortFunc f) : m_pfnSort(f), m_pThis(t) {}
-	bool operator()(int a, int b) { return (m_pThis->*m_pfnSort)(a, b); }
+	bool operator()(int a, int b) { return (g_Config.m_BrSortOrder ? (m_pThis->*m_pfnSort)(b, a) : (m_pThis->*m_pfnSort)(a, b)); }
 };
 
 CServerBrowser::CServerBrowser()
@@ -283,27 +283,16 @@ void CServerBrowser::Sort()
 
 	// sort
 	if(g_Config.m_BrSort == IServerBrowser::SORT_NAME)
-		std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareName));
+		std::stable_sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareName));
 	else if(g_Config.m_BrSort == IServerBrowser::SORT_PING)
-		std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortComparePing));
+		std::stable_sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortComparePing));
 	else if(g_Config.m_BrSort == IServerBrowser::SORT_MAP)
-		std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareMap));
+		std::stable_sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareMap));
 	else if(g_Config.m_BrSort == IServerBrowser::SORT_NUMPLAYERS)
-		std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this,
+		std::stable_sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this,
 					g_Config.m_BrFilterSpectators ? &CServerBrowser::SortCompareNumPlayers : &CServerBrowser::SortCompareNumClients));
 	else if(g_Config.m_BrSort == IServerBrowser::SORT_GAMETYPE)
-		std::sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareGametype));
-
-	// invert the list if requested
-	if(g_Config.m_BrSortOrder)
-	{
-		for(i = 0; i < m_NumSortedServers/2; i++)
-		{
-			int Temp = m_pSortedServerlist[i];
-			m_pSortedServerlist[i] = m_pSortedServerlist[m_NumSortedServers-i-1];
-			m_pSortedServerlist[m_NumSortedServers-i-1] = Temp;
-		}
-	}
+		std::stable_sort(m_pSortedServerlist, m_pSortedServerlist+m_NumSortedServers, SortWrap(this, &CServerBrowser::SortCompareGametype));
 
 	// set indexes
 	for(i = 0; i < m_NumSortedServers; i++)
@@ -381,7 +370,6 @@ void CServerBrowser::SetInfo(CServerEntry *pEntry, const CServerInfo &Info)
 	}*/
 
 	pEntry->m_GotInfo = 1;
-	Sort();
 }
 
 CServerBrowser::CServerEntry *CServerBrowser::Add(const NETADDR &Addr)
@@ -399,7 +387,7 @@ CServerBrowser::CServerEntry *CServerBrowser::Add(const NETADDR &Addr)
 	pEntry->m_Info.m_NetAddr = Addr;
 
 	pEntry->m_Info.m_Latency = 999;
-	net_addr_str(&Addr, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aAddress));
+	net_addr_str(&Addr, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aAddress), true);
 	str_copy(pEntry->m_Info.m_aName, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aName));
 
 	// check if it's a favorite
@@ -539,7 +527,7 @@ void CServerBrowser::RequestImpl(const NETADDR &Addr, CServerEntry *pEntry) cons
 	if(g_Config.m_Debug)
 	{
 		char aAddrStr[NETADDR_MAXSTRSIZE];
-		net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+		net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr), true);
 		char aBuf[256];
 		str_format(aBuf, sizeof(aBuf),"requesting server info from %s", aAddrStr);
 		m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", aBuf);
@@ -680,7 +668,7 @@ void CServerBrowser::AddFavorite(const NETADDR &Addr)
 	if(g_Config.m_Debug)
 	{
 		char aAddrStr[NETADDR_MAXSTRSIZE];
-		net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+		net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr), true);
 		char aBuf[256];
 		str_format(aBuf, sizeof(aBuf), "added fav, %s", aAddrStr);
 		m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", aBuf);
@@ -734,12 +722,11 @@ void CServerBrowser::ConfigSaveCallback(IConfig *pConfig, void *pUserData)
 {
 	CServerBrowser *pSelf = (CServerBrowser *)pUserData;
 
-	int i;
 	char aAddrStr[128];
 	char aBuffer[256];
-	for(i = 0; i < pSelf->m_NumFavoriteServers; i++)
+	for(int i = 0; i < pSelf->m_NumFavoriteServers; i++)
 	{
-		net_addr_str(&pSelf->m_aFavoriteServers[i], aAddrStr, sizeof(aAddrStr));
+		net_addr_str(&pSelf->m_aFavoriteServers[i], aAddrStr, sizeof(aAddrStr), true);
 		str_format(aBuffer, sizeof(aBuffer), "add_favorite %s", aAddrStr);
 		pConfig->WriteLine(aBuffer);
 	}
diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp
index c2ca91f7..343fa2e8 100644
--- a/src/engine/client/sound.cpp
+++ b/src/engine/client/sound.cpp
@@ -1,8 +1,11 @@
 /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
 /* If you are missing that file, acquire a complete release at teeworlds.com.                */
+#include <base/math.h>
 #include <base/system.h>
+
 #include <engine/graphics.h>
 #include <engine/storage.h>
+
 #include <engine/shared/config.h>
 
 #include "SDL.h"
@@ -19,8 +22,6 @@ enum
 	NUM_SAMPLES = 512,
 	NUM_VOICES = 64,
 	NUM_CHANNELS = 16,
-	
-	MAX_FRAMES = 1024
 };
 
 struct CSample
@@ -63,7 +64,8 @@ static int m_MixingRate = 48000;
 static volatile int m_SoundVolume = 100;
 
 static int m_NextVoice = 0;
-
+static int *m_pMixBuffer = 0;	// buffer only used by the thread callback function
+static unsigned m_MaxFrames = 0;
 
 // TODO: there should be a faster way todo this
 static short Int2Short(int i)
@@ -84,26 +86,27 @@ static int IntAbs(int i)
 
 static void Mix(short *pFinalOut, unsigned Frames)
 {
-	int aMixBuffer[MAX_FRAMES*2] = {0};
 	int MasterVol;
+	mem_zero(m_pMixBuffer, m_MaxFrames*2*sizeof(int));
+	Frames = min(Frames, m_MaxFrames);
 
 	// aquire lock while we are mixing
 	lock_wait(m_SoundLock);
-	
+
 	MasterVol = m_SoundVolume;
-	
+
 	for(unsigned i = 0; i < NUM_VOICES; i++)
 	{
 		if(m_aVoices[i].m_pSample)
 		{
 			// mix voice
 			CVoice *v = &m_aVoices[i];
-			int *pOut = aMixBuffer;
+			int *pOut = m_pMixBuffer;
 
 			int Step = v->m_pSample->m_Channels; // setup input sources
 			short *pInL = &v->m_pSample->m_pData[v->m_Tick*Step];
 			short *pInR = &v->m_pSample->m_pData[v->m_Tick*Step+1];
-			
+
 			unsigned End = v->m_pSample->m_NumFrames-v->m_Tick;
 
 			int Rvol = v->m_pChannel->m_Vol;
@@ -112,7 +115,7 @@ static void Mix(short *pFinalOut, unsigned Frames)
 			// make sure that we don't go outside the sound data
 			if(Frames < End)
 				End = Frames;
-			
+
 			// check if we have a mono sound
 			if(v->m_pSample->m_Channels == 1)
 				pInR = pInL;
@@ -126,14 +129,14 @@ static void Mix(short *pFinalOut, unsigned Frames)
 				int dy = v->m_Y - m_CenterY;
 				int Dist = (int)sqrtf((float)dx*dx+dy*dy); // float here. nasty
 				int p = IntAbs(dx);
-				if(Dist < Range)
+				if(Dist >= 0 && Dist < Range)
 				{
 					// panning
 					if(dx > 0)
 						Lvol = ((Range-p)*Lvol)/Range;
 					else
 						Rvol = ((Range-p)*Rvol)/Range;
-					
+
 					// falloff
 					Lvol = (Lvol*(Range-Dist))/Range;
 					Rvol = (Rvol*(Range-Dist))/Range;
@@ -154,7 +157,7 @@ static void Mix(short *pFinalOut, unsigned Frames)
 				pInR += Step;
 				v->m_Tick++;
 			}
-			
+
 			// free voice if not used any more
 			if(v->m_Tick == v->m_pSample->m_NumFrames)
 			{
@@ -165,8 +168,8 @@ static void Mix(short *pFinalOut, unsigned Frames)
 			}
 		}
 	}
-	
-	
+
+
 	// release the lock
 	lock_release(m_SoundLock);
 
@@ -176,8 +179,8 @@ static void Mix(short *pFinalOut, unsigned Frames)
 		for(unsigned i = 0; i < Frames; i++)
 		{
 			int j = i<<1;
-			int vl = ((aMixBuffer[j]*MasterVol)/101)>>8;
-			int vr = ((aMixBuffer[j+1]*MasterVol)/101)>>8;
+			int vl = ((m_pMixBuffer[j]*MasterVol)/101)>>8;
+			int vr = ((m_pMixBuffer[j+1]*MasterVol)/101)>>8;
 
 			pFinalOut[j] = Int2Short(vl);
 			pFinalOut[j+1] = Int2Short(vr);
@@ -201,14 +204,20 @@ int CSound::Init()
 	m_SoundEnabled = 0;
 	m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
 	m_pStorage = Kernel()->RequestInterface<IStorage>();
-	
+
 	SDL_AudioSpec Format;
-	
+
 	m_SoundLock = lock_create();
-	
+
 	if(!g_Config.m_SndEnable)
 		return 0;
-	
+
+	if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
+	{
+		dbg_msg("gfx", "unable to init SDL audio: %s", SDL_GetError());
+		return -1;
+	}
+
 	m_MixingRate = g_Config.m_SndRate;
 
 	// Set 16-bit stereo audio at 22Khz
@@ -228,8 +237,11 @@ int CSound::Init()
 	else
 		dbg_msg("client/sound", "sound init successful");
 
+	m_MaxFrames = g_Config.m_SndBufferSize*2;
+	m_pMixBuffer = (int *)mem_alloc(m_MaxFrames*2*sizeof(int), 1);
+
 	SDL_PauseAudio(0);
-	
+
 	m_SoundEnabled = 1;
 	Update(); // update the volume
 	return 0;
@@ -239,24 +251,30 @@ int CSound::Update()
 {
 	// update volume
 	int WantedVolume = g_Config.m_SndVolume;
-	
+
 	if(!m_pGraphics->WindowActive() && g_Config.m_SndNonactiveMute)
 		WantedVolume = 0;
-	
+
 	if(WantedVolume != m_SoundVolume)
 	{
 		lock_wait(m_SoundLock);
 		m_SoundVolume = WantedVolume;
 		lock_release(m_SoundLock);
 	}
-	
+
 	return 0;
 }
 
 int CSound::Shutdown()
 {
 	SDL_CloseAudio();
+	SDL_QuitSubSystem(SDL_INIT_AUDIO);
 	lock_destroy(m_SoundLock);
+	if(m_pMixBuffer)
+	{
+		mem_free(m_pMixBuffer);
+		m_pMixBuffer = 0;
+	}
 	return 0;
 }
 
@@ -277,7 +295,7 @@ void CSound::RateConvert(int SampleID)
 	CSample *pSample = &m_aSamples[SampleID];
 	int NumFrames = 0;
 	short *pNewData = 0;
-	
+
 	// make sure that we need to convert this sound
 	if(!pSample->m_pData || pSample->m_Rate == m_MixingRate)
 		return;
@@ -285,7 +303,7 @@ void CSound::RateConvert(int SampleID)
 	// allocate new data
 	NumFrames = (int)((pSample->m_NumFrames/(float)pSample->m_Rate)*m_MixingRate);
 	pNewData = (short *)mem_alloc(NumFrames*pSample->m_Channels*sizeof(short), 1);
-	
+
 	for(int i = 0; i < NumFrames; i++)
 	{
 		// resample TODO: this should be done better, like linear atleast
@@ -293,7 +311,7 @@ void CSound::RateConvert(int SampleID)
 		int f = (int)(a*pSample->m_NumFrames);
 		if(f >= pSample->m_NumFrames)
 			f = pSample->m_NumFrames-1;
-		
+
 		// set new data
 		if(pSample->m_Channels == 1)
 			pNewData[i] = pSample->m_pData[f];
@@ -303,7 +321,7 @@ void CSound::RateConvert(int SampleID)
 			pNewData[i*2+1] = pSample->m_pData[f*2+1];
 		}
 	}
-	
+
 	// free old data and apply new
 	mem_free(pSample->m_pData);
 	pSample->m_pData = pNewData;
@@ -321,15 +339,15 @@ int CSound::LoadWV(const char *pFilename)
 	int SampleID = -1;
 	char aError[100];
 	WavpackContext *pContext;
-	
+
 	// don't waste memory on sound when we are stress testing
 	if(g_Config.m_DbgStress)
 		return -1;
-		
+
 	// no need to load sound when we are running with no sound
 	if(!m_SoundEnabled)
 		return 1;
-		
+
 	if(!m_pStorage)
 		return -1;
 
@@ -372,7 +390,7 @@ int CSound::LoadWV(const char *pFilename)
 			dbg_msg("sound/wv", "file is %d Hz, not 44100 Hz. filename='%s'", snd->rate, filename);
 			return -1;
 		}*/
-		
+
 		if(BitsPerSample != 16)
 		{
 			dbg_msg("sound/wv", "bps is %d, not 16, filname='%s'", BitsPerSample, pFilename);
@@ -382,7 +400,7 @@ int CSound::LoadWV(const char *pFilename)
 		pData = (int *)mem_alloc(4*m_aSamples*m_aChannels, 1);
 		WavpackUnpackSamples(pContext, pData, m_aSamples); // TODO: check return value
 		pSrc = pData;
-		
+
 		pSample->m_pData = (short *)mem_alloc(2*m_aSamples*m_aChannels, 1);
 		pDst = pSample->m_pData;
 
@@ -428,9 +446,9 @@ int CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y)
 {
 	int VoiceID = -1;
 	int i;
-	
+
 	lock_wait(m_SoundLock);
-	
+
 	// search for voice
 	for(i = 0; i < NUM_VOICES; i++)
 	{
@@ -442,7 +460,7 @@ int CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y)
 			break;
 		}
 	}
-	
+
 	// voice found, use it
 	if(VoiceID != -1)
 	{
@@ -457,7 +475,7 @@ int CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y)
 		m_aVoices[VoiceID].m_X = (int)x;
 		m_aVoices[VoiceID].m_Y = (int)y;
 	}
-	
+
 	lock_release(m_SoundLock);
 	return VoiceID;
 }
diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp
index 4a3a2eb3..af06fc11 100644
--- a/src/engine/client/text.cpp
+++ b/src/engine/client/text.cpp
@@ -9,21 +9,11 @@
 	#include <windows.h>
 #endif
 
-#ifdef CONF_PLATFORM_MACOSX
-	#include <OpenGL/gl.h>
-	#include <OpenGL/glu.h>
-#else
-	#include <GL/gl.h>
-	#include <GL/glu.h>
-#endif
-
 // ft2 texture
 #include <ft2build.h>
 #include FT_FREETYPE_H
 
 // TODO: Refactor: clean this up
-
-
 enum
 {
 	MAX_CHARACTERS = 64,
@@ -54,7 +44,7 @@ struct CFontSizeData
 	int m_FontSize;
 	FT_Face *m_pFace;
 
-	GLuint m_aTextures[2];
+	int m_aTextures[2];
 	int m_TextureWidth;
 	int m_TextureHeight;
 
@@ -107,7 +97,7 @@ class CTextRender : public IEngineTextRender
 	float m_TextOutlineB;
 	float m_TextOutlineA;
 
-	int m_FontTextureFormat;
+	//int m_FontTextureFormat;
 
 	CFont *m_pDefaultFont;
 
@@ -158,26 +148,25 @@ class CTextRender : public IEngineTextRender
 		void *pMem = mem_alloc(Width*Height, 1);
 		mem_zero(pMem, Width*Height);
 
-		if(pSizeData->m_aTextures[0] == 0)
-			glGenTextures(2, pSizeData->m_aTextures);
-		else
-			FontMemoryUsage -= pSizeData->m_TextureWidth*pSizeData->m_TextureHeight*2;
+		for(int i = 0; i < 2; i++)
+		{
+			if(pSizeData->m_aTextures[i] != 0)
+			{
+				Graphics()->UnloadTexture(pSizeData->m_aTextures[i]);
+				FontMemoryUsage -= pSizeData->m_TextureWidth*pSizeData->m_TextureHeight;
+				pSizeData->m_aTextures[i] = 0;
+			}
+
+			pSizeData->m_aTextures[i] = Graphics()->LoadTextureRaw(Width, Height, CImageInfo::FORMAT_ALPHA, pMem, CImageInfo::FORMAT_ALPHA, IGraphics::TEXLOAD_NOMIPMAPS);
+			FontMemoryUsage += Width*Height;
+		}
 
 		pSizeData->m_NumXChars = Xchars;
 		pSizeData->m_NumYChars = Ychars;
 		pSizeData->m_TextureWidth = Width;
 		pSizeData->m_TextureHeight = Height;
 		pSizeData->m_CurrentCharacter = 0;
-
-		for(int i = 0; i < 2; i++)
-		{
-			glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[i]);
-			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-			glTexImage2D(GL_TEXTURE_2D, 0, m_FontTextureFormat, Width, Height, 0, m_FontTextureFormat, GL_UNSIGNED_BYTE, pMem);
-			FontMemoryUsage += Width*Height;
-		}
-
+		
 		dbg_msg("", "pFont memory usage: %d", FontMemoryUsage);
 
 		mem_free(pMem);
@@ -254,11 +243,16 @@ class CTextRender : public IEngineTextRender
 		int x = (SlotID%pSizeData->m_NumXChars) * (pSizeData->m_TextureWidth/pSizeData->m_NumXChars);
 		int y = (SlotID/pSizeData->m_NumXChars) * (pSizeData->m_TextureHeight/pSizeData->m_NumYChars);
 
+		Graphics()->LoadTextureRawSub(pSizeData->m_aTextures[Texnum], x, y,
+			pSizeData->m_TextureWidth/pSizeData->m_NumXChars,
+			pSizeData->m_TextureHeight/pSizeData->m_NumYChars,
+			CImageInfo::FORMAT_ALPHA, pData);
+		/*
 		glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[Texnum]);
 		glTexSubImage2D(GL_TEXTURE_2D, 0, x, y,
 			pSizeData->m_TextureWidth/pSizeData->m_NumXChars,
 			pSizeData->m_TextureHeight/pSizeData->m_NumYChars,
-			m_FontTextureFormat, GL_UNSIGNED_BYTE, pData);
+			m_FontTextureFormat, GL_UNSIGNED_BYTE, pData);*/
 	}
 
 	// 32k of data used for rendering glyphs
@@ -285,7 +279,8 @@ class CTextRender : public IEngineTextRender
 					Oldest = i;
 			}
 
-			if(time_get()-pSizeData->m_aCharacters[Oldest].m_TouchTime < time_freq())
+			if(time_get()-pSizeData->m_aCharacters[Oldest].m_TouchTime < time_freq() &&
+				(pSizeData->m_NumXChars < MAX_CHARACTERS || pSizeData->m_NumYChars < MAX_CHARACTERS))
 			{
 				IncreaseTextureSize(pSizeData);
 				return GetSlot(pSizeData);
@@ -455,7 +450,7 @@ public:
 		m_pDefaultFont = 0;
 
 		// GL_LUMINANCE can be good for debugging
-		m_FontTextureFormat = GL_ALPHA;
+		//m_FontTextureFormat = GL_ALPHA;
 	}
 
 	virtual void Init()
@@ -620,11 +615,10 @@ public:
 			if(pCursor->m_Flags&TEXTFLAG_RENDER)
 			{
 				// TODO: Make this better
-				glEnable(GL_TEXTURE_2D);
 				if (i == 0)
-					glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[1]);
+					Graphics()->TextureSet(pSizeData->m_aTextures[1]);
 				else
-					glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[0]);
+					Graphics()->TextureSet(pSizeData->m_aTextures[0]);
 
 				Graphics()->QuadsBegin();
 				if (i == 0)