/*
 * $Id: rendergl.cpp 324 2008-10-01 18:04:45Z kybav1 $
 *
 * Description  : One line description of file.
 * Author       : Vaclav Kyba <mail/Jabber: vaseo1@gmail.com>  <ICQ: 98576293>
 * Updates in 1.6.2010 - 17.9.2010: Milan Stezka <stezkamilan@seznam.cz>
 * Edit			: Kry�tof Valenta valenkry@fel.cvut.cz - Point geometry rendering support added (19.5.2011)
 *
 * Purpose      :
 *     These per material properties are ignored, they are treated globally as:
 *        Material::DEPTH_TEST always on
 *        Material::DEPTH_MASK always on
 *        Material::DEPTH_FUNC always GL_LEQUAL
 *        Material::TEX_GEN_FUNC_U_SPHERE and Material::TEX_GEN_FUNC_V_SPHERE on
 *        Material::INVERT_ALPHA ignored
 */

#include <wx/wx.h>
#include <wx/rawbmp.h>
#include <stdlib.h>
#include <GL/glew.h>
#ifdef __WINDOWS__
#include <GL/wglew.h>
#else
#include <GL/glxew.h>
#endif

//#include <wx/stopwatch.h>
#include "RenderGlModule.h"
//#include "scene.h"
//#include "camera.h"
#include "glgetproc.h"
#include "lightnode.h"
#include "rendermanager.h"
#include "BinTreesCulling.h"

#include "geometrytext.h"

using namespace VRUT;

static const GLenum GL_HEADLIGHT = GL_LIGHT0;
static const GLenum LIGHTID[] = { GL_LIGHT1, GL_LIGHT2, GL_LIGHT3, GL_LIGHT4, GL_LIGHT5, GL_LIGHT6, GL_LIGHT7 };


//------------------------------------------
//----    Classes for text rendering    ----
//------------------------------------------

FontGlyphsTextureOpenGL::FontGlyphsTextureOpenGL(const std::shared_ptr<FontGlyphsTexture> &glyphs, int dimx, int dimy) : glyphs(glyphs), texture(0)
{
	dims[0] = dimx;
	dims[1] = dimy;
	updateFontGlyphsTexture();
}

FontGlyphsTextureOpenGL::~FontGlyphsTextureOpenGL()
{
	if (texture)
		glDeleteTextures(1, &texture);
	texture = 0;
}

void FontGlyphsTextureOpenGL::updateFontGlyphsTexture()
{
	// Avoid out of bound errors. TODO Also add some warning when GetCharCount and dims differs.
	size_t effective_char_count = std::min(glyphs->GetCharCount(), size_t(dims[0] * dims[1]));

	size_t tex_size = glyphs->GetTexSize();

	if (!texture)
		glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);

	// Note: When using compatibility profile of OpenGL, use GL_ALPHA as internal_format and as format. When
	// using core profile, GL_ALPHA is not available, so use GL_RED and use swizzle in shaders to get .111r
	// or .rrr1. Do not use .rrrr, because the alpha value would be applied twice.
	glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, tex_size * dims[0], tex_size * dims[1], 0, GL_ALPHA, GL_FLOAT, nullptr);
		
	for (size_t c = 0; c < effective_char_count; c++)
	{
		int tex[3] = {
			int( c % dims[0]),
			int((c / dims[0]) % dims[1]),
			int((c / dims[0]) / dims[1])
		};
		glTexSubImage2D(GL_TEXTURE_2D, 0, tex[0] * tex_size, tex[1] * tex_size, tex_size, tex_size, GL_ALPHA, GL_FLOAT, glyphs->GetTexData(c));
	}
	if (GLEW_VERSION_3_0)
	{
		glGenerateMipmap(GL_TEXTURE_2D);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	}
	else
	{
		// No mipmaps
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	}
	glBindTexture(GL_TEXTURE_2D, 0);
}

//-----------------------------------

std::shared_ptr<FontGlyphsTextureOpenGL> FontGlyphsTexturesFactory::GetOrCreateFont(const wxFont &font)
{
	// Ignore point size
	wxFont _font(font);
	_font.SetPointSize(12);

	for (auto &fnt : fonts)
		if (fnt.first == _font)
			return fnt.second;

	auto glyphs = std::make_shared<FontGlyphsTexture>();
	glyphs->Create(_font, 1024, 64);			// Create 1024 glyphs
	auto texture = std::make_shared<FontGlyphsTextureOpenGL>(glyphs, 32, 32);
	fonts.push_back(std::make_pair(_font, texture));
	return texture;
}

void FontGlyphsTexturesFactory::DestroyAllFonts()
{
	fonts.clear();
}

//------------------------------
//----    RenderGlModule    ----
//------------------------------

RenderGlModule::RenderGlModule(MODULE_ID _id, const wxString & _name, Kernel * _kernel)
	: RenderModule(_id, _name, 0, _kernel), openGLVendor(NVIDIA), cameraLeft(false), windowID(RENDER_WINDOW_ID_NONE), lastRenderedState(0), lastBarrierState(0),
	  windowWidth(0), windowHeight(0), maxClippingPlanes(0), enabledClippingPlanesCount(0), defaultClearColor(0.3f, 0.3f, 0.3f, 0.0f), renderTargetR(nullptr),
	  multiSampledRenderTargetR(nullptr), clonedCamera(NULL), clonedCameraScene(NULL), TMTMatrix(nullptr)
{
#ifdef RENDER_GL_PROFILING
	drawTimeCPUProf = new ProfilerStatsOptim(wxT("CPU DrawTime"));
#endif

	//drawCount=0;
	synchronized=false;
	antiAliasingMethodStringList.push_back(wxT("None"));
	antiAliasingMethodStringList.push_back(wxT("Multi sampling"));
	antiAliasingMethodStringList.push_back(wxT("Coverage sampling"));
	lighting = true;
	REGISTER_PARAM_GUI_CHECKBOX(lightingParamID, wxT("lighting"), lighting, wxT("Enable lighting."));
	headlight = true;
	REGISTER_PARAM_GUI_CHECKBOX(headlightParamID, wxT("headlight"), headlight, wxT("Enable headlight."));
	drawUsingFBO = true;
	REGISTER_PARAM_GUI_CHECKBOX(drawUsingFBOParamID, wxT("drawUsingFBO"), drawUsingFBO, wxT("Draw using a Frame Buffer Object"));
	fboInternalFormatStringList.push_back(wxT("RGBA 8-bit Integer"));
	fboInternalFormatStringList.push_back(wxT("RGBA 16-bit Integer"));
	fboInternalFormatStringList.push_back(wxT("RGBA 16-bit Float"));
	fboInternalFormatStringList.push_back(wxT("RGBA 32-bit Float"));
	fboInternalFormatValuesList.push_back(GL_RGBA8);
	fboInternalFormatValuesList.push_back(GL_RGBA16);
	fboInternalFormatValuesList.push_back(GL_RGBA16F_ARB);
	fboInternalFormatValuesList.push_back(GL_RGBA32F_ARB);
	fboInternalFormatStringListIndex = 0;
	wxString fboInternalFormatTmp(fboInternalFormatStringList[0]);
	REGISTER_PARAM_GUI_COMBOBOX(fboInternalFormatParamID, wxT("fboInternalFormat"), fboInternalFormatTmp, fboInternalFormatStringList, wxT("Frame buffer object internal format"));
	fboInternalFormat=fboInternalFormatValuesList[FindBestMatch(fboInternalFormatStringList, fboInternalFormatTmp)];
	sortGeometries = 0;
	sortGeometriesStringList.push_back(wxT("0 - None"));
	sortGeometriesStringList.push_back(wxT("1 - Depth"));
	sortGeometriesStringList.push_back(wxT("2 - Material"));
	wxString sortGeometriesTmp(sortGeometriesStringList[0]);
	REGISTER_PARAM_GUI_COMBOBOX(sortGeometriesParamID, wxT("sortGeometries"), sortGeometriesTmp, sortGeometriesStringList, wxT("Sort solid geometries by depth (more z-early skips) or material (less OpenGL states switches)."));
	TextParser(sortGeometriesTmp.utf8_str()).ParseInt(&sortGeometries);

	wxString antiAliasingMethodTmp(antiAliasingMethodStringList[0]);
	REGISTER_PARAM_GUI_COMBOBOX(antiAliasingMethodParamID, wxT("antiAliasingMethod"), antiAliasingMethodTmp, antiAliasingMethodStringList, wxT("Anti aliasing method")); //Reserve place for the combo box, values will be corrected later
	antiAliasingMethod=(RenderTarget::AntiAliasingType)FindBestMatch(antiAliasingMethodStringList, antiAliasingMethodTmp);
	wxString antiAliasingColorSampleCountTmp(wxT("4"));
	REGISTER_PARAM_GUI_TEXTCONTROL(antiAliasingColorSampleCountParamID, wxT("antiAliasingColorSampleCount"), antiAliasingColorSampleCountTmp, wxT("Anti aliasing color sample count"));
	TextParser(antiAliasingColorSampleCountTmp.utf8_str()).ParseInt(&antiAliasingColorSampleCount);
	wxString antiAliasingCoverageSampleCountTmp(wxT("4"));
	REGISTER_PARAM_GUI_TEXTCONTROL(antiAliasingCoverageSampleCountParamID, wxT("antiAliasingCoverageSampleCount"), antiAliasingCoverageSampleCountTmp, wxT("Anti aliasing coverage sample count"));
	TextParser(antiAliasingCoverageSampleCountTmp.utf8_str()).ParseInt(&antiAliasingCoverageSampleCount);
	alphaToCoverage = false;
	REGISTER_PARAM_GUI_CHECKBOX(alphaToCoverageParamID, wxT("alphaToCoverage"), alphaToCoverage, wxT("Anti alias alpha tested edges."));

	initialized = vboSupported = shaderSupported = sceneInitialized = resetMaterial = resetGeometry = vboRenderUpdated = antiAliasingGUIInitialized = false;
	lastMaterialID = (MATERIAL_ID)-2;
	texturesUsedLast = 0;
	enabledLightCount = 1;
	maximumAnisotropy = 1.0f;
	rerender = false;
	viewDependentCubemapEnabled = false;
	fboSupported = false;
	nvMultisampleCoverageSupported = false;
	multisampleSupported = false;
	openGLDetailsDataLogged = false;
	dualDVI30BitColorsProgramInitialized = false;


	dualDVI30BitColors = false;
	REGISTER_PARAM_GUI_CHECKBOX(dualDVI30BitColorsParamID, wxT("dualDVI30BitColors"), dualDVI30BitColors, wxT("Enables support for 30 bit color on dual DVI devices which support this (Sony 4K)."));
	showStatsString = false;
	REGISTER_PARAM_GUI_CHECKBOX(showStatsStringParamID, wxT("showStats"), showStatsString, wxT("Show frame statistics as text overlay."));
	REGISTER_PARAM_GUI_TEXTCONTROL(messageStringParamID, wxT("messageString"), messageString, wxT("Message to be shown as text overlay"));
	wireframe = false;
	REGISTER_PARAM_GUI_CHECKBOX(wireframeParamID, wxT("wireframe"), wireframe, wxT("Display scene in wireframe."));
	useVBO = false;
	REGISTER_PARAM_GUI_CHECKBOX(useVBOParamID, wxT("useVBO"), useVBO, wxT("Use VBO instead of display lists."));
	backFaceCull = false;
	REGISTER_PARAM_GUI_CHECKBOX(backFaceCullParamID, wxT("backFaceCull"), backFaceCull, wxT("Enable back face culling."));
	texturing = true;
	REGISTER_PARAM_GUI_CHECKBOX(texturingParamID, wxT("texturing"), texturing, wxT("Enable texturing."));
	drawBVH = false;
	REGISTER_PARAM_GUI_CHECKBOX(drawBVHParamID, wxT("drawBVH"), drawBVH, wxT("Draw BVH into the model"));
	runRenderLoop = false;
	REGISTER_PARAM_GUI_CHECKBOX(runRenderLoopParamID, wxT("runRenderLoop"), runRenderLoop, wxT("Loop when drawing"));
	if (runRenderLoop)
	{
		eventTimeout=0;
	}
	clearColorBuffer=true;
	REGISTER_PARAM_GUI_CHECKBOX(clearColorBufferParamID, wxT("clearColorBuffer"), clearColorBuffer, wxT("Clear render buffer on drawing (disabling can little speedup, but you must be sure that you will repaint all pixels)."));
	cullingMethodStringList.push_back(wxT("VFC_VFC"));
	cullingMethodStringList.push_back(wxT("VFC_ALL"));
	cullingMethodStringList.push_back(wxT("ALL_VFC"));
	cullingMethodStringList.push_back(wxT("ALL_ALL"));
	wxString cullingMethodTmp(cullingMethodStringList[0]);
	REGISTER_PARAM_GUI_COMBOBOX(cullingMethodParamID, wxT("cullingMethod"), cullingMethodTmp, cullingMethodStringList, wxT("Culing method (used as workaround in some cases, first part is for standard rendering, second is for mirrors rendering)"));
	cullingMethod=(int)FindBestMatch(cullingMethodStringList, cullingMethodTmp);

	vSyncEnabled = true;
	useVSync = false;
	REGISTER_PARAM_GUI_CHECKBOX(useVSyncParamID, wxT("useVSync"), useVSync, wxT("Use V-sync"));

	// openVR support for renderGL
	openVR = useHiddenArea = false;
	REGISTER_PARAM_GUI_CHECKBOX(openVRParamID, wxT("openVR"), openVR, wxT("The library OpenVR is used for rendering output.\nThe known supported devices are HTC Vive and Oculus rift.\nIt uses double FBO for stereo rendering."));
	wxString sceneUnitsTmp(wxT("1000"));
	REGISTER_PARAM_GUI_TEXTCONTROL(sceneUnitsParamID, wxT("sceneUnits"), sceneUnitsTmp, wxT("Ratio to meters for OpenVR library, so for mm it must be 1000.\n(The OpenVR library use meters.)"));
	TextParser(sceneUnitsTmp.utf8_str()).ParseFloat(&sceneUnits);
	REGISTER_PARAM_GUI_CHECKBOX(useHiddenAreaParamID, wxT("useHiddenArea"), useHiddenArea, wxT("Render triangles on near plane distance over hidden area for OpenVR.\nThe hidden area contain pixels that are not visible becuase of HW lenses distortion."));

	renderingRootID = NODE_ID_NONE;
	REGISTER_PARAM_GUI_NODEID(renderingRootIDParamID, wxT("renderingRootID"), renderingRootID, sceneID, 511, wxT("NODE_ID_NONE or not existing nodeID - render whole scene\nExisting nodeID - render subtree"));

	// if true supports HUD rendering in large scenes where numerical precision issues occur
	// by rendering the current camera subtree in a separate pass
	// currently only blended geometry rendering is supported in the camera subtree
	mHudSupport = false;
	REGISTER_PARAM_GUI_CHECKBOX(mHudSupportParamID, wxT("hudSupport"), mHudSupport, wxT("Support HUD rendering in a separate pass to resolve improve numerical precision issues in large scenes."));

	cubeMapGenerationViewMatrix[0] = Matrix::RotationY((float)(-M_PI / 2.0)); //Positive X
	cubeMapGenerationViewMatrix[1] = Matrix::RotationY((float)(M_PI / 2.0)); //Negative X
	cubeMapGenerationViewMatrix[2] = Matrix::RotationX((float)(M_PI / 2.0)); //Positive Y
	cubeMapGenerationViewMatrix[3] = Matrix::RotationX((float)(-M_PI / 2.0)); //Negative Y
	cubeMapGenerationViewMatrix[4] = Matrix::RotationY((float)M_PI); //Positive Z
	cubeMapGenerationViewMatrix[5] = Matrix(); //Negative Z

	sceneShadow=NULL;

	///Listen to scene changes
	REGISTER_LISTENER(Event::EVT_SCENE_MATERIAL_ADDED);
	REGISTER_LISTENER(Event::EVT_SCENE_MATERIAL_REMOVED);
	REGISTER_LISTENER(Event::EVT_SCENE_GEOMETRY_ADDED);
	REGISTER_LISTENER(Event::EVT_SCENE_GEOMETRY_REMOVED);
	REGISTER_LISTENER(Event::EVT_SCENE_IMAGE_ADDED);
	REGISTER_LISTENER(Event::EVT_SCENE_NODE_TRANSFORMED);
	REGISTER_LISTENER(Event::EVT_SCENE_NODE_INSERTED);
	REGISTER_LISTENER(Event::EVT_SCENE_NODE_REMOVED);
	REGISTER_LISTENER(Event::EVT_SCENE_NODE_MOVED);
	REGISTER_LISTENER(Event::EVT_SCENE_NODE_INDEX_CHANGED);
	REGISTER_LISTENER(Event::EVT_SCENE_NODE_GEOMETRY_CHANGED);
	REGISTER_LISTENER(Event::EVT_SCENE_NODE_MATERIAL_CHANGED);
	REGISTER_LISTENER(Event::EVT_SCENE_NODE_LIGHT_CHANGED);
	REGISTER_LISTENER(Event::EVT_SCENE_CAM_PROJECTION_CHANGED);
	REGISTER_LISTENER(Event::EVT_RENDER_UPDATE);
	REGISTER_LISTENER(Event::EVT_RENDER_SYNCHRONIZATION_REGISTER);
	REGISTER_LISTENER(Event::EVT_RENDER_SYNCHRONIZATION_UNREGISTER);
	REGISTER_LISTENER(Event::EVT_RENDER_SYNCHRONIZATION_BARRIER_RELEASE);
	REGISTER_LISTENER(Event::EVT_SCENE_NODE_ACTIVE_CHANGED);
	REGISTER_LISTENER(Event::EVT_SCENE_NODE_SWITCH_ACTIVECHILD_CHANGED);
	REGISTER_LISTENER(Event::EVT_SCENE_CLIPPING_PLANE_ADDED);
}

RenderGlModule::~RenderGlModule()
{
	KERNEL->renderManager->DestroyGLWindow(this);
	Deinitialize();
	SAFE_DELETE(sceneShadow);
}

void RenderGlModule::clearCachedGeometry()
{
	for (auto &dlGID : dispListGeomIDs)
	{
		if (dlGID != GL_ID_NONE)
		{
			glDeleteLists(dlGID, 1);
			dlGID = GL_ID_NONE;
		}
	}
	dispListGeomIDs.clear();
	for (auto &dlMID : dispListMatIDs)
	{
		if (dlMID != GL_ID_NONE)
		{
			glDeleteLists(dlMID, 1);
			dlMID = GL_ID_NONE;
		}
	}
	dispListMatIDs.clear();
	for (auto &vboid : VBOIDs)
		vboid.DeleteBuffers();
	VBOIDs.clear();

	for (auto &texture : texture2DIDMap)
	{
		if (texture.second.object != GL_ID_NONE)
			glDeleteTextures(1, &(texture.second.object));
	}
	texture2DIDMap.clear();
	for (auto &texture : textureCubeIDMap)
	{
		if (texture.second.object != GL_ID_NONE)
			glDeleteTextures(1, &(texture.second.object));
	}
	textureCubeIDMap.clear();

	for (auto &shader : shaderIDMap)
	{
		ShaderHandler2 &handler = shader.second;
		if (handler.program != GL_ID_NONE)
			glDeleteObjectARB(handler.program);
		if (handler.fragmentShader != GL_ID_NONE)
			glDeleteObjectARB(handler.fragmentShader);
		if (handler.vertexShader != GL_ID_NONE)
			glDeleteObjectARB(handler.vertexShader);
		if (handler.geometryShader != GL_ID_NONE)
			glDeleteObjectARB(handler.geometryShader);
		// Delete textures, since they are not handled by TextureIDMap
		for (auto &param : handler.paramList)
		{
			if (param.textureID != GL_ID_NONE)
				glDeleteTextures(1, &param.textureID);
		}
	}
	shaderIDMap.clear();

	fontFactory.DestroyAllFonts();

	sceneInitialized = false;
}

void RenderGlModule::checkExtSupport()
{
	if (GLEW_ARB_vertex_buffer_object)
		vboSupported = true;
	else
	{
		useVBO = vboSupported = false;
		LOGWARNING(wxT("<RenderGlModule>VBO not supported, switching to display lists"));
	}

	if (GLEW_ARB_shader_objects && GLEW_ARB_shading_language_100 && GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader)
		shaderSupported = true;
	else
	{
		shaderSupported = false;
		LOGWARNING(wxT("<RenderGlModule>Shaders not supported"));
	}

	if (!GLEW_ARB_multitexture)
		LOGWARNING(wxT("<RenderGlModule>Multiple textures not supported"));

	if (GLEW_EXT_texture_filter_anisotropic) {
		glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maximumAnisotropy);
		LOG(wxString::Format(wxT("<RenderGlModule>Maximum anisotropy %f"), maximumAnisotropy));
	}

	if (GLEW_EXT_framebuffer_object)
	{
		fboSupported = true;
	}
	else
	{
		fboSupported = false;
		LOGWARNING(wxT("<RenderGlModule>Framebuffer objects not supported. Prepare for some OpenGL dark ages."));
		SET_PARAM(drawUsingFBOParamID, wxT("0"));
		drawUsingFBO = false;
	}

	if (GLEW_EXT_framebuffer_multisample)
	{
		multisampleSupported = true;
		//antiAliasingMethodStringList.push_back(wxT("Multi sampling"));
	}
	else
	{
		LOGWARNING(wxT("<RenderGlModule>Not even the Multi Sampling is supported. Stop living in the past."));
		multisampleSupported = false;
		antiAliasingMethodStringList.Remove(wxT("Multi sampling"));
	}

	if (glewIsExtensionSupported("GL_NV_framebuffer_multisample_coverage"))
	{
		nvMultisampleCoverageSupported = true;
		//antiAliasingMethodStringList.push_back(wxT("Coverage sampling"));
	}
	else
	{
		LOGWARNING(wxT("<RenderGlModule>No GL_NV_framebuffer_multisample_coverage extension. CSAA anti aliasing will not be available."));
		nvMultisampleCoverageSupported = false;
		if (antiAliasingMethodStringList.Index(wxT("Coverage sampling"))!=wxNOT_FOUND)
			antiAliasingMethodStringList.Remove(wxT("Coverage sampling"));
	}

	wxString oglVendor(wxString::FromAscii((char*)glGetString(GL_VENDOR)));
	if (oglVendor.Find(wxT("ATI")) != wxNOT_FOUND || oglVendor.Find(wxT("AMD")) != wxNOT_FOUND)
		openGLVendor = AMD;
}

void RenderGlModule::enableAASampleTextControl(RenderTarget::AntiAliasingType aaType)
{
	switch (aaType)
	{
	case RenderTarget::NONE:
		ENABLE_PARAM_GUI_CONTROL(antiAliasingColorSampleCountParamID, false);
		ENABLE_PARAM_GUI_CONTROL(antiAliasingCoverageSampleCountParamID, false);
		ENABLE_PARAM_GUI_CONTROL(alphaToCoverageParamID, false);
		break;
	case RenderTarget::MULTI_SAMPLING:
		ENABLE_PARAM_GUI_CONTROL(antiAliasingColorSampleCountParamID, true);
		ENABLE_PARAM_GUI_CONTROL(antiAliasingCoverageSampleCountParamID, false);
		ENABLE_PARAM_GUI_CONTROL(alphaToCoverageParamID, true);
		break;
	case RenderTarget::COVERAGE_SAMPLING:
		ENABLE_PARAM_GUI_CONTROL(antiAliasingColorSampleCountParamID, true);
		ENABLE_PARAM_GUI_CONTROL(antiAliasingCoverageSampleCountParamID, true);
		ENABLE_PARAM_GUI_CONTROL(alphaToCoverageParamID, true);
		break;
	};
}

void RenderGlModule::setupAAGUI()
{
	if (!antiAliasingGUIInitialized)
	{
		wxString antiAliasingMethodTmp(antiAliasingMethodStringList[antiAliasingMethod]);
		REGISTER_PARAM_GUI_COMBOBOX(antiAliasingMethodParamID, wxT("antiAliasingMethod"), antiAliasingMethodTmp, antiAliasingMethodStringList, wxT("Anti aliasing method")); //Reserve place for the combo box, values will be corrected later
		antiAliasingMethod=(RenderTarget::AntiAliasingType)FindBestMatch(antiAliasingMethodStringList, antiAliasingMethodTmp);

		enableAASampleTextControl(antiAliasingMethod);

		if (!multisampleSupported)
		{
			ENABLE_PARAM_GUI_CONTROL(antiAliasingColorSampleCountParamID, false);
			ENABLE_PARAM_GUI_CONTROL(alphaToCoverageParamID, false);
		}
		if (!nvMultisampleCoverageSupported)
			ENABLE_PARAM_GUI_CONTROL(antiAliasingCoverageSampleCountParamID, false);
		antiAliasingGUIInitialized = true;
	}
}

void RenderGlModule::setDefaultShaderParams(std::vector<ShaderParam>& shaderParams)
{
	shaderParams[ShaderProgram::LIGHT_COUNT].iVal = enabledLightCount;
}

void RenderGlModule::drawShader(const smart_pointer<Material> & material)
{
	if (!shaderSupported)
		return;

	if (material && material->shader && (material->shader->type==ShaderProgram::LANG_GLSL))
	{
		setDefaultShaderParams(material->shader->paramList);	// TODO Is it true that this module now changes data of a scene material without any locks and without telling anyone?

		ShaderIDMap::iterator shdIt = shaderIDMap.find(material->name);
		if (shdIt == shaderIDMap.end())
		{
			ShaderHandler2 handler;
			auto res = shaderIDMap.insert(ShaderIDMap::value_type(material->name, handler));
			if (!res.second)
				LOGERROR(wxT("<RenderGlModule>Failed to insert shader handler '")+material->name+wxT("' to hashmap"));
			else
				shdIt = shaderIDMap.find(material->name);
		}
		ShaderHandler2 &handler = shdIt->second;

		bool recreate_shader_program = false;

		// Check vertex shader: delete it if it does not exist, recompile it if necessary
		if (material->shader->vsSource.IsEmpty())
		{
			if (handler.vertexShader != GL_ID_NONE)
			{
				glDeleteObjectARB(handler.vertexShader);
				handler.vertexShader = GL_ID_NONE;
				handler.vertexShaderSource.Clear();
				recreate_shader_program = true;
			}
		}
		else
		{
			if (handler.vertexShaderSource != material->shader->vsSource)
			{
				// Recompile shader
				if (handler.vertexShader != GL_ID_NONE)
					glDeleteObjectARB(handler.vertexShader);
				handler.vertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
				wxCharBuffer buff = material->shader->vsSource.ToAscii();
				const char * source = buff.data();
				glShaderSourceARB(handler.vertexShader, 1, (const GLcharARB **)(&source), NULL);
				glCompileShaderARB(handler.vertexShader);
				handler.vertexShaderSource = material->shader->vsSource;
				recreate_shader_program = true;
			}
		}

		// Check fragment shader: delete it if it does not exist, recompile it if necessary
		if (material->shader->fsSource.IsEmpty())
		{
			if (handler.fragmentShader != GL_ID_NONE)
			{
				glDeleteObjectARB(handler.fragmentShader);
				handler.fragmentShader = GL_ID_NONE;
				handler.fragmentShaderSource.Clear();
				recreate_shader_program = true;
			}
		}
		else
		{
			if (handler.fragmentShaderSource != material->shader->fsSource)
			{
				// Recompile shader
				if (handler.fragmentShader != GL_ID_NONE)
					glDeleteObjectARB(handler.fragmentShader);
				handler.fragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
				wxCharBuffer buff = material->shader->fsSource.ToAscii();
				const char * source = buff.data();
				glShaderSourceARB(handler.fragmentShader, 1, (const GLcharARB **)(&source), NULL);
				glCompileShaderARB(handler.fragmentShader);
				handler.fragmentShaderSource = material->shader->fsSource;
				recreate_shader_program = true;
			}
		}

		// Check geometry shader: delete it if it does not exist, recompile it if necessary
		if (material->shader->gsSource.IsEmpty())
		{
			if (handler.geometryShader != GL_ID_NONE)
			{
				glDeleteObjectARB(handler.geometryShader);
				handler.geometryShader = GL_ID_NONE;
				handler.geometryShaderSource.Clear();
				recreate_shader_program = true;
			}
		}
		else
		{
			if (handler.geometryShaderSource != material->shader->gsSource)
			{
				// Recompile shader
				if (handler.geometryShader != GL_ID_NONE)
					glDeleteObjectARB(handler.geometryShader);
				handler.geometryShader = glCreateShaderObjectARB(GL_GEOMETRY_SHADER_ARB);
				wxCharBuffer buff = material->shader->gsSource.ToAscii();
				const char * source = buff.data();
				glShaderSourceARB(handler.geometryShader, 1, (const GLcharARB **)(&source), NULL);
				glCompileShaderARB(handler.geometryShader);
				handler.geometryShaderSource = material->shader->gsSource;
				recreate_shader_program = true;
			}
		}

		if (recreate_shader_program)
		{
			if (handler.program != GL_ID_NONE)
				glDeleteObjectARB(handler.program);
			handler.program = GL_ID_NONE;
			handler.paramList.clear();

			// Create shader program only if there is a shader
			if ((handler.vertexShader != GL_ID_NONE) || (handler.geometryShader != GL_ID_NONE) || (handler.fragmentShader != GL_ID_NONE))
			{
				handler.program = glCreateProgramObjectARB();
		
				// Attach shaders and set parameters
				if (handler.vertexShader != GL_ID_NONE)
				{
					glAttachObjectARB(handler.program, handler.vertexShader);
				}
				if (handler.geometryShader != GL_ID_NONE)
				{
					glAttachObjectARB(handler.program, handler.geometryShader);

					// Geometry shader works only for purpose of splatting otherwise there has to be some
					// extension of shaderprogram made, to decide how to set input/output of GS
					glProgramParameteriARB(handler.program, GL_GEOMETRY_VERTICES_OUT_EXT, 60);
					glProgramParameteriARB(handler.program, GL_GEOMETRY_INPUT_TYPE_EXT, GL_POINTS);
					glProgramParameteriARB(handler.program, GL_GEOMETRY_OUTPUT_TYPE_EXT, GL_TRIANGLE_STRIP);
				}
				if (handler.fragmentShader != GL_ID_NONE)
				{
					glAttachObjectARB(handler.program, handler.fragmentShader);
				}

				// Link and check for errors
				glLinkProgramARB(handler.program);
				glUseProgramObjectARB(handler.program);

				///Info message
				int infoLogLength = 0;
				glGetObjectParameterivARB(handler.program, GL_OBJECT_INFO_LOG_LENGTH_ARB, &infoLogLength);
				if (infoLogLength > 1)		// More than '\0'
				{
					std::unique_ptr<char []> buffer(new char[infoLogLength]);
					glGetInfoLogARB(handler.program, infoLogLength, 0, buffer.get());
					wxString info(buffer.get(), wxConvUTF8);
					buffer.reset();
					LOG(wxT("<RenderGlModule>Shader program message (") + material->shader->fsFileName + wxT(", ") +
						material->shader->vsFileName + wxT(", ") + material->shader->gsFileName + wxT("):\n") + info);
				}

				///Cache param locations
				for (auto &par : material->shader->paramList)
				{
					ShaderParamID param(glGetUniformLocationARB(handler.program, par.name.ToAscii()), GL_ID_NONE);
					handler.paramList.push_back(param);
				}
			}
		}

		// Set shader program and its parameters
		//TODO: testing
		glUseProgramObjectARB(shdIt->second.program);

		for (size_t texUsed = 0; texUsed < texturesUsedLast; texUsed++)
		{
			glActiveTexture(GLenum(GL_TEXTURE0 + texUsed));
			glBindTexture(GL_TEXTURE_2D, 0);
			glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
		}
		texturesUsedLast = 0;
		GLuint samplerID = 0;
		for (size_t pID = 0; pID < shdIt->second.paramList.size(); pID++)
		{
			const ShaderParam * param = &material->shader->paramList[pID];
			const ShaderParamID * paramID = &shdIt->second.paramList[pID];
			if (paramID->paramLocation != (GLuint)-1)
			{
				switch (param->type)
				{
				case ShaderParam::BOOL:
					glUniform1iARB(paramID->paramLocation, param->iVal);
					break;
				case ShaderParam::INT:
					glUniform1iARB(paramID->paramLocation, param->iVal);
					break;
				case ShaderParam::FLOAT1:
					glUniform1fvARB(paramID->paramLocation, 1, param->fVals);
					break;
				case ShaderParam::FLOAT2:
					glUniform2fvARB(paramID->paramLocation, 1, param->fVals);
					break;
				case ShaderParam::FLOAT3:
					glUniform3fvARB(paramID->paramLocation, 1, param->fVals);
					break;
				case ShaderParam::FLOAT4:
					glUniform4fvARB(paramID->paramLocation, 1, param->fVals);
					break;
				case ShaderParam::SAMPLER:
				case ShaderParam::SAMPLERCUBE:
				case ShaderParam::SAMPLER3D:
					if (!param->imageName.IsEmpty())
					{
						GLenum texType = GL_TEXTURE_2D;
						Image::TEXTURE_SAMPLER_TYPE imTexType = Image::TEX2D;
						if (param->type == ShaderParam::SAMPLERCUBE)
						{
							texType = GL_TEXTURE_CUBE_MAP;
							imTexType = Image::TEXCUBE;
						}
						else if (param->type == ShaderParam::SAMPLER3D)
						{
							texType = GL_TEXTURE_3D;
							imTexType = Image::TEXTURE3D;
						}
						GLuint texID = getTextureID(param->imageName, imTexType, true, true);
						glActiveTexture(GL_TEXTURE0 + samplerID);
						glBindTexture(texType, texID);
						glUniform1iARB(paramID->paramLocation, samplerID);
						samplerID++;
						texturesUsedLast++;
					}
					break;
				}
			}
		}
	}
	else
		glUseProgramObjectARB(0);
	GLCHECKERROR(wxT("<RenderGlModule::drawShader>"));
}


void RenderGlModule::drawLights(const std::vector<LightInfo> &lights)
{
	unsigned lID = 0;
	enabledLightCount = 1;
	for (std::vector<LightInfo>::const_iterator lightIt = lights.begin(); (lID < 7) && (lightIt != lights.end()); ++lightIt)
	{
		const Light & light = (*lightIt).lightNode->GetLight();

		Vector4 lightColor(light.diffuse.x*light.diffuse.w, light.diffuse.y*light.diffuse.w, light.diffuse.z*light.diffuse.w, 1.0f);
		glLightfv(LIGHTID[lID], GL_DIFFUSE, lightColor._v);
		glLightfv(LIGHTID[lID], GL_SPECULAR, lightColor._v);
		++enabledLightCount;

		glLoadMatrixf(((*lightIt).matrix*camMatInverse)._m);
		lastMatrix=(*lightIt).matrix;
		if (light.type == Light::OMNI)
		{
			GLfloat lpos[4] = {0, 0, 0, 1 };
			glLightfv(LIGHTID[lID], GL_POSITION, lpos);
			glLightf(LIGHTID[lID], GL_SPOT_EXPONENT, 0.0f);
			glLightf(LIGHTID[lID], GL_SPOT_CUTOFF, 180.0f);
			glEnable(LIGHTID[lID++]);
		}
		else if (light.type == Light::DIRECTIONAL)
		{
			GLfloat ldir[4] = { 0,0,1, 0 };
			glLightfv(LIGHTID[lID], GL_POSITION, ldir);
			glLightf(LIGHTID[lID], GL_SPOT_EXPONENT, 0.0f);
			glLightf(LIGHTID[lID], GL_SPOT_CUTOFF, 180.0f);
			glEnable(LIGHTID[lID++]);
		}
		else if (light.type == Light::SPOTLIGHT)
		{
			GLfloat lpos[4] = {0, 0, 0, 1 };
			glLightfv(LIGHTID[lID], GL_POSITION, lpos);
			GLfloat ldir[4] = { 0,0,-1, 0 };
			glLightfv(LIGHTID[lID], GL_SPOT_DIRECTION, ldir);
			glLightf(LIGHTID[lID], GL_SPOT_EXPONENT, light.exponent);
			glLightf(LIGHTID[lID], GL_SPOT_CUTOFF, light.angle);
			glEnable(LIGHTID[lID++]);
		}
		else
			LOGWARNING(wxT("<RenderGlModule>Light node '")+(*lightIt).lightNode->GetName()+wxT("' has invalid light type"));
	}
	for (; lID<7; lID++)
		glDisable(LIGHTID[lID]);
}

void RenderGlModule::enableClippingPlanes()
{
	std::vector<ClippingPlane> clippingPlanes;
	scene->GetClippingPlanes(clippingPlanes);
	enabledClippingPlanesCount=0;
	for (auto &clippingPlane : clippingPlanes)
	{
		if (!clippingPlane.enabled)
			continue;

		++enabledClippingPlanesCount;
		GLdouble clipppingPlaneGL[4] = {clippingPlane.plane.x, clippingPlane.plane.y, clippingPlane.plane.z, clippingPlane.plane.w};
		glClipPlane(GL_CLIP_PLANE0+enabledClippingPlanesCount, clipppingPlaneGL);
		glEnable(GL_CLIP_PLANE0+enabledClippingPlanesCount);
		if (enabledClippingPlanesCount>=maxClippingPlanes-1)
			return;
	}
}

void RenderGlModule::disableClippingPlanes()
{
	for (int i = 0; i < enabledClippingPlanesCount; ++i)
		glDisable(GL_CLIP_PLANE1+i);
}

void RenderGlModule::drawBackground()
{
	/*
	Foreword:
	Every cube map has to be specifically designed for the loaded scene. There is no
	algorithmic way to make every cube map correctly oriented for every scene. For
	example the Krizovatka scene has the earth ground in the -Z direction, so the
	environment cube map also has to have the ground on the -Z cube face. On the other hand,
	some native OpenGL scene will have the ground in the -Y direction, so the cube map
	has to have the ground also on the -Y face. I recommend the app CubeMapGen to edit
	the cube maps.
	*/
	if (backgroundItemsList.empty())
		return;

	// disables writing to the depth buffer
	glDepthMask(GL_FALSE);

	/*
	Excerpt from OGL glDepthRange man page:
	After	clipping and division by w, depth coordinates range
	from -1 to 1,	corresponding to the near and far clipping
	planes.  glDepthRange	specifies a linear mapping of the
	normalized depth coordinates in this range to window depth
	coordinates.

	Our reasoning:
	We want the background to have the largest depth value as possible,
	so we set the mapping to utilize only the largest depth values.
	With this, we can draw the background last. This saves us
	some overdraw.
	*/
	glDepthRange(1, 1);

	// Set the texture
	drawDispListMaterial(backgroundItemsList[0].node);

	// We use the same "hack" as in rendergl3: we compute texture coordinates by ourselves,
	// especially when using cubemaps.
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	// We must disable automatic generation of texture coordinates, some materials enables it.
	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T);
	glDisable(GL_TEXTURE_GEN_R);

	Vector4 screen_coords[4] = {
		Vector4(-1.0f, -1.0f, 0.0f, 1.0f),		// Left bottom
		Vector4( 1.0f, -1.0f, 0.0f, 1.0f),		// Right bottom
		Vector4( 1.0f,  1.0f, 0.0f, 1.0f),		// Right top
		Vector4(-1.0f,  1.0f, 0.0f, 1.0f),		// Left top
	};
	Vector4 tex_coords[4];

	if (backgroundItemsList[0].node->GetNodeProperties() & SceneNodeShadow::TEX_CUBE_MAP)
	{
		Matrix model = backgroundItemsList[0].matrix;
		Matrix world = clonedCameraScene->GetWorldTransMatrix(clonedCamera->GetID());
		Matrix projection = getProjectionMatrix(clonedCameraScene, clonedCamera, cameraLeft);
		Matrix matrix = projection.Inverse() * world.ExtractRotation() * (model.ExtractRotation().Transpose());
		tex_coords[0] = matrix.TransformCoord(screen_coords[0]);
		tex_coords[1] = matrix.TransformCoord(screen_coords[1]);
		tex_coords[2] = matrix.TransformCoord(screen_coords[2]);
		tex_coords[3] = matrix.TransformCoord(screen_coords[3]);

		// Force clamping of texture coordinates, to avoid some artefacts
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
	}
	else
	{
		tex_coords[0] = Vector4(0.0f, 0.0f, 0.0f, 1.0f);
		tex_coords[1] = Vector4(1.0f, 0.0f, 0.0f, 1.0f);
		tex_coords[2] = Vector4(1.0f, 1.0f, 0.0f, 1.0f);
		tex_coords[3] = Vector4(0.0f, 1.0f, 0.0f, 1.0f);

		// Force clamping of texture coordinates, to avoid some artefacts
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	}

	glBegin(GL_QUADS);
		glTexCoord3fv(tex_coords[0]._v);		glVertex2fv(screen_coords[0]._v);
		glTexCoord3fv(tex_coords[1]._v);		glVertex2fv(screen_coords[1]._v);
		glTexCoord3fv(tex_coords[2]._v);		glVertex2fv(screen_coords[2]._v);
		glTexCoord3fv(tex_coords[3]._v);		glVertex2fv(screen_coords[3]._v);
	glEnd();

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
		
	glDepthMask(GL_TRUE);
	glDepthRange(0, 1);
}

void RenderGlModule::drawMaterial(const smart_pointer<Material> & material)
{
	bool tex = false;

	if (material)
	{
		unsigned int texFlags = material->texFlags;
		if (lighting && ((material->flags & Material::UNLIT) == 0))
		{
			if (material->reflectivity == 0.0f)
			{
				glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material->diffuse._v);
				glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material->ambient._v);
				glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, material->emission._v);
				glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material->specular._v);
			}
			else if (material->reflectivity > 0.0f)
			{
				Material reflectionBlendMaterial(*material);
				reflectionBlendMaterial.diffuse._v[3] *= 1.0 - material->reflectivity;
				reflectionBlendMaterial.ambient._v[3] *= 1.0 - material->reflectivity;
				reflectionBlendMaterial.emission._v[3] *= 1.0 - material->reflectivity;
				reflectionBlendMaterial.specular._v[3] *= 1.0 - material->reflectivity;
				glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, reflectionBlendMaterial.diffuse._v);
				glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, reflectionBlendMaterial.ambient._v);
				glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, reflectionBlendMaterial.emission._v);
				glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, reflectionBlendMaterial.specular._v);
			}
			glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, __max(__min(material->shininess, 128), 0));//TODO: check shininess value correctness at this place instead of in IO modules?
		}
		else
			glColor4fv(material->diffuse._v);

		if (material->reflectivity > 0.0f)
		{
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		}

		if (material->flags & Material::BLENDED)
		{
			if (material->blendSrc != GL_ID_NONE && material->blendDst != GL_ID_NONE)
				//TODO: default blendfunc if not defined?
				glBlendFunc(material->blendSrc, material->blendDst);
		}

		if (!material->imageName.IsEmpty() && texturing)
		{
			// Do not create the texture if it is not already loaded, so that display lists are without this creation
			GLuint textureID = getTextureID(material->imageName, (texFlags & Material::TEX_CUBE_MAP)?Image::TEXCUBE:Image::TEX2D,
				FLAG2B(texFlags, Material::TEX_MIN_FILTER_MIPMAP, Material::TEX_MIN_FILTER_MASK), true);
			tex = true;
			if (glActiveTexture != NULL)
			{
				for (size_t texUsed = 1; texUsed < texturesUsedLast; texUsed++)
				{
					glActiveTexture(GLenum(GL_TEXTURE0 + texUsed));
					glBindTexture(GL_TEXTURE_2D, 0);
					glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
				}
				texturesUsedLast = 1;
				glActiveTexture(GL_TEXTURE0);
			}
			int texTarget = (texFlags & Material::TEX_CUBE_MAP) ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
			glBindTexture(texTarget, textureID);
			if (texFlags & Material::TEX_GEN_ENABLED)
			{
				glEnable(GL_TEXTURE_GEN_S);
				glEnable(GL_TEXTURE_GEN_T);
				glEnable(GL_TEXTURE_GEN_R);

				if (texFlags & Material::TEX_GEN_FUNC_U_EYE)
				{
					glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
					glTexGenfv(GL_S, GL_OBJECT_PLANE, material->texGenUPlane._v);
				}
				if (texFlags & Material::TEX_GEN_FUNC_U_OBJECT)
				{
					glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
					glTexGenfv(GL_S, GL_OBJECT_PLANE, material->texGenUPlane._v);
				}
				if (texFlags & Material::TEX_GEN_FUNC_U_SPHERE)
				{
					glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
				}
				if (texFlags & Material::TEX_GEN_FUNC_V_EYE)
				{
					glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
					glTexGenfv(GL_T, GL_OBJECT_PLANE, material->texGenVPlane._v);
				}
				if (texFlags & Material::TEX_GEN_FUNC_V_OBJECT)
				{
					glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
					glTexGenfv(GL_T, GL_OBJECT_PLANE, material->texGenVPlane._v);
				}
				if (texFlags & Material::TEX_GEN_FUNC_V_SPHERE)
				{
					glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
				}
				if (texFlags & Material::TEX_GEN_FUNC_V_OBJECT)
				{
					// TODO Why?
					glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
					glTexGenfv(GL_R, GL_OBJECT_PLANE, Vector4(0,0,1,0)._v);
				}
				if (texFlags & Material::TEX_GEN_NORMAL)
				{
					glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_ARB);
					glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_ARB);
					glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_ARB);
				}
				if (texFlags & Material::TEX_GEN_REFLECTION)
				{
					glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB);
					glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB);
					glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB);
				}
			}
			else
			{
				glDisable(GL_TEXTURE_GEN_S);
				glDisable(GL_TEXTURE_GEN_T);
				glDisable(GL_TEXTURE_GEN_R);
			}

			if (FLAG2B(texFlags, Material::TEX_ENV_REPLACE, Material::TEX_ENV_MASK))
				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
			else if (FLAG2B(texFlags, Material::TEX_ENV_DECAL, Material::TEX_ENV_MASK))
				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
			else if (FLAG2B(texFlags, Material::TEX_ENV_BLEND, Material::TEX_ENV_MASK))
				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
			else if (FLAG2B(texFlags, Material::TEX_ENV_ADD, Material::TEX_ENV_MASK))
				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
			else if (FLAG2B(texFlags, Material::TEX_ENV_INTERPOLATE, Material::TEX_ENV_MASK))
			{
				GLfloat textureIntensity[] = {material->textureMix , material->textureMix, material->textureMix, 1.0f };
				glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, textureIntensity);
				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
				glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);

				glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PRIMARY_COLOR);
				glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);

				glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE);
				glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);

				glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_RGB, GL_CONSTANT);
				glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_ONE_MINUS_SRC_COLOR);
			}
			else
			{
				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
				if (!FLAG2B(texFlags, Material::TEX_ENV_MODULATE, Material::TEX_ENV_MASK))
					LOGERROR(wxT("<RenderGLModule> Material ") + material->name + wxT(": unknown TEX_ENV flag"));
			}

			//TEXWRAP
			if (FLAG2B(texFlags, Material::TEX_UWRAP_CLAMP, Material::TEX_UWRAP_MASK))
				glTexParameteri(texTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
			else if (FLAG2B(texFlags, Material::TEX_UWRAP_REPEAT, Material::TEX_UWRAP_MASK))
				glTexParameteri(texTarget, GL_TEXTURE_WRAP_S, GL_REPEAT);
			else
			{
				glTexParameteri(texTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
				if (!FLAG2B(texFlags, Material::TEX_UWRAP_BORDER, Material::TEX_UWRAP_MASK))
					LOGERROR(wxT("<RenderGLModule> Material ") + material->name + wxT(": unknown TEX_UWRAP flag"));
			}

			if (FLAG2B(texFlags, Material::TEX_VWRAP_CLAMP, Material::TEX_VWRAP_MASK))
				glTexParameteri(texTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
			else if (FLAG2B(texFlags, Material::TEX_VWRAP_REPEAT, Material::TEX_VWRAP_MASK))
				glTexParameteri(texTarget, GL_TEXTURE_WRAP_T, GL_REPEAT);
			else
			{
				glTexParameteri(texTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
				if (!FLAG2B(texFlags, Material::TEX_VWRAP_BORDER, Material::TEX_VWRAP_MASK))
					LOGERROR(wxT("<RenderGLModule> Material ") + material->name + wxT(": unknown TEX_VWRAP flag"));
			}

			//TEXFILTER
			if (FLAG2B(texFlags, Material::TEX_MIN_FILTER_POINT, Material::TEX_MIN_FILTER_MASK))
				glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			else if (FLAG2B(texFlags, Material::TEX_MIN_FILTER_LINEAR, Material::TEX_MIN_FILTER_MASK))
				glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			else
			{
				glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
				if (!FLAG2B(texFlags, Material::TEX_MIN_FILTER_MIPMAP, Material::TEX_MIN_FILTER_MASK))
					LOGERROR(wxT("<RenderGLModule> Material ") + material->name + wxT(": unknown TEX_MIN_FILTER flag"));

				if (maximumAnisotropy!=1.0)
					glTexParameterf(texTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, maximumAnisotropy);

				// prevent extensive overbluring by setting the texture max lod
				// helps significantly in large scale scenes (vehicle simulator) - should be check in other contexts
				glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 4.0f);
			}

			if (FLAG2B(texFlags, Material::TEX_MAG_FILTER_POINT, Material::TEX_MAG_FILTER_MASK))
				glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			else
			{
				glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				if (!FLAG2B(texFlags, Material::TEX_MAG_FILTER_LINEAR, Material::TEX_MAG_FILTER_MASK))
					LOGERROR(wxT("<RenderGLModule> Material ") + material->name + wxT(": unknown TEX_MAG_FILTER flag"));
			}

			if (texFlags & Material::TEX_CUBE_MAP)
			{
				glEnable(GL_TEXTURE_CUBE_MAP);
				glDisable(GL_TEXTURE_2D);
			}
			else
			{
				glDisable(GL_TEXTURE_CUBE_MAP);
				glEnable(GL_TEXTURE_2D);
			}
		}
	}
	else
	{
		///Missing material, using default
		if (lighting)
		{
			glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Vector4(1,1,1,1)._v);
			glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, Vector4(0,0,0,0)._v);
			glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, Vector4(0,0,0,0)._v);
			glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, Vector4(1,1,1,1)._v);
			glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 1);
		}
		else
			glColor4fv(Vector4(1,1,1,1)._v);
	}

	if (!tex || !texturing)
	{
		glDisable(GL_TEXTURE_CUBE_MAP);
		glDisable(GL_TEXTURE_2D);
	}

	if (lighting && (!material || ((material->flags & Material::UNLIT) == 0)))
	{
		glEnable(GL_LIGHTING);
//		if (material->flags & Material::DOUBLE_SIDED)
		glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
//		else
//			glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
	}
	else
		glDisable(GL_LIGHTING);
	GLCHECKERROR(wxT("<RenderGlModule::drawMaterial>"));
}

void RenderGlModule::drawMaterialGeometryText(const smart_pointer<Material> & material)
{
	// This method sets material for GeometryText. Rendering text is specific in some
	// areas, mainly as it uses its own texture and blending

	if (material)
	{
		if (lighting && ((material->flags & Material::UNLIT) == 0))
		{
			glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material->diffuse._v);
			glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material->ambient._v);
			glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, material->emission._v);
			glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material->specular._v);
			glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, __max(__min(material->shininess, 128), 0));//TODO: check shininess value correctness at this place instead of in IO modules?
		}
		else
		{
			glColor4fv(material->diffuse._v);
		}
	}
	else
	{
		///Missing material, using default
		if (lighting)
		{
			glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Vector4(1,1,1,1)._v);
			glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, Vector4(0,0,0,0)._v);
			glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, Vector4(0,0,0,0)._v);
			glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, Vector4(1,1,1,1)._v);
			glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 1);
		}
		else
			glColor4fv(Vector4(1,1,1,1)._v);
	}

	// GeometryText uses single 2D font texture
	if (glActiveTexture != NULL)
	{
		for (size_t texUsed = 1; texUsed < texturesUsedLast; texUsed++)
		{
			glActiveTexture(GLenum(GL_TEXTURE0 + texUsed));
			glBindTexture(GL_TEXTURE_2D, 0);
			glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
		}
		texturesUsedLast = 1;
		glActiveTexture(GL_TEXTURE0);
	}
	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T);
	glDisable(GL_TEXTURE_GEN_R);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glEnable(GL_TEXTURE_2D);
	glDisable(GL_TEXTURE_CUBE_MAP);

	// GeometryText texture uses blending as antialiasing
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	if (lighting && (!material || ((material->flags & Material::UNLIT) == 0)))
	{
		glEnable(GL_LIGHTING);
//		if (material->flags & Material::DOUBLE_SIDED)
		glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
//		else
//			glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
	}
	else
		glDisable(GL_LIGHTING);

	GLCHECKERROR(wxT("<RenderGlModule::drawMaterialGeometryText>"));
}

GLuint RenderGlModule::getTextureID(const wxString &imageName, Image::TEXTURE_SAMPLER_TYPE buildAsType, bool use_mipmaps, bool buildIfNeeded)
{
	if (imageName.IsEmpty())
		return 0;

	TexObject tex_object;
	tex_object.object = 0;

	//check existence
	if (buildAsType==Image::TEXCUBE)
	{
		TextureIDMap::const_iterator texIt = textureCubeIDMap.find(imageName);
		if (texIt != textureCubeIDMap.end())
		{
			if (use_mipmaps && !texIt->second.has_mipmaps)
				tex_object.object = texIt->second.object;		// Update texture object
			else
				return texIt->second.object;
		}
	}
	else
	{
		TextureIDMap::const_iterator texIt = texture2DIDMap.find(imageName);
		if (texIt != texture2DIDMap.end())
		{
			if (use_mipmaps && !texIt->second.has_mipmaps)
				tex_object.object = texIt->second.object;		// Update texture object
			else
				return texIt->second.object;
		}
	}
	if (!buildIfNeeded)
		return 0;
	//try to build new
	const_smart_pointer<Image> img = KERNEL->sceneManager->GetImage(imageName, false);
	if (!img || !img->IsOk())
	{
		LOGWARNING(wxT("<RenderGlModule> getTextureID: sceneManager returns invalid image for image ") + imageName);
		return 0;
	}

	if (tex_object.object == 0)		glGenTextures(1, &tex_object.object);
	img->UpdateOglTexture(tex_object.object, buildAsType, use_mipmaps);
	tex_object.has_mipmaps = use_mipmaps;
	if (buildAsType==Image::TEXCUBE)
		textureCubeIDMap.insert(TextureIDMap::value_type(imageName, tex_object));
	else
		texture2DIDMap.insert(TextureIDMap::value_type(imageName, tex_object));
	return tex_object.object;
}

void RenderGlModule::drawVBO(const SceneNodeShadow *node)
{
	//drawVBOMaterial(node);
	drawDispListMaterial(node);
	drawVBOGeometry(node);
}

void RenderGlModule::drawVBOGeometry(const SceneNodeShadow *node)
{
	const smart_pointer<Geometry> & nodeGeometry = node->geometry;
	if (!nodeGeometry)
		return;
	switch (nodeGeometry->type)
	{
		//////////////////////////////////////////////////
		/////////// TRIANGLE REPRESENTATION ///////////////
		//////////////////////////////////////////////////
		case Geometry::GEOMETRY_TRIANGLE:
			{
				unsigned id = node->GetGeometryID();
				if (id >= VBOIDs.size())
					return;
				const smart_pointer<GeometryTriangles> & geometry = nodeGeometry;

				if (VBOIDs[id].verticesID == GL_ID_NONE)
				{
					const std::vector<Vector3> * vertices = &geometry->vertices;
					const std::vector<Vector3> * normals = &geometry->normals;
					const std::vector<GeometryTriangles::TexCoord> * texCoords = &geometry->texCoords;

					if (geometry->vertices.empty() || geometry->indices.empty())
						break;

					//compute autonormal if needed
					std::vector<Vector3> autonormal;
					if (normals->size() != vertices->size())
					{
						autonormal.assign(normals->begin(), normals->end());
						autonormal.resize(vertices->size(), Vector3());
						std::vector<GeometryTriangles::Index> indicesTri;
						geometry->Triangulate(indicesTri);
						for (size_t i = 0; i<indicesTri.size(); i = i + 3)
						{
							//Vector3 autonorm(((vertices->at(indicesTri[i+1]) - vertices->at(indicesTri[i])).Cross(vertices->at(indicesTri[i+2]) - vertices->at(indicesTri[i]))).Normalize());
							Vector3 autonorm(((vertices->at(indicesTri[i + 1]) - vertices->at(indicesTri[i])).Cross(vertices->at(indicesTri[i + 2]) - vertices->at(indicesTri[i]))));
							float autonormLS = autonorm.Length();
							if ((autonormLS != 0.0f) && (autonormLS != 1.0f))
								autonorm *= (1.0f / autonormLS);
							if (autonormal[indicesTri[i]].LengthSq() == 0)
								autonormal[indicesTri[i]] = autonorm;
							if (autonormal[indicesTri[i + 1]].LengthSq() == 0)
								autonormal[indicesTri[i + 1]] = autonorm;
							if (autonormal[indicesTri[i + 2]].LengthSq() == 0)
								autonormal[indicesTri[i + 2]] = autonorm;
						}
						for (auto &norm : autonormal)
						{
							if (norm.LengthSq() == 0)
								norm[2] = 1.0f;
						}
					}

					VBOID cachedIDgroup;
					glGenBuffersARB(1, &cachedIDgroup.verticesID);
					glBindBufferARB(GL_ARRAY_BUFFER_ARB, cachedIDgroup.verticesID);
					float *interleaved;
					if (texCoords->size() == vertices->size())
					{
						interleaved = new float[8 * vertices->size()];
						for (size_t i = 0; i<vertices->size(); ++i)
						{
							memcpy(interleaved + i * 8, texCoords->at(i)._v, 2 * sizeof(float));
							if (normals->size() != vertices->size())
								memcpy(interleaved + i * 8 + 2, autonormal.at(i)._v, 3 * sizeof(float));
							else
								memcpy(interleaved + i * 8 + 2, normals->at(i)._v, 3 * sizeof(float));
							memcpy(interleaved + i * 8 + 5, vertices->at(i)._v, 3 * sizeof(float));
						}
						//WARNING: std::array to C array - requires consecutive memory implementation
						glBufferDataARB(GL_ARRAY_BUFFER_ARB, 8 * vertices->size() * sizeof(float), interleaved, GL_STATIC_DRAW_ARB);
						cachedIDgroup.vertexType = GL_T2F_N3F_V3F;
					}
					else
					{
						interleaved = new float[6 * vertices->size()];
						for (size_t i = 0; i<vertices->size(); ++i)
						{
							if (normals->size() != vertices->size())
								memcpy(interleaved + i * 6, autonormal.at(i)._v, 3 * sizeof(float));
							else
								memcpy(interleaved + i * 6, normals->at(i)._v, 3 * sizeof(float));
							memcpy(interleaved + i * 6 + 3, vertices->at(i)._v, 3 * sizeof(float));
						}
						//WARNING: std::array to C array - requires consecutive memory implementation
						glBufferDataARB(GL_ARRAY_BUFFER_ARB, 6 * vertices->size() * sizeof(float), interleaved, GL_STATIC_DRAW_ARB);
						cachedIDgroup.vertexType = GL_N3F_V3F;
					}
					delete [] interleaved;

					glGenBuffersARB(1, &cachedIDgroup.indicesID);
					glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, cachedIDgroup.indicesID);
					//WARNING: std::array to C array - requires consecutive memory implementation
					glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, geometry->indices.size() * sizeof(GLuint), &(*geometry->indices.begin()), GL_STATIC_DRAW_ARB);

					cachedIDgroup.verticesCount = geometry->vertices.size();

					VBOIDs[id] = cachedIDgroup;
					if (!vboRenderUpdated)
						vboRenderUpdated = true;
				}

				if (VBOIDs[id].verticesID != GL_ID_NONE)
				{
					VBOID cachedIDgroup = VBOIDs[id];
					glBindBufferARB(GL_ARRAY_BUFFER_ARB, cachedIDgroup.verticesID);
					glInterleavedArrays(cachedIDgroup.vertexType, 0, NULL);

					//TODO: support for multiple primitive type in single geometry object
					glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, cachedIDgroup.indicesID);

					for (GeometryTriangles::TriDescList::const_iterator indPrimType = geometry->triDescList.begin(); indPrimType != geometry->triDescList.end(); ++indPrimType)
					{
						unsigned nextInd = ((indPrimType+1) == geometry->triDescList.end() ? unsigned(geometry->indices.size()) : (indPrimType+1)->first);
#ifdef __WINDOWS__
#pragma warning(push)
#pragma warning(disable:4312) ///Disable warning for type cast in last argument (VBO uses offset instead of pointer)
#endif
						////WARNING: std::array to C array - requires consecutive memory implementation
						glDrawElements(GetOglPrimitiveType(indPrimType->second), GLsizei(nextInd - indPrimType->first), GL_UNSIGNED_INT, (const GLvoid *)(indPrimType->first * sizeof(GeometryTriangles::Index)));
#ifdef __WINDOWS__
#pragma warning(pop)
#endif
					}
					glDisableClientState(GL_VERTEX_ARRAY);
					glDisableClientState(GL_NORMAL_ARRAY);
					if (cachedIDgroup.vertexType == GL_T2F_N3F_V3F)
						glDisableClientState(GL_TEXTURE_COORD_ARRAY);
					glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
					glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
				}
			}
			break;

		/////////////////////////////////////////////////////////////////////
		//// POINT REPRESENTATION - RENDERS ONLY ACTUAL SPT (NO HISTORY) ////
		//// splatting technique - creates normal oriented surfels at    ////
		//// geometry processor on GPU									 ////
		/////////////////////////////////////////////////////////////////////
		case Geometry::GEOMETRY_POINT:
			/*{
				const smart_pointer<GeometryPoints> &geometry = nodeGeometry;
				unsigned id = node->GetGeometryID();
				if (id >= VBOIDs.size())
					return;

			// If VBO ready - Render model
				if (VBOIDs[id].verticesID != GL_ID_NONE)
				{
					VBOID cachedIDgroup = VBOIDs[id];

					{
						// Lock mutex to prevent data being changed while rendering
						wxCriticalSectionLocker lock(geometry->mutexPoints);

						// Get camera and compute distance to AABB center
						smart_pointer<Camera> camera;
						scene->GetNode(camera, cameraID);
						float distance = (camera->GetLocalTransMatrix()->ExtractTranslation() - geometry->GetAABB().Center()).Length();

						// Compute pixel width (at near plane level)
						float pixelWidth = (tanf(camera->GetFOV() / 2) * camera->GetNearPlane()) / (camera->GetWidth() / 2);

						// Compute AABB max dimension
						Vector3 ext(geometry->SPTS[0]->SPTaabb.Extent());
						float rootSize = __max(ext.x, __max(ext.y, ext.z));

						// Compute desired LOD
						float projectedSize = (camera->GetNearPlane() * rootSize) / distance;
						float splatSize = (projectedSize / pixelWidth) * 2;
						unsigned int LOD = int((log10f(splatSize) / log10f(2.0f)) + 0.5f);
						if (LOD > geometry->SPTS[0]->levels - 1) LOD = geometry->SPTS[0]->levels - 1;
						LOG(wxString::Format(wxT("<RenderGLModule>Selected LOD: %i"), LOD));

						// Compute number of points to draw
						long drawPoints = geometry->SPTS[0]->levelPointCounts[ LOD ];
						LOG(wxString::Format(wxT("<RenderGLModule>Drawing %i points"), drawPoints));

						// Compute size of smallest displayed SPT node = splatSize
						float size = rootSize / powf(2, LOD);

						// Set splat size and initialize Geometry shader
						smart_pointer<Material> material;
						scene->GetMaterial(material, node->GetMaterialID());
						ShaderIDMap::const_iterator shdIt = shaderIDMap.find(material->name);
						ShaderParam * param = &material->shader->paramList[1];
						const ShaderParamID * paramID = &shdIt->second.paramList[1];
						param->fVals[0] = size * 2.0f; // double the size to prevent holes
						glUniform1fvARB(paramID->paramLocation, 1, param->fVals);

						// Bind vertices
						glEnableClientState(GL_VERTEX_ARRAY);
						glBindBufferARB(GL_ARRAY_BUFFER_ARB, cachedIDgroup.verticesID);
						glVertexPointer(3, GL_FLOAT, 0, (char *) NULL);

						// Bind normals
						glEnableClientState(GL_NORMAL_ARRAY);
						glBindBufferARB(GL_ARRAY_BUFFER_ARB, cachedIDgroup.normalsID);
						glNormalPointer(GL_FLOAT, 0, (char *) NULL);

						// Bind colors
						glEnableClientState(GL_COLOR_ARRAY);
						glBindBufferARB(GL_ARRAY_BUFFER_ARB, cachedIDgroup.texCoordsID); //NOTE: texCoordsID used as colorsID
						glColorPointer(3, GL_FLOAT, 0, (char *) NULL);

						// Render points
						glDrawArrays(GL_POINTS, 0, drawPoints);
					}

					// Disable
					glDisableClientState(GL_VERTEX_ARRAY);
					glDisableClientState(GL_NORMAL_ARRAY);
					glDisableClientState(GL_COLOR_ARRAY);
					glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
				}
			// Create VBO
				else
				{
					VBOID cachedIDgroup;
					if (VBOIDs[id].verticesID==GL_ID_NONE)
					{
						// Prepare vertex array
						unsigned int vertexCount = geometry->SPTS[0]->points.size();
						Vector3 * vertices = new Vector3[vertexCount];
						for (unsigned int p = 0; p < vertexCount; p++)
						{
							vertices[p] = geometry->SPTS[0]->points[p].position;
						}

						// Create vertex buffer
						glGenBuffersARB(1, &cachedIDgroup.verticesID);
						glBindBufferARB(GL_ARRAY_BUFFER_ARB, cachedIDgroup.verticesID);
						glBufferDataARB(GL_ARRAY_BUFFER_ARB, vertexCount * sizeof(Vector3), &(*vertices), GL_STATIC_DRAW_ARB);

						// Prepare normal array
						for (unsigned int p = 0; p < vertexCount; p++)
						{
							vertices[p] = geometry->SPTS[0]->points[p].normal;
						}

						// Create normal buffer
						glGenBuffersARB(1, &cachedIDgroup.normalsID);
						glBindBufferARB(GL_ARRAY_BUFFER_ARB, cachedIDgroup.normalsID);
						glBufferDataARB(GL_ARRAY_BUFFER_ARB, vertexCount * sizeof(Vector3), &(*vertices), GL_STATIC_DRAW_ARB);

						// Prepare color array
						for (unsigned int p = 0; p < vertexCount; p++)
						{
							vertices[p] = geometry->SPTS[0]->points[p].color;
						}

						// Create color buffer
						glGenBuffersARB(1, &cachedIDgroup.texCoordsID);
						glBindBufferARB(GL_ARRAY_BUFFER_ARB, cachedIDgroup.texCoordsID); //NOTE: texCoordsID used as colorsID
						glBufferDataARB(GL_ARRAY_BUFFER_ARB, vertexCount * sizeof(Vector3), &(*vertices), GL_STATIC_DRAW_ARB);


						VBOIDs[id] = cachedIDgroup;
						if (!vboRenderUpdated)
							vboRenderUpdated = true;

						delete [] vertices;
					}
				}
			}*/
			break;

		//////////////////////////////////////////////////
		/////////// TEXT REPRESENTATION ///////////////
		//////////////////////////////////////////////////
		case Geometry::GEOMETRY_TEXT:
			{
				unsigned id = node->GetGeometryID();
				if (id >= VBOIDs.size())
					return;
				const smart_pointer<GeometryText> & geometry = nodeGeometry;

				wxFont font = geometry->GetFont();
				std::shared_ptr<FontGlyphsTextureOpenGL> font_tex = fontFactory.GetOrCreateFont(font);

				if (VBOIDs[id].verticesID == GL_ID_NONE)
				{
					TextLettersData letters(geometry->GetText(), font_tex->GetGlyphs(), geometry->GetSize(), geometry->GetSpacing(),
						geometry->GetMajorJustify(), geometry->GetMinorJustify());

					float dims[3] = {
						float(font_tex->GetDim(0)),
						float(font_tex->GetDim(1)),
						1.0f
					};

					struct Vertex
					{
						Vector2 tex_coord;
						Vector3 normal;
						Vector3 vertex;
					};
					std::vector<Vertex> data(letters.GetLetterDesc().size() * 4);

					for (size_t c = 0; c < letters.GetLetterDesc().size(); c++)
					{
						float tex[3] = {
							float(letters.GetLetterDesc()[c].glyph % font_tex->GetDim(0)),
							float((letters.GetLetterDesc()[c].glyph / font_tex->GetDim(0)) % font_tex->GetDim(1)),
							float((letters.GetLetterDesc()[c].glyph / font_tex->GetDim(0)) / font_tex->GetDim(1))
						};

						data[c * 4 + 0].vertex = Vector3(letters.GetLetterDesc()[c].position.x, letters.GetLetterDesc()[c].position.y, 0.0f);
						data[c * 4 + 1].vertex = Vector3(letters.GetLetterDesc()[c].position.x + letters.GetRectSize(), letters.GetLetterDesc()[c].position.y, 0.0f);
						data[c * 4 + 2].vertex = Vector3(letters.GetLetterDesc()[c].position.x + letters.GetRectSize(), letters.GetLetterDesc()[c].position.y + letters.GetRectSize(), 0.0f);
						data[c * 4 + 3].vertex = Vector3(letters.GetLetterDesc()[c].position.x, letters.GetLetterDesc()[c].position.y + letters.GetRectSize(), 0.0f);
						data[c * 4 + 0].normal = Vector3(0.0f, 0.0f, 1.0f);
						data[c * 4 + 1].normal = Vector3(0.0f, 0.0f, 1.0f);
						data[c * 4 + 2].normal = Vector3(0.0f, 0.0f, 1.0f);
						data[c * 4 + 3].normal = Vector3(0.0f, 0.0f, 1.0f);
						data[c * 4 + 0].tex_coord = Vector2((0.0f + tex[0]) / dims[0], (0.0f + tex[1]) / dims[1]);
						data[c * 4 + 1].tex_coord = Vector2((1.0f + tex[0]) / dims[0], (0.0f + tex[1]) / dims[1]);
						data[c * 4 + 2].tex_coord = Vector2((1.0f + tex[0]) / dims[0], (1.0f + tex[1]) / dims[1]);
						data[c * 4 + 3].tex_coord = Vector2((0.0f + tex[0]) / dims[0], (1.0f + tex[1]) / dims[1]);
					}

					VBOID cachedIDgroup;
					cachedIDgroup.vertexType = GL_T2F_N3F_V3F;
					glGenBuffersARB(1, &cachedIDgroup.verticesID);
					glBindBufferARB(GL_ARRAY_BUFFER_ARB, cachedIDgroup.verticesID);
					//WARNING: std::array to C array - requires consecutive memory implementation
					glBufferDataARB(GL_ARRAY_BUFFER_ARB, 8 * letters.GetLetterDesc().size() * 4 * sizeof(float), &data.front(), GL_STATIC_DRAW_ARB);

					cachedIDgroup.verticesCount = 8 * letters.GetLetterDesc().size() * 4;

					// No index buffer, uses glDrawArrays

					VBOIDs[id] = cachedIDgroup;
					if (!vboRenderUpdated)
						vboRenderUpdated = true;
				}

				if (VBOIDs[id].verticesID != GL_ID_NONE)
				{
					VBOID cachedIDgroup = VBOIDs[id];
					glBindBufferARB(GL_ARRAY_BUFFER_ARB, cachedIDgroup.verticesID);
					glInterleavedArrays(cachedIDgroup.vertexType, 0, NULL);

					glPushAttrib(GL_COLOR_BUFFER_BIT | GL_TEXTURE_BIT);		// Push blend, alpha test and alpha ref
					glEnable(GL_BLEND);
					glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
					glEnable(GL_ALPHA_TEST);
					glAlphaFunc(GL_GREATER, 0.1f);
					glEnable(GL_TEXTURE_2D);
					glBindTexture(GL_TEXTURE_2D, font_tex->GetTexture());

					glDrawArrays(GL_QUADS, 0, cachedIDgroup.verticesCount);

					glPopAttrib();		// Restore blend, alpha test and alpha ref

					glDisableClientState(GL_VERTEX_ARRAY);
					glDisableClientState(GL_NORMAL_ARRAY);
					glDisableClientState(GL_TEXTURE_COORD_ARRAY);
					glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
				}
				break;
			}

		default:
			//Geometry::GEOMETRY_UNKNOWN
			break;
	}
}

void RenderGlModule::drawDispList(const SceneNodeShadow *node)
{
	drawDispListMaterial(node);
	drawDispListGeometry(node);
}

void RenderGlModule::drawDispListMaterial(const SceneNodeShadow *node)
{
	size_t mid = node->GetMaterialID();
	if (lastMaterialID!=mid)
	{
		if (mid!=MATERIAL_ID_NONE)
		{
			if (mid >= dispListMatIDs.size())
				dispListMatIDs.resize(mid+1, GL_ID_NONE);

			if (dispListMatIDs[mid] != GL_ID_NONE)
			{
				glCallList(dispListMatIDs[mid]);
			}
			else
			{
				smart_pointer<Material> material;
				scene->GetMaterial(material, node->GetMaterialID());
				smart_pointer<Geometry> geometry;
				scene->GetGeometry(geometry, node->GetGeometryID());

				if (geometry && (geometry->type == Geometry::GEOMETRY_TEXT))
				{
					GLuint cachedID = dispListMatIDs[mid] = glGenLists(1);
					glNewList(cachedID, GL_COMPILE_AND_EXECUTE);
					{
						drawMaterialGeometryText(material);
					}
					glEndList();
				}
				else
				{
					// Load texture before creating list, so that it is not created in the list
					if (material)
						getTextureID(material->imageName, (material->texFlags & Material::TEX_CUBE_MAP)?Image::TEXCUBE:Image::TEX2D,
							FLAG2B(material->texFlags, Material::TEX_MIN_FILTER_MIPMAP, Material::TEX_MIN_FILTER_MASK), true);
					GLuint cachedID = dispListMatIDs[mid] = glGenLists(1);
					glNewList(cachedID, GL_COMPILE_AND_EXECUTE);
					{
						//glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
						drawMaterial(material);
						drawShader(material);
					}
					glEndList();
				}
			}
		}
		else
		{
			smart_pointer<Material> material;
			drawMaterial(material);
			drawShader(material);
		}
		lastMaterialID=(MATERIAL_ID)mid;
	}
	GLCHECKERROR(wxT("<RenderGLModule::drawDispListMaterial>"));
}

void RenderGlModule::drawDispListGeometry(const SceneNodeShadow *node)
{
	size_t gid = (size_t)node->GetGeometryID();
	if (gid==GEOMETRY_ID_NONE)
		return;

	if (gid >= dispListGeomIDs.size())
		dispListGeomIDs.resize(gid+1, GL_ID_NONE);

	if (dispListGeomIDs[gid] != GL_ID_NONE)
		glCallList(dispListGeomIDs[gid]);
	else
	{
		GLuint cachedID = dispListGeomIDs[gid] = glGenLists(1);
		const smart_pointer<Geometry> & nodeGeometry = (const smart_pointer<Geometry> &) node->geometry;
		if (!nodeGeometry)
			return;
		switch (nodeGeometry->type)
		{
			//////////////////////////////////////////////////
			/////////// TRIANGLE REPRESENTATION //////////////
			//////////////////////////////////////////////////
		case Geometry::GEOMETRY_TRIANGLE:
			{
				const smart_pointer<GeometryTriangles> & geometry = nodeGeometry;
				glNewList(cachedID, GL_COMPILE_AND_EXECUTE);
				{
					if (geometry)
					{
						const std::vector<Vector3> * vertices = &geometry->vertices;
						const std::vector<Vector3> * normals = &geometry->normals;
						const std::vector<GeometryTriangles::TexCoord> * texCoords = &geometry->texCoords;
#ifdef RENDERGL_OLD_DISPLAYLIST
						size_t indiceIndex = 0;
						size_t maxInd = geometry->indices.size();
						size_t normalsCount = normals->size();
						size_t texCoordsCount = texCoords->size();
						const std::vector<GeometryTriangles::Index> * indices = &geometry->indices;
						for (GeometryTriangles::TriDescList::const_iterator indPrimType = geometry->triDescList.begin(); indPrimType != geometry->triDescList.end(); ++indPrimType)
						{
							GeometryTriangles::TriDescList::const_iterator endIndPrimType = indPrimType + 1;
							size_t endInd = (endIndPrimType == geometry->triDescList.end() ? maxInd : endIndPrimType->first);
							endInd = __min(endInd, maxInd);

							///Simple polygonal normal generation
							Vector3 autonormal;
							if (normals->empty() && (indiceIndex + 2 < endInd))
							{
								std::vector<GeometryTriangles::Index>::const_iterator indIt = indices->begin() + indiceIndex;
								Vector3 v1(vertices->at(*indIt++));
								Vector3 v2(vertices->at(*indIt++));
								Vector3 v3(vertices->at(*indIt));
								autonormal = ((v2 - v1).Cross(v3 - v1)).Normalize();
							}

							glBegin(GetOglPrimitiveType(indPrimType->second));

							for (;	indiceIndex < endInd; indiceIndex++)
							{
								GeometryTriangles::Index indice = indices->at(indiceIndex);
								if (indice < normalsCount)
									glNormal3fv(normals->at(indice)._v);
								else
									glNormal3fv(autonormal._v);
								if (indice < texCoordsCount)
									glTexCoord2fv(texCoords->at(indice)._v);
								glVertex3fv(vertices->at(indice)._v);
							}

							glEnd();
						}
#else //use vertex arrays
						//compute autonormal if needed
						std::vector<Vector3> autonormal;
						if (normals->size()!=vertices->size())
						{
							autonormal.assign(normals->begin(), normals->end());
							autonormal.resize(vertices->size(), Vector3());
							std::vector<GeometryTriangles::Index> indicesTri;
							geometry->Triangulate(indicesTri);
							for (size_t i=0; i<indicesTri.size(); i=i+3)
							{
								//Vector3 autonorm(((vertices->at(indicesTri[i+1]) - vertices->at(indicesTri[i])).Cross(vertices->at(indicesTri[i+2]) - vertices->at(indicesTri[i]))).Normalize());
								Vector3 autonorm(((vertices->at(indicesTri[i+1]) - vertices->at(indicesTri[i])).Cross(vertices->at(indicesTri[i+2]) - vertices->at(indicesTri[i]))));
								float autonormLS=autonorm.Length();
								if ((autonormLS!=0.0f) && (autonormLS!=1.0f))
									autonorm*=(1.0f/autonormLS);
								if (autonormal[indicesTri[i]].LengthSq()==0)
									autonormal[indicesTri[i]]=autonorm;
								if (autonormal[indicesTri[i+1]].LengthSq()==0)
									autonormal[indicesTri[i+1]]=autonorm;
								if (autonormal[indicesTri[i+2]].LengthSq()==0)
									autonormal[indicesTri[i+2]]=autonorm;
							}
							for (auto &norm : autonormal)
							{
								if (norm.LengthSq()==0)
									norm[2]=1.0f;
							}
						}
#define RENDERGL_INTERLEAVED
#ifdef RENDERGL_INTERLEAVED
						float *interleaved=NULL;
						if (texCoords->size()==vertices->size())
						{
							interleaved = new float[8*vertices->size()];
							for (size_t i=0; i<vertices->size(); ++i)
							{
								memcpy(interleaved+i*8, texCoords->at(i)._v, 2*sizeof(float));
								if (normals->size()!=vertices->size())
									memcpy(interleaved+i*8+2, autonormal.at(i)._v, 3*sizeof(float));
								else
									memcpy(interleaved+i*8+2, normals->at(i)._v, 3*sizeof(float));
								memcpy(interleaved+i*8+5, vertices->at(i)._v, 3*sizeof(float));
							}
							glInterleavedArrays(GL_T2F_N3F_V3F, 0, interleaved);
						}
						else
						{
							interleaved = new float[6*vertices->size()];
							for (size_t i=0; i<vertices->size(); ++i)
							{
								if (normals->size()!=vertices->size())
									memcpy(interleaved+i*6, autonormal.at(i)._v, 3*sizeof(float));
								else
									memcpy(interleaved+i*6, normals->at(i)._v, 3*sizeof(float));
								memcpy(interleaved+i*6+3, vertices->at(i)._v, 3*sizeof(float));
							}
							glInterleavedArrays(GL_N3F_V3F, 0, interleaved);
						}
#else
						glEnableClientState(GL_VERTEX_ARRAY);
						glEnableClientState(GL_NORMAL_ARRAY);
						if (texCoords->size()==vertices->size())
							glEnableClientState(GL_TEXTURE_COORD_ARRAY);

						glVertexPointer(3, GL_FLOAT, 0, &(*vertices->begin()));
						if (normals->size()==vertices->size())
							glNormalPointer(GL_FLOAT, 0, &(*normals->begin()));
						else
							glNormalPointer(GL_FLOAT, 0, &(*autonormal.begin()));
						if (texCoords->size()==vertices->size())
							glTexCoordPointer(2, GL_FLOAT, 0, &(*texCoords->begin()));
#endif // RENDERGL_INTERLEAVED


						/*std::vector<GeometryTriangles::Index> indicesTri;
						geometry->Triangulate(indicesTri);
						glDrawElements(GL_TRIANGLES, indicesTri.size(), GL_UNSIGNED_INT, &(*indicesTri.begin()));*/
						struct GeometryMultiDraw
						{
							GLenum mode;							// Mode of drawing (e.g. GL_TRIANGLE_STRIP
							std::vector<GLsizei>		count;		// Number of elements per MultiDrawElements command
							std::vector<const void *>	indicesVA;	// Pointers to indices in CPU memory for one command
							GeometryMultiDraw(GLenum mode) : mode(mode) {}
						};
						GeometryMultiDraw GL_POINTS_commands(GL_POINTS);
						GeometryMultiDraw GL_LINES_commands(GL_LINES);
						GeometryMultiDraw GL_LINE_STRIP_commands(GL_LINE_STRIP);
						GeometryMultiDraw GL_TRIANGLES_commands(GL_TRIANGLES);
						GeometryMultiDraw GL_TRIANGLE_STRIP_commands(GL_TRIANGLE_STRIP);
						GeometryMultiDraw GL_TRIANGLE_FAN_commands(GL_TRIANGLE_FAN);
						GeometryMultiDraw GL_QUADS_commands(GL_QUADS);
						GeometryMultiDraw GL_POLYGON_commands(GL_POLYGON);

						// In this implementation, transform polygons and quads to triangle fans and
						// quad strips to triangle strips. Thanks to this there is no need to change geometry_tris->indices array
						//coreMultiDrawElementsIndices = geometry_tris->indices;

						for (auto iter = geometry->triDescList.begin(); iter != geometry->triDescList.end(); ++iter)
						{
							auto next_iter = iter;
							++next_iter;
							GLsizei count = ((next_iter == geometry->triDescList.end()) ? geometry->indices.size() : next_iter->first) - iter->first;

							// Choose target for this command
							GeometryMultiDraw *target = nullptr;
							switch (iter->second)
							{
							case VRUT::GeometryTriangles::POINTS:		target = &GL_POINTS_commands;			break;
							case VRUT::GeometryTriangles::LINES:		target = &GL_LINES_commands;			break;
							case VRUT::GeometryTriangles::LINE_STRIP:	target = &GL_LINE_STRIP_commands;		break;
							case VRUT::GeometryTriangles::TRI_LIST:		target = &GL_TRIANGLES_commands;		break;
							case VRUT::GeometryTriangles::TRI_STRIP:	target = &GL_TRIANGLE_STRIP_commands;	break;
							case VRUT::GeometryTriangles::TRI_FAN:		target = &GL_TRIANGLE_FAN_commands;		break;
							case VRUT::GeometryTriangles::QUADS:		target = &GL_QUADS_commands;		break;
							case VRUT::GeometryTriangles::POLYGON:		target = &GL_POLYGON_commands;		break;
							default: break;
							}
							if (nullptr == target)		continue;		// Unknown type

							// Push the whole object
							if (count > 0)
							{
								const char *va_address = (const char *)(&(geometry->indices[iter->first]));
								target->count.push_back(count);
								target->indicesVA.push_back(va_address);
							}
						}

						// use the commands
						if (!GL_POINTS_commands.count.empty())
							glMultiDrawElements(GL_POINTS, &(GL_POINTS_commands.count[0]), GL_UNSIGNED_INT, (const GLvoid **)&(GL_POINTS_commands.indicesVA[0]), GL_POINTS_commands.count.size());
						if (!GL_LINES_commands.count.empty())
							glMultiDrawElements(GL_LINES, &(GL_LINES_commands.count[0]), GL_UNSIGNED_INT, (const GLvoid **)&(GL_LINES_commands.indicesVA[0]), GL_LINES_commands.count.size());
						if (!GL_LINE_STRIP_commands.count.empty())
							glMultiDrawElements(GL_LINE_STRIP, &(GL_LINE_STRIP_commands.count[0]), GL_UNSIGNED_INT, (const GLvoid **)&(GL_LINE_STRIP_commands.indicesVA[0]), GL_LINE_STRIP_commands.count.size());
						if (!GL_TRIANGLES_commands.count.empty())
							glMultiDrawElements(GL_TRIANGLES, &(GL_TRIANGLES_commands.count[0]), GL_UNSIGNED_INT, (const GLvoid **)&(GL_TRIANGLES_commands.indicesVA[0]), GL_TRIANGLES_commands.count.size());
						if (!GL_TRIANGLE_STRIP_commands.count.empty())
							glMultiDrawElements(GL_TRIANGLE_STRIP, &(GL_TRIANGLE_STRIP_commands.count[0]), GL_UNSIGNED_INT, (const GLvoid **)&(GL_TRIANGLE_STRIP_commands.indicesVA[0]), GL_TRIANGLE_STRIP_commands.count.size());
						if (!GL_TRIANGLE_FAN_commands.count.empty())
							glMultiDrawElements(GL_TRIANGLE_FAN, &(GL_TRIANGLE_FAN_commands.count[0]), GL_UNSIGNED_INT, (const GLvoid **)&(GL_TRIANGLE_FAN_commands.indicesVA[0]), GL_TRIANGLE_FAN_commands.count.size());
						if (!GL_QUADS_commands.count.empty())
							glMultiDrawElements(GL_QUADS, &(GL_QUADS_commands.count[0]), GL_UNSIGNED_INT, (const GLvoid **)&(GL_QUADS_commands.indicesVA[0]), GL_QUADS_commands.count.size());
						if (!GL_POLYGON_commands.count.empty())
							glMultiDrawElements(GL_POLYGON, &(GL_POLYGON_commands.count[0]), GL_UNSIGNED_INT, (const GLvoid **)&(GL_POLYGON_commands.indicesVA[0]), GL_POLYGON_commands.count.size());

						glDisableClientState(GL_VERTEX_ARRAY);
						glDisableClientState(GL_NORMAL_ARRAY);
						if (texCoords->size()==vertices->size())
							glDisableClientState(GL_TEXTURE_COORD_ARRAY);
#ifdef RENDERGL_INTERLEAVED
						delete [] interleaved;
#endif //RENDERGL_INTERLEAVED
#endif
					}
					//glPopClientAttrib();
				}
				glEndList();
			}
			break;

			//////////////////////////////////////////////////////////////////////
			//// POINT REPRESENTATION - RENDER POSSIBLE INCLUDING SPT HISTORY ////
			//// splatting technique - creates normal oriented surfels at     ////
			//// geometry processor on GPU									  ////
			//////////////////////////////////////////////////////////////////////
		case Geometry::GEOMETRY_POINT:
			{
				const smart_pointer<GeometryPoints> &points = nodeGeometry;

				// Lock mutex to prevent data being changed while rendering
				wxCriticalSectionLocker lock(points->mutexPoints);

				// Get camera and compute distance to AABB center
				smart_pointer<Camera> camera;
				scene->GetNode(camera, cameraID);
				float distance = (camera->GetLocalTransMatrix()->ExtractTranslation() - points->GetAABB().Center()).Length();

				// Compute pixel width (at near plane level)
				float pixelWidth = (tanf(camera->GetFOV() / 2) * camera->GetNearPlane()) / (camera->GetWidth() / 2);

				// Compute AABB max dimension
				Vector3 ext(points->SPTS[0]->SPTaabb.Extent());
				float rootSize = __max(ext.x, __max(ext.y, ext.z));

				// Compute desired LOD
				float projectedSize = (camera->GetNearPlane() * rootSize) / distance;
				float splatSize = (projectedSize / pixelWidth) * 2;
				unsigned int LOD = int((log10f(splatSize) / log10f(2.0f)) + 0.5f);
				if (LOD > points->SPTS[0]->levels - 1)
					LOD = points->SPTS[0]->levels - 1;
				LOG(wxString::Format(wxT("<RenderGLModule>Selected LOD: %i"), LOD));

				// Compute number of points to draw
				long drawPoints = points->SPTS[0]->levelPointCounts[LOD];
				LOG(wxString::Format(wxT("<RenderGLModule>Drawing %i points"), drawPoints));

				// Compute size of smallest displayed SPT node
				float size = rootSize / powf(2, LOD);

				// Set splat size and initialize Geometry shader
				smart_pointer<Material> material;
				scene->GetMaterial(material, node->GetMaterialID());
				ShaderIDMap::const_iterator shdIt = shaderIDMap.find(material->name);
				ShaderParam * param = &material->shader->paramList[1];
				const ShaderParamID * paramID = &shdIt->second.paramList[1];
				param->fVals[0] = size * 2.0f; // double the size to prevent holes
				glUniform1fvARB(paramID->paramLocation, 1, param->fVals);

				// Render points
				glBegin(GL_POINTS);
				//TODO: select LOD for SPTs in history ??
				if (points->drawHistory)
				{
					for (size_t i = 0; i < points->SPTS.size(); i++)
					{
						for (size_t p = 0; p < points->SPTS[i]->points.size(); p++)
						{
							glNormal3f(points->SPTS[i]->points[p].normal.x, points->SPTS[i]->points[p].normal.y, points->SPTS[i]->points[p].normal.z);
							glColor3f(points->SPTS[i]->points[p].color.x, points->SPTS[i]->points[p].color.y, points->SPTS[i]->points[p].color.z);
							glVertex3f(points->SPTS[i]->points[p].position.x, points->SPTS[i]->points[p].position.y, points->SPTS[i]->points[p].position.z);
						}
					}
				}
				else
				{
					for (long p = 0; p < drawPoints; p++)
					{
						glNormal3f(points->SPTS[0]->points[p].normal.x, points->SPTS[0]->points[p].normal.y, points->SPTS[0]->points[p].normal.z);
						glColor3f(points->SPTS[0]->points[p].color.x, points->SPTS[0]->points[p].color.y, points->SPTS[0]->points[p].color.z);
						glVertex3f(points->SPTS[0]->points[p].position.x, points->SPTS[0]->points[p].position.y, points->SPTS[0]->points[p].position.z);
					}
				}
				glEnd();

				glColor3f(1.0f, 1.0f, 1.0f);
			}
			break;

			//////////////////////////////////////////////
			/////////// TEXT REPRESENTATION //////////////
			//////////////////////////////////////////////
		case Geometry::GEOMETRY_TEXT:
			{
				const smart_pointer<GeometryText> & geometry = nodeGeometry;
				if (!geometry)
					break;
				std::shared_ptr<FontGlyphsTextureOpenGL> font_tex;
				// Do not create font inside display list
				{
					wxFont font = geometry->GetFont();
					font_tex = fontFactory.GetOrCreateFont(font);
				}
				if (!font_tex)
					break;

				TextLettersData letters(geometry->GetText(), font_tex->GetGlyphs(), geometry->GetSize(), geometry->GetSpacing(), geometry->GetMajorJustify(), geometry->GetMinorJustify());
				if (letters.GetLetterDesc().empty())
					break;

				glNewList(cachedID, GL_COMPILE_AND_EXECUTE);
				{
					float dims[3] = {
						float(font_tex->GetDim(0)),
						float(font_tex->GetDim(1)),
						1.0f
					};

					glPushAttrib(GL_COLOR_BUFFER_BIT | GL_TEXTURE_BIT);		// Push blend, alpha test and alpha ref
					glEnable(GL_BLEND);
					glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
					glEnable(GL_ALPHA_TEST);
					glAlphaFunc(GL_GREATER, 0.1f);
					glEnable(GL_TEXTURE_2D);
					glBindTexture(GL_TEXTURE_2D, font_tex->GetTexture());

#ifdef RENDERGL_OLD_DISPLAYLIST
					glBegin(GL_QUADS);
					glNormal3f(0.0f, 0.0f, 1.0f);

					for (size_t c = 0; c < letters.GetLetterDesc().size(); c++)
					{
						float tex[3] = {
							float( letters.GetLetterDesc()[c].glyph % font_tex->GetDim(0)),
							float((letters.GetLetterDesc()[c].glyph / font_tex->GetDim(0)) % font_tex->GetDim(1)),
							float((letters.GetLetterDesc()[c].glyph / font_tex->GetDim(0)) / font_tex->GetDim(1))
						};

						glTexCoord2f((0.0f + tex[0]) / dims[0], (0.0f + tex[1]) / dims[1]);
						glVertex3f(letters.GetLetterDesc()[c].position.x,							letters.GetLetterDesc()[c].position.y,							0.0f);

						glTexCoord2f((1.0f + tex[0]) / dims[0], (0.0f + tex[1]) / dims[1]);
						glVertex3f(letters.GetLetterDesc()[c].position.x + letters.GetRectSize(),	letters.GetLetterDesc()[c].position.y,							0.0f);

						glTexCoord2f((1.0f + tex[0]) / dims[0], (1.0f + tex[1]) / dims[1]);
						glVertex3f(letters.GetLetterDesc()[c].position.x + letters.GetRectSize(),	letters.GetLetterDesc()[c].position.y + letters.GetRectSize(),	0.0f);

						glTexCoord2f((0.0f + tex[0]) / dims[0], (1.0f + tex[1]) / dims[1]);
						glVertex3f(letters.GetLetterDesc()[c].position.x,							letters.GetLetterDesc()[c].position.y + letters.GetRectSize(),	0.0f);
					}
					glEnd();
#else	// RENDERGL_OLD_DISPLAYLIST

#define RENDERGL_INTERLEAVED
#ifdef RENDERGL_INTERLEAVED
					struct Vertex
					{
						Vector2 tex_coord;
						Vector3 normal;
						Vector3 vertex;
					};
					std::vector<Vertex> data(letters.GetLetterDesc().size() * 4);

					for (size_t c = 0; c < letters.GetLetterDesc().size(); c++)
					{
						float tex[3] = {
							float( letters.GetLetterDesc()[c].glyph % font_tex->GetDim(0)),
							float((letters.GetLetterDesc()[c].glyph / font_tex->GetDim(0)) % font_tex->GetDim(1)),
							float((letters.GetLetterDesc()[c].glyph / font_tex->GetDim(0)) / font_tex->GetDim(1))
						};

						data[c*4+0].vertex = Vector3(letters.GetLetterDesc()[c].position.x,							letters.GetLetterDesc()[c].position.y,							0.0f);
						data[c*4+1].vertex = Vector3(letters.GetLetterDesc()[c].position.x + letters.GetRectSize(),	letters.GetLetterDesc()[c].position.y,							0.0f);
						data[c*4+2].vertex = Vector3(letters.GetLetterDesc()[c].position.x + letters.GetRectSize(),	letters.GetLetterDesc()[c].position.y + letters.GetRectSize(),	0.0f);
						data[c*4+3].vertex = Vector3(letters.GetLetterDesc()[c].position.x,							letters.GetLetterDesc()[c].position.y + letters.GetRectSize(),	0.0f);
						data[c*4+0].normal = Vector3(0.0f, 0.0f, 1.0f);
						data[c*4+1].normal = Vector3(0.0f, 0.0f, 1.0f);
						data[c*4+2].normal = Vector3(0.0f, 0.0f, 1.0f);
						data[c*4+3].normal = Vector3(0.0f, 0.0f, 1.0f);
						data[c*4+0].tex_coord = Vector2((0.0f + tex[0]) / dims[0], (0.0f + tex[1]) / dims[1]);
						data[c*4+1].tex_coord = Vector2((1.0f + tex[0]) / dims[0], (0.0f + tex[1]) / dims[1]);
						data[c*4+2].tex_coord = Vector2((1.0f + tex[0]) / dims[0], (1.0f + tex[1]) / dims[1]);
						data[c*4+3].tex_coord = Vector2((0.0f + tex[0]) / dims[0], (1.0f + tex[1]) / dims[1]);
					}
					glInterleavedArrays(GL_T2F_N3F_V3F, 0, &data.front());

#else	// RENDERGL_INTERLEAVED
					std::vector<Vector3> vertices(letters.GetLetterDesc().size() * 4);
					std::vector<Vector3> normals(letters.GetLetterDesc().size() * 4);
					std::vector<Vector2> tex_coords(letters.GetLetterDesc().size() * 4);

					for (size_t c = 0; c < letters.GetLetterDesc().size(); c++)
					{
						float tex[3] = {
							float( letters.GetLetterDesc()[c].glyph % font_tex->GetDim(0)),
							float((letters.GetLetterDesc()[c].glyph / font_tex->GetDim(0)) % font_tex->GetDim(1)),
							float((letters.GetLetterDesc()[c].glyph / font_tex->GetDim(0)) / font_tex->GetDim(1))
						};

						vertices[c*4+0] = Vector3(letters.GetLetterDesc()[c].position.x,							letters.GetLetterDesc()[c].position.y,							0.0f);
						vertices[c*4+1] = Vector3(letters.GetLetterDesc()[c].position.x + letters.GetRectSize(),	letters.GetLetterDesc()[c].position.y,							0.0f);
						vertices[c*4+2] = Vector3(letters.GetLetterDesc()[c].position.x + letters.GetRectSize(),	letters.GetLetterDesc()[c].position.y + letters.GetRectSize(),	0.0f);
						vertices[c*4+3] = Vector3(letters.GetLetterDesc()[c].position.x,							letters.GetLetterDesc()[c].position.y + letters.GetRectSize(),	0.0f);
						normals[c*4+0] = Vector3(0.0f, 0.0f, 1.0f);
						normals[c*4+1] = Vector3(0.0f, 0.0f, 1.0f);
						normals[c*4+2] = Vector3(0.0f, 0.0f, 1.0f);
						normals[c*4+3] = Vector3(0.0f, 0.0f, 1.0f);
						tex_coords[c*4+0] = Vector2((0.0f + tex[0]) / dims[0], (0.0f + tex[1]) / dims[1]);
						tex_coords[c*4+1] = Vector2((1.0f + tex[0]) / dims[0], (0.0f + tex[1]) / dims[1]);
						tex_coords[c*4+2] = Vector2((1.0f + tex[0]) / dims[0], (1.0f + tex[1]) / dims[1]);
						tex_coords[c*4+3] = Vector2((0.0f + tex[0]) / dims[0], (1.0f + tex[1]) / dims[1]);
					}

					glEnableClientState(GL_VERTEX_ARRAY);
					glVertexPointer(3, GL_FLOAT, 0, &vertices.front());
					glEnableClientState(GL_NORMAL_ARRAY);
					glNormalPointer(GL_FLOAT, 0, &normals.front());
					glEnableClientState(GL_TEXTURE_COORD_ARRAY);
					glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords.front());
#endif	// RENDERGL_INTERLEAVED

					glDrawArrays(GL_QUADS, 0, letters.GetLetterDesc().size()*4);
					glDisableClientState(GL_VERTEX_ARRAY);
					glDisableClientState(GL_NORMAL_ARRAY);
					glDisableClientState(GL_TEXTURE_COORD_ARRAY);

#endif	// RENDERGL_OLD_DISPLAYLIST

					glPopAttrib();		// Restore blend, alpha test and alpha ref
				}
				glEndList();
			}
			break;

		default:
			//Geometry::GEOMETRY_UNKNOWN
			break;
		}
	}

	GLCHECKERROR(wxT("<RenderGLModule::drawDispListGeometry>"));
}

void RenderGlModule::drawFlatMirrorGeometryIntoDepthAndColorBuffer(SceneShadow::ItemsList& mirrorList, NODE_ID doNotDrawMe)
{
	if (!scene || mirrorList.empty())
		return;

	drawSolidGeometry(mirrorList, false, doNotDrawMe);

	GLCHECKERROR(wxT("<RenderGLModule::drawFlatMirrorGeometryIntoDepthAndColorBuffer>"));
}

void RenderGlModule::drawFlatMirrorStencilAndSetupClippingPlane(const SceneShadow::ItemInfo& mirrorNodeInfo, const Matrix& reflectedMatrix, const Vector4& clipPlane)
{
	if (!scene)
		return;

	SceneShadow::ItemsList itemsList;
	itemsList.push_back(mirrorNodeInfo);

	drawSolidGeometry(itemsList);

	//We need to render only the geometry in front of the mirror
	glLoadMatrixf((reflectedMatrix*camMatInverse)._m);
	lastMatrix=reflectedMatrix;
	glEnable(GL_CLIP_PLANE0);
	GLdouble clipPlaneArray[4] = {clipPlane.x, clipPlane.y, clipPlane.z, clipPlane.w};
	glClipPlane(GL_CLIP_PLANE0, clipPlaneArray);
	GLCHECKERROR(wxT("<RenderGLModule::drawFlatMirrorStencil>"));
}

void RenderGlModule::drawSceneReflection(const Matrix& reflectedMatrix, SceneNodeShadow& mirrorNode, const std::vector<LightInfo> &lights, NODE_ID clonedCameraID, const Vector4 &reflectionPlane)
{
	if (!scene)
		return;
	if (!Initialize())
		return;

	SceneShadow::ItemsList itemsList, blendedItemsList, reflectiveItemsList, virtualCameraItemsList;
	Matrix backupClonedCameraMatrix(clonedCameraScene->GetWorldTransMatrix(clonedCameraID));
	AABB aabb=mirrorNode.GetAABB();
	Matrix mirrorNodeMat(*sceneShadow->GetWorldMatrix(mirrorNode.GetID()));
	smart_pointer<Camera> clonedCamera;
	clonedCameraScene->GetNode(clonedCamera, clonedCameraID);
	Matrix clonedCamWorldMat(clonedCameraScene->GetWorldTransMatrix(clonedCameraID));
	Vector3 cameraPosition(clonedCamWorldMat.ExtractTranslation());
	cameraPosition+=clonedCamWorldMat.ExtractRotation().TransformCoord(clonedCamera->GetOffset()+clonedCamera->GetEyeAxis()*clonedCamera->GetEyeSeparation());
	Vector3 reflectedCameraPosition(reflectedMatrix.TransformCoord(cameraPosition));
	float farPlane=clonedCamera->GetFarPlane();

	if (cullingMethod&1)
		sceneShadow->Cull(cameraPosition, itemsList, blendedItemsList, reflectiveItemsList, backgroundItemsList, virtualCameraItemsList, renderingRootID);//ALL
	else
	{
		BSphere bsphere;
		bTree::CullingPlanes planes;
		//std::vector<Vector3> frustumPoints;
		planes.FromReflection(aabb, mirrorNodeMat, cameraPosition, reflectionPlane, reflectedCameraPosition, farPlane, bsphere/*, &frustumPoints*/);
		sceneShadow->Cull(planes, bsphere, cameraPosition, itemsList, blendedItemsList, reflectiveItemsList, backgroundItemsList, virtualCameraItemsList, renderingRootID);
	}
	//LOG(wxString::Format(wxT("drawSceneReflection: itemsList=%lu, blendedItemsList=%lu"), (unsigned long)itemsList.size(), (unsigned long)blendedItemsList.size()));

	/*//draw the frustum
	if (showStatsString)
	{
		smart_pointer<GeometryTriangles> gTri;
		scene->GetGeometry(gTri, 2);
		GeometryTriangles *geoTri=(GeometryTriangles *)gTri->Clone();
		geoTri->vertices.clear();
		geoTri->indices.clear();
		geoTri->normals.clear();
		geoTri->texCoords.clear();
		geoTri->triDescList.clear();
		std::pair<GeometryTriangles::Index, GeometryTriangles::PRIMITIVE_TYPE> indType(0, GeometryTriangles::LINES);
		geoTri->triDescList.push_back(indType);
		geoTri->vertices.assign(frustumPoints.begin(), frustumPoints.end());
		//unsigned int tris[]={0, 2, 1,  5, 3, 4,  0, 1, 4,  2, 3, 5,  0, 4, 3,  1, 2, 5};
		//for (int i = 0; i < 18; i++)
		//	geoTri->indices.push_back(tris[i]);

		//lines around whole frustum
		//unsigned int frustum[]={0, 1, 0, 5, 4, 5, 1, 2, 2, 6, 6, 4, 7, 1, 7, 6, 7, 5, 3, 0, 3, 4, 3, 2};
		unsigned int frustum[]={0, 1, 0, 3, 0, 4, 1, 2, 1, 5, 2, 3, 2, 6, 3, 7, 4, 5, 5, 6, 6, 7, 7, 4};
		for (int i = 0; i < 24; i++)
			geoTri->indices.push_back(frustum[i]);

		//all active quads
		//for (int i = 0; i < geoTri->vertices.size(); i++)
		//{
		//	geoTri->indices.push_back(i);
		//	geoTri->indices.push_back((i+1)%4+(i/4)*4);
		//}
		scene->AddGeometry(geoTri);
	}//*/

		
	///Set view matrix
	glMatrixMode(GL_MODELVIEW);

	Matrix camMatInverseBackup=camMatInverse;
	Matrix curMatrix(reflectedMatrix*camMatInverse);
	glLoadMatrixf(curMatrix._m);
	camMatInverse=curMatrix;
	lastMatrix=Matrix();

	lastMaterialID = (MATERIAL_ID)-2;

	//sort for rendering
	if (sortGeometries==1)
		std::sort(itemsList.begin(), itemsList.end(), SceneShadow::ItemInfo::SortByDistance);
	else if (sortGeometries==2)
		std::sort(itemsList.begin(), itemsList.end(), SceneShadow::ItemInfo::SortByMaterialID);

	//While rendering the reflected scene, some techniques (like cube map reflections) require correct, reflected, camera position.
	//Otherwise, these techniques would yield wrong results (eg. wrong cube map reflection in a flat mirror).
	clonedCameraScene->SetTransformation(clonedCameraID, backupClonedCameraMatrix * reflectedMatrix);

	drawBackground();

	//Scene lights
	drawLights(lights);

	drawSolidGeometry(reflectiveItemsList, true, mirrorNode.GetID()); //comment out for debugDraw
	drawSolidGeometry(itemsList, true); //comment out for debugDraw
	drawTransparentGeometry(blendedItemsList, true); //comment out for debugDraw

	if (shaderSupported)
		glUseProgramObjectARB(0);

	clonedCameraScene->SetTransformation(clonedCameraID, backupClonedCameraMatrix);

	glMatrixMode(GL_MODELVIEW);
	camMatInverse=camMatInverseBackup;
	glLoadMatrixf((reflectedMatrix*camMatInverse)._m);
	lastMatrix=reflectedMatrix;
	GLCHECKERROR(wxT("<RenderGLModule::drawSceneReflection>"));
}

void RenderGlModule::snapshotBuffer(int windowWidth, int windowHeight, GLenum format, const char* filename)
{
	unsigned char *bmpBuffer;
	float *depthBuffer;
	unsigned char *stencilBuffer;
	int imgByteCount(windowWidth*windowHeight*3);
	int depthFloatCount(windowWidth*windowHeight);
	if (depthFloatCount == 0)
		return;

	bmpBuffer = (unsigned char*)malloc(imgByteCount);
	if (!bmpBuffer)
		return;
	depthBuffer = (float*)malloc(depthFloatCount * sizeof(float));
	if (!depthBuffer)
	{
      	free(bmpBuffer);
		return;
	}
	stencilBuffer = (unsigned char*)malloc(depthFloatCount);
	if (!stencilBuffer)
	{
      	free(bmpBuffer);
		free(depthBuffer);
		return;
	}

	memset(bmpBuffer, 0, imgByteCount);

	if (format == GL_DEPTH_COMPONENT || format == GL_DEPTH_COMPONENT16 || format == GL_DEPTH_COMPONENT24 || format == GL_DEPTH_COMPONENT32)
	{
		//WIP
		glReadPixels((GLint)0, (GLint)0, (GLint)windowWidth, (GLint)windowHeight, format, GL_FLOAT, depthBuffer);
		for (int j = 0; j < windowHeight; ++j)
		{
			for (int i = 0; i < windowWidth; ++i)
			{
				float dbg = depthBuffer[j * windowWidth + i];
				bmpBuffer[(j * windowWidth + i) * 3] = (unsigned char) (depthBuffer[j * windowWidth + i]);
				if (dbg < 1.0 && dbg > 0.0)
					dbg = 0;
			}
		}
	}
	else if (format == GL_STENCIL_INDEX)
	{
		glReadPixels((GLint)0, (GLint)0, (GLint)windowWidth, (GLint)windowHeight, format, GL_UNSIGNED_BYTE, stencilBuffer);
		for (int j = 0; j < windowHeight; j++)
		{
			for (int i = 0; i < windowWidth; i++)
			{
				float dbg = stencilBuffer[j * windowWidth + i];
				bmpBuffer[(j * windowWidth + i) * 3] = stencilBuffer[j * windowWidth + i];
				if (dbg < 255 && dbg > 0)
					dbg = 0;
			}
		}
	}
	else
	{
		glReadPixels((GLint)0, (GLint)0, (GLint)windowWidth-1, (GLint)windowHeight-1, format, GL_UNSIGNED_BYTE, bmpBuffer);
	}

	FILE *filePtr = fopen(filename, "wb");
	if (!filePtr)
	{
      		free(bmpBuffer);
		free(depthBuffer);
		free(stencilBuffer);
		return;
	}

	BITMAPFILEHEADER bitmapFileHeader;
	BITMAPINFOHEADER bitmapInfoHeader;

	bitmapFileHeader.bfType = 0x4D42; //"BM"
	bitmapFileHeader.bfSize = imgByteCount;
	bitmapFileHeader.bfReserved1 = 0;
	bitmapFileHeader.bfReserved2 = 0;
	bitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

	bitmapInfoHeader.biBitCount = 24;
	bitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER); bitmapInfoHeader.biWidth = windowWidth-1; bitmapInfoHeader.biHeight = windowHeight-1; bitmapInfoHeader.biPlanes = 1; bitmapInfoHeader.biCompression = 0/*BI_RGB*/; bitmapInfoHeader.biSizeImage = 0; bitmapInfoHeader.biXPelsPerMeter = 0; // ?
	bitmapInfoHeader.biYPelsPerMeter = 0; // ?
	bitmapInfoHeader.biClrUsed = 0;
	bitmapInfoHeader.biClrImportant = 0;

	fwrite(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr); fwrite(&bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr); fwrite(bmpBuffer, imgByteCount, 1, filePtr); fclose(filePtr);

	free(bmpBuffer);
	free(depthBuffer);
	free(stencilBuffer);
}


// make a snaphot of the currently bound texture
void
RenderGlModule::snapshotTexture(const int level, const char* filename)
{
	int width;
	int height;
	glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &width);
	glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &height);

	int imgByteCount(width*height*3);

	unsigned char *bmpBuffer = (unsigned char*)malloc(imgByteCount);
	if (!bmpBuffer)
		return;
	
	memset(bmpBuffer, 0, imgByteCount);
	
	glGetTexImage(GL_TEXTURE_2D, level, GL_BGR, GL_UNSIGNED_BYTE, bmpBuffer);
	
	FILE *filePtr = fopen(filename, "wb");
	if (!filePtr) {
		free(bmpBuffer);
		return;
	}
	
	BITMAPFILEHEADER bitmapFileHeader;
	BITMAPINFOHEADER bitmapInfoHeader;
	
	bitmapFileHeader.bfType = 0x4D42; //"BM"
	bitmapFileHeader.bfSize = imgByteCount;
	bitmapFileHeader.bfReserved1 = 0;
	bitmapFileHeader.bfReserved2 = 0;
	bitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

	bitmapInfoHeader.biBitCount = 24;
	bitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER); bitmapInfoHeader.biWidth = width; bitmapInfoHeader.biHeight = height; bitmapInfoHeader.biPlanes = 1; bitmapInfoHeader.biCompression = 0/*BI_RGB*/; bitmapInfoHeader.biSizeImage = 0; bitmapInfoHeader.biXPelsPerMeter = 0; // ?
	bitmapInfoHeader.biYPelsPerMeter = 0; // ?
	bitmapInfoHeader.biClrUsed = 0;
	bitmapInfoHeader.biClrImportant = 0;

	fwrite(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
	fwrite(&bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);
	fwrite(bmpBuffer, imgByteCount, 1, filePtr);
	fclose(filePtr);
	
	free(bmpBuffer);
}

void RenderGlModule::drawFlatMirrorReflection(SceneShadow::ItemInfo& item, SceneShadow::ItemsList mirrorList, const std::vector<LightInfo> &lights)
{
	smart_pointer<GeometryTriangles> reflectNodeGeometryTriangles;
	scene->GetGeometry(reflectNodeGeometryTriangles, item.node->GetGeometryID());
	if (reflectNodeGeometryTriangles)
	{
		//First normal of the geometry is used as the reflection normal. Should cause no problems for planar geometries. Any better idea?
		if (reflectNodeGeometryTriangles->vertices.empty())
			return;
		Vector3 reflectionPlanePoint(reflectNodeGeometryTriangles->vertices.front());
		Vector3 reflectionNormal;
		if (reflectNodeGeometryTriangles->normals.empty())
		{
			std::vector<GeometryTriangles::Index> triangulated;
			reflectNodeGeometryTriangles->Triangulate(triangulated, true);
			if (triangulated.size()<3)
				return;
			else
			{
				Vector3 v1(reflectNodeGeometryTriangles->vertices.at(triangulated.at(0)));
				Vector3 v2(reflectNodeGeometryTriangles->vertices.at(triangulated.at(1)));
				Vector3 v3(reflectNodeGeometryTriangles->vertices.at(triangulated.at(2)));
				reflectionNormal = ((v2 - v1).Cross(v3 - v1)).Normalize();
			}
		}
		else
			reflectionNormal=reflectNodeGeometryTriangles->normals.front();
		
		reflectionNormal = sceneShadow->GetWorldMatrix(item.node->GetID())->TransformNormal(reflectionNormal).Normalize();
		reflectionPlanePoint = sceneShadow->GetWorldMatrix(item.node->GetID())->TransformCoord(reflectionPlanePoint);

		glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

		Vector4 clipPlane(Vector4::PlaneFromPointNorm(reflectionPlanePoint, reflectionNormal));

		//There is a need to display the reflections correctly even on the back side of the mirror.
		//If the camera is in the half-space behind the mirror, we flip the reflection normal.
		if (clonedCamera)
		{
			Matrix clonedCamWorldMat(clonedCameraScene->GetWorldTransMatrix(clonedCamera->GetID()));
			Vector3 cameraPosition(clonedCamWorldMat.ExtractTranslation());
			cameraPosition+=clonedCamWorldMat.ExtractRotation().TransformCoord(clonedCamera->GetOffset()+clonedCamera->GetEyeAxis()*clonedCamera->GetEyeSeparation());
			if (clipPlane.PlaneDotCoord(cameraPosition) < 0.0f)
			{
				reflectionNormal = -reflectionNormal;
				clipPlane = Vector4::PlaneFromPointNorm(reflectionPlanePoint, reflectionNormal);
			}
		}

		Matrix reflectionMatrix(Matrix::MirrorReflect(reflectionPlanePoint, reflectionNormal));

		//Despite the name, we draw only into the depth buffer, because it is overridden by the glColorMask above
		drawFlatMirrorGeometryIntoDepthAndColorBuffer(mirrorList, item.node->GetID());
		
		//Setup the stencil buffer
		glClearStencil(0);
		glClear(GL_STENCIL_BUFFER_BIT);
		glEnable(GL_STENCIL_TEST); //comment out for debugDraw
		if (GLEW_EXT_stencil_two_side)
		{
			glEnable(GL_STENCIL_TEST_TWO_SIDE_EXT);

			glActiveStencilFaceEXT(GL_FRONT);
			glStencilFunc(GL_ALWAYS, 1, ~0);
			glStencilOp(GL_ZERO, GL_ZERO, GL_REPLACE);

			glActiveStencilFaceEXT(GL_BACK);
			glStencilFunc(GL_ALWAYS, 1, ~0);
			glStencilOp(GL_ZERO, GL_KEEP, GL_REPLACE);
		}
		else
		{
			glStencilFunc(GL_ALWAYS, 1, ~0);
			glStencilOp(GL_ZERO, GL_ZERO, GL_REPLACE);
		}

		//Mirrors can be created as more complex models than just a plane. For example an extruded profile with a small extrusion depth.
		//Faces in such a model are drawn in an arbitrary order (they are not ordered from back to front), so the faces lying on the
		//back side would fail the zfail stencilOp test and would clear the correct stencil values already drawn by the closer mirror faces.
		//We do not want the zFail test to fail for geometry of one mirror.
		//But multiple mirrors can occlude each other, thus it is desirable for them to fail the zFail test when they occlude.
		//To fix the behavior of zFail fail in a single mirror, the GL_STENCIL_TEST_TWO_SIDE_EXT extension has been used. The stencil test
		//for back faces is set up not to modify the stencil buffer if the back face z-fails. Simply enabling backface culling is out of
		//the question unfortunately, because that would require correct orientation of mirrors in the models, which is not always the case.
		//See the scene vrut\trunk\data\test_scene\mirrors.fhs to verify correctness of mirror rendering.

		//Draw mirror geometry into the stencil buffer
		glDepthMask(GL_FALSE);
		drawFlatMirrorStencilAndSetupClippingPlane(item, reflectionMatrix, clipPlane);
		glDepthMask(GL_TRUE);

		//Restore normal drawing and draw only into the fragments selected by the stencil.
		//Previous depth values would interfere with the next drawing step.

		glClear(GL_DEPTH_BUFFER_BIT);
		glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
		
		glStencilFunc(GL_EQUAL, 1, ~0);
		glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
		if (GLEW_EXT_stencil_two_side)
		{
			glActiveStencilFaceEXT(GL_FRONT);
			glStencilFunc(GL_EQUAL, 1, ~0);
			glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
		}

		//Front face switching avoids the unwanted normal flip in the reflected scene.
		glFrontFace(GL_CW);

		//We draw the whole reflected scene
		drawSceneReflection(reflectionMatrix, *item.node, lights, clonedCamera->GetID(), Vector4::PlaneFromPointNorm(reflectionPlanePoint, reflectionNormal));

		glFrontFace(GL_CCW);
		glDisable(GL_STENCIL_TEST);
		if (GLEW_EXT_stencil_two_side)
			glDisable(GL_STENCIL_TEST_TWO_SIDE_EXT);
		glDisable(GL_CLIP_PLANE0);
		//glDisable(GL_CULL_FACE);
		//Previous depth values would interfere with the next drawing step.
		glClear(GL_DEPTH_BUFFER_BIT);
	}
}

void RenderGlModule::drawReflectiveGeometry(SceneShadow::ItemsList& itemsList, const std::vector<LightInfo> &lights)
{
	//Draw reflective geometry
	int ccw=-1;

	for (auto &node : itemsList)
	{
		int ccwt=(node.matrix.IsMirror())?GL_CCW:GL_CW;
		if (ccw!=ccwt)
		{
			ccw=ccwt;
			glFrontFace(ccw);
		}

		//Draw node
		drawFlatMirrorReflection(node, itemsList, lights);
	}

	//Depth information from the reflection rendering must not interfere with the regular scene rendering, which will follow.
	//So we clear the depth buffer. But there is a catch. We must keep the depth of the mirrors itself, otherwise they would be overdrawn
	//by geometry which is actually behind the mirror. So we render the mirror geometry again in the depth buffer.
	//Note that glClear is not affected by stenciling, so drawing the mirror geometry again probably can not be avoided.
	//Originally, the color buffer was not involved here at all, but the later support for semi reflective mirrors required the write
	//of blended geometry also into the color buffer. It is done in two passes (in case we see more than one mirror).
	//First only the depth is drawn and then also the color. Otherwise the material of the mirror geometry could be seen through a closer mirror.
	if (itemsList.size() < 2)
	{
		drawFlatMirrorGeometryIntoDepthAndColorBuffer(itemsList);
	}
	else
	{
		glColorMask(GL_FALSE,GL_FALSE, GL_FALSE, GL_FALSE);
		drawFlatMirrorGeometryIntoDepthAndColorBuffer(itemsList);
		glColorMask(GL_TRUE,GL_TRUE, GL_TRUE, GL_TRUE);
		drawFlatMirrorGeometryIntoDepthAndColorBuffer(itemsList);
	}
}

void RenderGlModule::drawSolidGeometry(SceneShadow::ItemsList& itemsList, bool reverseFaceWinding, NODE_ID doNotDrawMe)
{
	//Draw solid geometry
	int ccw=(reverseFaceWinding)?GL_CW:GL_CCW;
	glFrontFace(ccw);
	bool pushMatrix=false;
	for (auto &node : itemsList)
	{
		if ((node.node->GetID() == doNotDrawMe))
			continue;
		pushMatrix=(lastMatrix!=node.matrix);
		if (pushMatrix)
		{
			glLoadMatrixf((node.matrix*camMatInverse)._m);
			lastMatrix=node.matrix;
			int ccwt;

			if (reverseFaceWinding)
				ccwt=(node.matrix.IsMirror())?GL_CCW:GL_CW;
			else
				ccwt=(node.matrix.IsMirror())?GL_CW:GL_CCW;
			if (ccw!=ccwt)
			{
				ccw=ccwt;
				glFrontFace(ccw);
			}
		}

		if (node.node->GetNodeProperties()&SceneNodeShadow::FLAT_REFLECTION)//Support for semi-reflective mirrors
			glEnable(GL_BLEND);

		if (node.node->GetNodeProperties()&SceneNodeShadow::TEX_GEN_REFLECTION)
			startViewDependentCubemapReflectionCore();

		///Draw node
		if (useVBO && vboSupported)
			drawVBO(node.node);
		else
			drawDispList(node.node);

		if (node.node->GetNodeProperties()&SceneNodeShadow::TEX_GEN_REFLECTION)
			stopViewDependentCubemap();

		if (node.node->GetNodeProperties()&SceneNodeShadow::FLAT_REFLECTION)//Support for semi-reflective mirrors
			glDisable(GL_BLEND);
	}
}

void RenderGlModule::drawTransparentGeometry(SceneShadow::ItemsList& blendedItemsList, bool reverseFaceWinding, bool useCameraMatrix)
{
	if (blendedItemsList.empty())
		return;
	//Draw transparent geometry
	OpenGLStateTracker oglStateStacker;
	int ccw=(reverseFaceWinding)?GL_CW:GL_CCW;
	glFrontFace(ccw);
#if !defined(_MSC_VER) || (_MSC_VER >= 1400)
	for (SceneShadow::ItemsList::const_reverse_iterator nodeIt = blendedItemsList.rbegin(); nodeIt != blendedItemsList.rend(); ++nodeIt)
#else
	for (SceneShadow::ItemsList::reverse_iterator nodeIt = blendedItemsList.rbegin(); nodeIt != blendedItemsList.rend(); ++nodeIt)
#endif
	{
		if (lastMatrix != nodeIt->matrix)
		{
			if (useCameraMatrix)
				glLoadMatrixf((nodeIt->matrix*camMatInverse)._m);
			else
				glLoadMatrixf((nodeIt->matrix)._m);
			
			lastMatrix=nodeIt->matrix;
			int ccwt;

			if (reverseFaceWinding)
				ccwt=(nodeIt->matrix.IsMirror())?GL_CCW:GL_CW;
			else
				ccwt=(nodeIt->matrix.IsMirror())?GL_CW:GL_CCW;
			if (ccw!=ccwt)
			{
				ccw=ccwt;
				glFrontFace(ccw);
			}
		}
		if (nodeIt->node->GetNodeProperties()&SceneNodeShadow::ALPHA_TEST)  //Support for alpha value based masking
		{
			oglStateStacker.disableBlend();
			//We disable blending for alpha masks to avoid blending on the interpolated-alpha edges
			//We can use alpha to coverage if it is available, to anti alias those alpha tested edges.
			if (alphaToCoverage && GLEW_ARB_multisample && (antiAliasingMethod!=RenderTarget::NONE))
			{
				oglStateStacker.disableAlphaTest();
				oglStateStacker.enableAlphaToCoverage();
			}
			else
			{
				oglStateStacker.setAlphaFunc(GL_GREATER, 0.5f);
				oglStateStacker.enableAlphaTest();
				oglStateStacker.disableAlphaToCoverage();
			}
		}
		else
		{
			oglStateStacker.enableBlend();
			oglStateStacker.disableAlphaTest();
			oglStateStacker.disableAlphaToCoverage();
		}

		if (nodeIt->node->GetNodeProperties()&SceneNodeShadow::TEX_GEN_REFLECTION)
			startViewDependentCubemapReflectionCore();

		///Draw node
		if (useVBO && vboSupported)
			drawVBO(nodeIt->node);
		else
			drawDispList(nodeIt->node);

		if (nodeIt->node->GetNodeProperties()&SceneNodeShadow::TEX_GEN_REFLECTION)
			stopViewDependentCubemap();
	}
	oglStateStacker.disableBlend();
	oglStateStacker.disableAlphaTest();
	oglStateStacker.disableAlphaToCoverage();
}

wxString RenderGlModule::GetDesc() const
{
	return wxT("Create window and render given scene to it by using OpenGL");
}

bool RenderGlModule::IsExtensionSupported(const char * szTargetExtension)
{
	const unsigned char *pszExtensions = NULL;
	const unsigned char *pszStart;
	unsigned char *pszWhere, *pszTerminator;

	// Extension names should not have spaces
	pszWhere = (unsigned char *) strchr(szTargetExtension, ' ');
	if (pszWhere || *szTargetExtension == '\0')
		return false;

	// Get Extensions String
	pszExtensions = glGetString(GL_EXTENSIONS);
	if (!pszExtensions)
		///Error getting extensions
		return false;

	// Search The Extensions String For An Exact Copy
	pszStart = pszExtensions;
	for (;;)
	{
		pszWhere = (unsigned char *) strstr((const char *) pszStart, szTargetExtension);
		if (!pszWhere)
			break;
		pszTerminator = pszWhere + strlen(szTargetExtension);
		if (pszWhere == pszStart || *(pszWhere - 1) == ' ')
			if (*pszTerminator == ' ' || *pszTerminator == '\0')
				return true;
		pszStart = pszTerminator;
	}
	return false;
}

void RenderGlModule::logOpenGLDetailsData()
{
	if (!openGLDetailsDataLogged)
	{
		LOGVERBOSE(wxT("OpenGL extensions:")+wxString::FromAscii((char*)glGetString(GL_EXTENSIONS)));
		LOG(wxString::Format(wxT("OpenGL vendor: ") + wxString::FromAscii((char*)glGetString(GL_VENDOR)) + wxT(", renderer: ")
			+ wxString::FromAscii((char*)glGetString(GL_RENDERER)) + wxT(", version: ") + wxString::FromAscii((char*)glGetString(GL_VERSION))));
		GLint redBitCount(0), greenBitCount(0), blueBitCount(0);
		glGetIntegerv(GL_RED_BITS, &redBitCount);
		glGetIntegerv(GL_GREEN_BITS, &greenBitCount);
		glGetIntegerv(GL_BLUE_BITS, &blueBitCount);
		LOGVERBOSE(wxString::Format(wxT("Number of bits per component in the framebuffer: red %d, green %d, blue %d"), redBitCount, greenBitCount, blueBitCount));
		openGLDetailsDataLogged = true;
	}

}

void RenderGlModule::deleteDualDVI30BitColorsProgram()
{
	if (dualDVI30BitColorsProgramHandler.program != GL_ID_NONE)
		glDeleteProgram(dualDVI30BitColorsProgramHandler.program);
	if (dualDVI30BitColorsProgramHandler.fragmentShader != GL_ID_NONE)
		glDeleteShader(dualDVI30BitColorsProgramHandler.fragmentShader);
	if (dualDVI30BitColorsProgramHandler.vertexShader != GL_ID_NONE)
		glDeleteShader(dualDVI30BitColorsProgramHandler.vertexShader);
}

void RenderGlModule::initDualDVI30BitColorsProgram()
{
	if (!dualDVI30BitColorsProgramInitialized && shaderSupported)
	{
		dualDVI30BitColorsProgramInitialized = true;

		wxFFile fileV(KERNEL->GetAppPath()+wxT("DualDVI30BitColors.vert"));
		wxFFile fileF(KERNEL->GetAppPath()+wxT("DualDVI30BitColors.frag"));

		if (!fileV.IsOpened() || !fileF.IsOpened())
		{
			LOGERROR(wxT("Unable to locate the 30 bit rendering shaders"));
			return;
		}

		fileV.ReadAll(&dualDVI30BitColorsProgram.vsSource);
		fileF.ReadAll(&dualDVI30BitColorsProgram.fsSource);

		dualDVI30BitColorsProgram.paramList.clear();

		dualDVI30BitColorsProgram.type = ShaderProgram::LANG_GLSL;
		ShaderParam shaderParam(wxT("image"));
		shaderParam.type = ShaderParam::SAMPLER;
		shaderParam.iVal = 0;
		dualDVI30BitColorsProgram.paramList.push_back(shaderParam);

		ShaderProgram *shader = &dualDVI30BitColorsProgram;

		// Create Shader And Program Objects
		dualDVI30BitColorsProgramHandler.program = glCreateProgramObjectARB();
		dualDVI30BitColorsProgramHandler.fragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
		dualDVI30BitColorsProgramHandler.vertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
		// Load Shader Sources
		wxCharBuffer buff = shader->fsSource.ToAscii();
		const char * fss = buff.data();
		wxCharBuffer buff2 = shader->vsSource.ToAscii();
		const char * vss = buff2.data();
		glShaderSourceARB(dualDVI30BitColorsProgramHandler.fragmentShader, 1, (const GLcharARB **)(&fss), NULL);
		glShaderSourceARB(dualDVI30BitColorsProgramHandler.vertexShader, 1, (const GLcharARB **)(&vss), NULL);
		// Compile The Shaders
		glCompileShaderARB(dualDVI30BitColorsProgramHandler.fragmentShader);
		glCompileShaderARB(dualDVI30BitColorsProgramHandler.vertexShader);
		// Attach The Shader Objects To The Program Object
		glAttachObjectARB(dualDVI30BitColorsProgramHandler.program, dualDVI30BitColorsProgramHandler.fragmentShader);
		glAttachObjectARB(dualDVI30BitColorsProgramHandler.program, dualDVI30BitColorsProgramHandler.vertexShader);
		// Link The Program Object
		glLinkProgramARB(dualDVI30BitColorsProgramHandler.program);
		glUseProgramObjectARB(dualDVI30BitColorsProgramHandler.program);

		///Info message
		int infoLogLength = 0;
		glGetObjectParameterivARB(dualDVI30BitColorsProgramHandler.program, GL_OBJECT_INFO_LOG_LENGTH_ARB, &infoLogLength);
		char * buffer = new char[infoLogLength];
		glGetInfoLogARB(dualDVI30BitColorsProgramHandler.program, infoLogLength, 0, buffer);
		wxString info(buffer, wxConvUTF8);
		delete [] buffer;
		if (!info.IsEmpty())
			LOG(wxT("<RenderGlModule::initDualDVI30BitColorsProgram>Shader program message:\n") + info);

		///Cache param locations and textures
		for (auto &par : shader->paramList)
		{
			ShaderParamID param(glGetUniformLocationARB(dualDVI30BitColorsProgramHandler.program, par.name.ToAscii()));
			dualDVI30BitColorsProgramHandler.paramList.push_back(param);
		}
		glUseProgramObjectARB(0);
	}
}

void RenderGlModule::resizeDualDVI30BitColorsProjection()
{
	int w, h;
	GLWindow *win=KERNEL->renderManager->GetGLWindow(this);
	if (!win || !scene)
		return;
	win->GetClientSize(&w, &h);

	smart_pointer<Camera> camera;
	scene->GetNode(camera, cameraID);
	if (camera)
	{
		//Only if the window truly changes dimensions. Changing the projection when only the projection changes is undesirable.
		if (((unsigned int)w != windowWidth) || ((unsigned int)h != windowHeight))
		{
			camera->WriteLock();
			//LOG(wxString::Format(wxT("RenderGlModule::resizeDualDVI30BitColorsProjection before SetProjectionParams camID=%lu, w=%i, h=%i"), (unsigned long)cameraID, w, h));
			if (dualDVI30BitColors)
				scene->SetProjectionParams(cameraID, camera->GetWidth() / 2, camera->GetHeight());
			else
				scene->SetProjectionParams(cameraID, camera->GetWidth(), camera->GetHeight());
			camera->WriteUnlock();
		}
	}

	windowWidth = (unsigned int)w;
	windowHeight = (unsigned int)h;
}

void RenderGlModule::setDualDVI30BitColorsProjection()
{
	if (!scene)
		return;

	smart_pointer<Camera> camera;
	scene->GetNode(camera, cameraID);
	if (camera)
	{
		camera->WriteLock();
		//LOG(wxString::Format(wxT("RenderGlModule::resizeDualDVI30BitColorsProjection before SetProjectionParams camID=%lu, w=%i, h=%i"), (unsigned long)cameraID, camera->GetWidth(), camera->GetHeight()));
		if (dualDVI30BitColors)
			scene->SetProjectionParams(cameraID, camera->GetWidth() / 2, camera->GetHeight());
		else
			scene->SetProjectionParams(cameraID, camera->GetWidth() * 2, camera->GetHeight());
		camera->WriteUnlock();
	}
}

bool RenderGlModule::Initialize()
{
	if (!initialized)
	{
		GLCHECKERROR(wxT("<RenderGlModule::Initialize>"));
		glClearDepth(1.0f);
		glEnable(GL_DEPTH_TEST);
		glDepthFunc(GL_LEQUAL);
		glShadeModel(GL_SMOOTH);
		glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
		glGetIntegerv(GL_MAX_CLIP_PLANES, &maxClippingPlanes);
		//TODO: the slowest solution!
		glEnable(GL_NORMALIZE);
		if (wireframe)
			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		else
			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

		if (lighting)
			glEnable(GL_LIGHTING);
		else
			glDisable(GL_LIGHTING);

		if (backFaceCull)
			glEnable(GL_CULL_FACE);
		else
			glDisable(GL_CULL_FACE);

		//Init and draw headlight
		float ambient[] = { 0.2f, 0.2f, 0.2f, 1.0f };
		float diffuse[] = { 0.5f, 0.5f, 0.5f, 1.0f };
		glLightfv(GL_HEADLIGHT, GL_AMBIENT, ambient);
		glLightfv(GL_HEADLIGHT, GL_DIFFUSE, diffuse);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		GLfloat lpos[4] = { 0, 0, 0, 1 };
		glLightfv(GL_HEADLIGHT, GL_POSITION, lpos);
		if (headlight)
			glEnable(GL_HEADLIGHT);
		else
			glDisable(GL_HEADLIGHT);

		checkExtSupport();

		initDualDVI30BitColorsProgram();

		setupAAGUI();

		logOpenGLDetailsData();

		if (GLEW_EXT_framebuffer_object)
		{
			if (renderTarget.GetTextureAttachmentOfType(GL_COLOR_ATTACHMENT0_EXT))
				updateMultiSampledRenderTarget();
			updateFrameBufferObjectBinds();
		}
		if (SetSwapInterval(useVSync?1:0))
			vSyncEnabled=useVSync;
		initialized = true;
	}

	if (!sceneInitialized && scene)
	{
        GLWindow* _w = KERNEL->renderManager->GetGLWindow(this);
        if (_w)
        {
            _w->GetClientSize((int*)&windowWidth, (int*)&windowHeight);
        }
        else
        {
            windowWidth = 640;
            windowHeight = 480;
        }

		if (GLEW_EXT_framebuffer_object)
		{
			//If we are part of a render pass chain or this module wants to use FBOs, switch to FBO render target and add the attachments to the FBO.
			//Also check if there is not an attachment of such type already created. We do not want to add it again when the renderer gets reinitialized.
			if ((drawUsingFBO || renderTargetMapMember) && !renderTarget.GetRenderBufferAttachmentOfType(GL_COLOR_ATTACHMENT0_EXT))
			{
				updateAllSingleSampledRenderTargets(windowWidth, windowHeight);
				updateAllMultiSampledRenderTargets(windowWidth, windowHeight);
				updateFrameBufferObjectBinds();
			}
		}
		else
		{
			LOGWARNING(wxT("<RenderGlModule::Initialize>Yer olde GPU chippe does not support frame buffer objects. Try updating thy drivers. Many advanced rendering techniques will be unavailable."));
			ENABLE_PARAM_GUI_CONTROL(drawUsingFBOParamID, false);
			SET_PARAM(drawUsingFBOParamID, wxT("0"));
		}

		if (renderTargetMapMember)
		{
			ENABLE_PARAM_GUI_CONTROL(drawUsingFBOParamID, false);
		}

		wxStopWatch sw;
		clearCachedGeometry();

		size_t maxGeoID = scene->GetGeometryMaxID();
		size_t maxMatID = scene->GetMaterialMaxID();

		resetGeometries.resize(maxGeoID, false);
		resetMaterials.resize(maxMatID, false);
		resetGeometry = true;
		resetMaterial = true;

		///Resize arrays
		if (maxGeoID > VBOIDs.size())
			VBOIDs.resize(maxGeoID);
		if (maxGeoID > dispListGeomIDs.size())
			dispListGeomIDs.resize(maxGeoID, GL_ID_NONE);
		if (maxMatID > dispListMatIDs.size())
			dispListMatIDs.resize(maxMatID, GL_ID_NONE);
		sceneInitialized = true;
		LOGDEBUG(wxString::Format(wxT("<RenderGlModule>Geometry nodes cached in %.3f secs"), 0.001f * sw.Time()));
	}

	return initialized && sceneInitialized;
}

void RenderGlModule::updateAllSingleSampledRenderTargets(GLsizei w, GLsizei h)
{
	updateSingleSampledRenderTarget(w, h);
	if (openVR) {
		if (renderTargetR == nullptr)
			renderTargetR = new RenderTarget();

		updateSingleSampledRenderTarget(w, h, RIGHT_EYE);
	}
}

void RenderGlModule::updateAllMultiSampledRenderTargets(GLsizei w, GLsizei h)
{
	updateMultiSampledRenderTarget(w, h);
	if (openVR) {
		if (multiSampledRenderTargetR == nullptr)
			multiSampledRenderTargetR = new RenderTarget();

		updateMultiSampledRenderTarget(w, h, RIGHT_EYE);
	}
}

void RenderGlModule::updateSingleSampledRenderTarget(GLsizei w, GLsizei h, int openVREyeID)
{
	RenderTarget *rt;
	if (openVREyeID == RIGHT_EYE)
		rt = renderTargetR;
	else
		rt = &renderTarget;

	if (w == -1 && h == -1)
	{
		const RenderTarget::TextureAttachment *a(renderTarget.GetTextureAttachmentOfType(GL_COLOR_ATTACHMENT0_EXT));
		if (a)
		{
			w = a->params.GetWidth();
			h = a->params.GetHeight();
		}
		else
		{
			LOGWARNING(wxT("<RenderGlModule::updateSingleSampledRenderTarget>Unfortunate event. Width and height not specified and I do not know how to obtain these values."));
			return;
		}
	}

	rt->DeleteAllTextureAttachments();

	GLenum type(GL_UNSIGNED_BYTE);
	if (fboInternalFormat == GL_RGBA8)
	{
		type = GL_UNSIGNED_BYTE;
	}
	else if (fboInternalFormat == GL_RGBA16)
	{
		type = GL_UNSIGNED_SHORT;
	}
	else if (fboInternalFormat == GL_RGBA16F_ARB)
	{
		type = GL_HALF_FLOAT_ARB;
	}
	else if (fboInternalFormat == GL_RGBA32F_ARB)
	{
		type = GL_FLOAT;
	}
	rt->AddTextureAttachment(RenderTarget::TextureAttachmentParams(w, h, fboInternalFormat, GL_COLOR_ATTACHMENT0_EXT, GL_RGBA, type));

	if (GLEW_EXT_packed_depth_stencil)
	{
		RenderTarget::AttachmentPointVector attachmentPointVector;
		attachmentPointVector.push_back(GL_DEPTH_ATTACHMENT_EXT);
		attachmentPointVector.push_back(GL_STENCIL_ATTACHMENT_EXT);
		RenderTarget::RenderBufferAttachmentParams rbap(w, h, GL_DEPTH24_STENCIL8_EXT, 1, 1, attachmentPointVector, RenderTarget::NONE);
		rt->AddRenderBufferAttachment(rbap);
	}
	else
	{
		LOGWARNING(wxT("<RenderGlModule::updateSingleSampledRenderTarget>Packed depth stencil not supported by your drivers or GPU. Creating separate depth and stencil attachments. Pray that your GPU creates a complete FBO. It is now mid 2010 and separate depth and stencil is still not supported (at least on NV)."));
		rt->AddRenderBufferAttachment(RenderTarget::RenderBufferAttachmentParams(w, h, GL_DEPTH_COMPONENT, 1, 1, GL_DEPTH_ATTACHMENT_EXT, RenderTarget::NONE));
		rt->AddRenderBufferAttachment(RenderTarget::RenderBufferAttachmentParams(w, h, GL_STENCIL_INDEX8_EXT, 1, 1, GL_STENCIL_ATTACHMENT_EXT, RenderTarget::NONE)); //TODO: Find a working alternative to GL_DEPTH24_STENCIL8_EXT. GL_STENCIL_INDEX8_EXT does not work in this config.
		if (rt->CheckFrameBufferObjectStatus() != GL_FRAMEBUFFER_COMPLETE_EXT)
		{
			LOGWARNING(wxT("<RenderGlModule::updateSingleSampledRenderTarget>FBO is not complete. Switching off FBO usage. Next time, pray harder."));
			SET_PARAM(drawUsingFBOParamID, wxT("0"));
			ENABLE_PARAM_GUI_CONTROL(drawUsingFBOParamID, false);
			drawUsingFBO = false;
		}
	}
}

void RenderGlModule::updateMultiSampledRenderTarget(GLsizei w, GLsizei h, int openVREyeID)
{
	RenderTarget *msrt;
	if (openVREyeID == RIGHT_EYE)
		msrt = multiSampledRenderTargetR;
	else
		msrt = &multiSampledRenderTarget;

	if (w == -1 && h == -1)
	{
		const RenderTarget::TextureAttachment *a(renderTarget.GetTextureAttachmentOfType(GL_COLOR_ATTACHMENT0_EXT));
		if (a)
		{
			w = a->params.GetWidth();
			h = a->params.GetHeight();
		}
		else
		{
			LOGWARNING(wxT("<RenderGlModule::updateMultiSampledRenderTarget>Unfortunate event. Width and height not specified and I do not know how to obtain these values."));
			return;
		}
	}

	int tryColorSampleCount(antiAliasingColorSampleCount);
	int tryCoverageSampleCount(antiAliasingCoverageSampleCount);
	bool reduceSamples = true;
	GLenum ret(GL_FRAMEBUFFER_COMPLETE_EXT);
	for (; reduceSamples;)
	{
		msrt->DeleteAllRenderBufferAttachments();
		//Adding color attachment
		msrt->AddRenderBufferAttachment(RenderTarget::RenderBufferAttachmentParams(w, h, fboInternalFormat, tryColorSampleCount, tryCoverageSampleCount, GL_COLOR_ATTACHMENT0_EXT, antiAliasingMethod));

		//Adding depth and stencil attachments
		if (GLEW_EXT_packed_depth_stencil)
		{
			RenderTarget::AttachmentPointVector attachmentPointVector;
			attachmentPointVector.push_back(GL_DEPTH_ATTACHMENT_EXT);
			attachmentPointVector.push_back(GL_STENCIL_ATTACHMENT_EXT);
			RenderTarget::RenderBufferAttachmentParams rbap(w, h, GL_DEPTH24_STENCIL8_EXT, tryColorSampleCount, tryCoverageSampleCount, attachmentPointVector, antiAliasingMethod);
			ret = msrt->AddRenderBufferAttachment(rbap);
		}
		else
		{
			LOGWARNING(wxT("<RenderGlModule::updateMultiSampledRenderTarget>Oh no. Packed depth stencil not supported by your drivers or GPU. Creating separate depth and stencil attachments. Pray that your GPU creates a complete FBO. It is now mid 2010 and separate depth and stencil is still not supported (at least on NV)."));
			msrt->AddRenderBufferAttachment(RenderTarget::RenderBufferAttachmentParams(w, h, GL_DEPTH_COMPONENT, tryColorSampleCount, tryCoverageSampleCount, GL_DEPTH_ATTACHMENT_EXT, antiAliasingMethod));
			ret = msrt->AddRenderBufferAttachment(RenderTarget::RenderBufferAttachmentParams(w, h, GL_STENCIL_INDEX8_EXT, tryColorSampleCount, tryCoverageSampleCount, GL_STENCIL_ATTACHMENT_EXT, antiAliasingMethod)); //TODO: Find a working alternative to GL_DEPTH24_STENCIL8_EXT. GL_STENCIL_INDEX8_EXT does not work in this config.
			if (ret != GL_FRAMEBUFFER_COMPLETE_EXT)
			{
				LOGWARNING(wxT("<RenderGlModule::updateMultiSampledRenderTarget>FBO is not complete. Switching off FBO usage. Next time, pray harder."));
				SET_PARAM(drawUsingFBOParamID, wxT("0"));
				ENABLE_PARAM_GUI_CONTROL(drawUsingFBOParamID, false);
				drawUsingFBO = false;
			}
		}
		bool highSamples(antiAliasingMethod == RenderTarget::COVERAGE_SAMPLING ? (tryColorSampleCount >= 2 || tryCoverageSampleCount >= 2) : (tryColorSampleCount >= 2));
		if (ret != GL_FRAMEBUFFER_COMPLETE_EXT && highSamples)
		{
			LOGWARNING(wxT("<RenderGlModule::updateMultiSampledRenderTarget>FBO is not complete. Trying to halve sample count."));
			reduceSamples = true;
			tryColorSampleCount /= 2;
			if (antiAliasingMethod == RenderTarget::COVERAGE_SAMPLING)
				tryCoverageSampleCount /= 2;
			glGetError(); //Eat the error
		}
		else
			reduceSamples = false;
	}
	if (ret == GL_FRAMEBUFFER_COMPLETE_EXT && (tryColorSampleCount != antiAliasingColorSampleCount || tryCoverageSampleCount != antiAliasingCoverageSampleCount))
	{
		LOGWARNING(wxT("<RenderGlModule::updateMultiSampledRenderTarget>Reducing sample count helped. Updating the sample count params."));
		SET_PARAM(antiAliasingColorSampleCountParamID, wxString::Format(wxT("%d"), tryColorSampleCount));
		SET_PARAM(antiAliasingCoverageSampleCountParamID, wxString::Format(wxT("%d"), tryCoverageSampleCount));
		antiAliasingColorSampleCount = tryColorSampleCount;
		antiAliasingCoverageSampleCount = tryCoverageSampleCount;
	}
}

void RenderGlModule::debugDraw(smart_pointer<Camera> camera)
{
	GLCHECKERROR(wxT("<RenderGLModule::test0>"));
	///Clear buffer even if nothing to render
	glClearColor(defaultClearColor.x, defaultClearColor.y, defaultClearColor.z, defaultClearColor.w);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	GLCHECKERROR(wxT("<RenderGLModule::test1>"));

	if (!camera)
		return;

	///Set projection matrix
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();

	GLCHECKERROR(wxT("<RenderGLModule::test2>"));

	int w=640, h=480;
	 GLWindow* wnd = KERNEL->renderManager->GetGLWindow(this);
    if (wnd != nullptr)
		wnd->GetClientSize(&w, &h);

	glViewport(0, 0, w, h);
	glLoadMatrixf(getProjectionMatrix(scene.getPtr(), camera, cameraLeft)._m);
	GLCHECKERROR(wxT("<RenderGLModule::test3>"));
	///Set view matrix
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadMatrixf(sceneShadow->GetWorldMatrix(camera->GetID())->InverseOrto()._m);
	SceneShadow::ItemsList itemsList;
	//scene->GetNodeIDs(itemsList, SceneNode::GEOMETRY);
/*
	std::deque<NODE_ID> nodes;
	bvh->GetGeometryNodeIDs(&nodes,bvh->GetRoot());
	for (std::deque<NODE_ID>::iterator i = nodes.begin(); i != nodes.end(); i++)
	{
		itemsList.push_back(*i);
	}*/

	GLCHECKERROR(wxT("<RenderGLModule::test>"));
	//get lights
	std::vector<LightInfo> lights;
	std::vector<smart_pointer<LightNode> > lightNodes;
	scene->GetNodes(lightNodes, SceneNode::LIGHT);
	for (auto &lightNode : lightNodes)
	{
		if (sceneShadow->IsActive(lightNode->GetID()))
			lights.push_back(LightInfo(lightNode, *sceneShadow->GetWorldMatrix(lightNode->GetID())));
	}

	drawLights(lights);

	drawSolidGeometry(itemsList);

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
	GLCHECKERROR(wxT("<RenderGLModule::debugdDraw>"));
}

Matrix RenderGlModule::getProjectionMatrix(Scene *scene, smart_pointer<Camera> camera, bool cameraLeft) 
{
    unsigned int width=640, height=480;
    GLWindow* wnd = KERNEL->renderManager->GetGLWindow(this);
    if (wnd != nullptr)
        wnd->GetClientSize((int*)&width, (int*)&height);

    const float aspectRatio = float(width) / float(height);
    Matrix tmp(scene->GetProjectionMatrix(camera->GetID(), aspectRatio, !cameraLeft));
	return tmp;
}

void RenderGlModule::drawIntoEnvironmentProbe(smart_pointer<EnvironmentProbe> &environmentProbe, const std::vector<LightInfo> &lights)
{
	GLboolean clipPlaneEnabled(glIsEnabled(GL_CLIP_PLANE0));
	if (clipPlaneEnabled)
		glDisable(GL_CLIP_PLANE0);

	glPushAttrib(GL_VIEWPORT_BIT | GL_TRANSFORM_BIT);

	smart_pointer<SceneNode> attachedNode;
	scene->GetNode(attachedNode, environmentProbe->GetAttachedNodeID());

	GLuint environmentProbeTextureID;
	//If the cube map render target just got created, let us insert it into the map, so that other nodes can use this cube map in a straightforward manner.
	if (scene->BindAndSetupEnvironmentProbe(environmentProbe->GetID(), environmentProbeTextureID))
	{
		wxString textureName(EnvironmentProbe::BuildEnvironmentProbeName(attachedNode));
		TexObject tex_object;
		tex_object.object = environmentProbeTextureID;
		tex_object.has_mipmaps = true;
		textureCubeIDMap.insert(TextureIDMap::value_type(textureName, tex_object));
	}

	//Set projection matrix
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();

	Camera *clonedCameraBackup((Camera*)clonedCamera->Clone());

	int dim(environmentProbe->GetTextureDimension());
	clonedCameraScene->SetProjectionParams(clonedCamera->GetID(), dim, dim, environmentProbe->GetNearPlaneDistance(), environmentProbe->GetFarPlaneDistance(), 90);

	glViewport(0, 0, dim, dim);

	Matrix projectionMatrix(getProjectionMatrix(clonedCameraScene, clonedCamera, cameraLeft));
	Matrix locTMTMatrix(*clonedCamera->GetTMTMatrix());
	glLoadMatrixf(((locTMTMatrix.Inverse()) * projectionMatrix)._m);
	
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();

	std::vector<bool> itemsMark;
	scene->MarkSubtree(attachedNode->GetID(), itemsMark, true);
	Vector3 probeCenter;
	probeCenter=scene->GetAABB(attachedNode->GetID()).Center();//TODO: it will be slow

	for (int j = 0; j < 6; j++)
	{
		Matrix alignedView;
		//OpenGL cube maps are designed to be consistent with DirectX cube maps, but image origin in DX is in the upper left corner and OGL considers the lower left corner as the origin,
		//so we need to flip the cube map faces diagonally. Except the Y cube faces.
		if (j != 2 && j != 3)
			alignedView.Scale(Vector3(-1, -1, 1));

		alignedView *= (cubeMapGenerationViewMatrix[j]);

		alignedView.Translate(probeCenter);
		clonedCameraScene->SetTransformation(clonedCamera->GetID(), alignedView);
		//Set view matrix
		Matrix camMat(clonedCameraScene->GetWorldTransMatrix(clonedCamera->GetID()));
		Matrix camMatInverseLoc = camMat.InverseOrto() * locTMTMatrix;
		glLoadMatrixf(camMatInverseLoc._m);

		//Cull scene
		SceneShadow::ItemsList itemsList, blendedItemsList, reflectiveItemsList, virtualCameraItemsList;
		Vector3 clonedCameraTranslation(clonedCameraScene->GetWorldTransMatrix(clonedCamera->GetID()).ExtractTranslation());
		bTree::CullingPlanes planes;
		planes.FromMatrixVPinv(projectionMatrix.Inverse()*camMat);
		sceneShadow->Cull(planes, *clonedCamera->GetBSphere(), clonedCameraTranslation, itemsList, blendedItemsList, reflectiveItemsList, backgroundItemsList, virtualCameraItemsList, renderingRootID);

		//filter out attachedNode subtree
		for (size_t i=itemsList.size(); i>0; --i)
		{
			NODE_ID nodeID=itemsList[i-1].node->GetID();
			if ((nodeID<itemsMark.size()) && (itemsMark[nodeID]))
				itemsList.erase(itemsList.begin()+i-1);
		}
		for (size_t i=blendedItemsList.size(); i>0; --i)
		{
			NODE_ID nodeID=blendedItemsList[i-1].node->GetID();
			if ((nodeID<itemsMark.size()) && (itemsMark[nodeID]))
				blendedItemsList.erase(blendedItemsList.begin()+i-1);
		}
		for (size_t i=reflectiveItemsList.size(); i>0; --i)
		{
			NODE_ID nodeID=reflectiveItemsList[i-1].node->GetID();
			if ((nodeID<itemsMark.size()) && (itemsMark[nodeID]))
				reflectiveItemsList.erase(reflectiveItemsList.begin()+i-1);
		}
		for (size_t i = virtualCameraItemsList.size(); i>0; --i)
		{
			NODE_ID nodeID = virtualCameraItemsList[i - 1].node->GetID();
			if ((nodeID<itemsMark.size()) && (itemsMark[nodeID]))
				virtualCameraItemsList.erase(virtualCameraItemsList.begin() + i - 1);
		}
		//sort for rendering
		if (sortGeometries==1)
			std::sort(itemsList.begin(), itemsList.end(), SceneShadow::ItemInfo::SortByDistance);
		else if (sortGeometries==2)
			std::sort(itemsList.begin(), itemsList.end(), SceneShadow::ItemInfo::SortByMaterialID);

		glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + j);

		glClearColor(defaultClearColor.x, defaultClearColor.y, defaultClearColor.z, defaultClearColor.w);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		drawBackground();

		drawLights(lights);

		drawSolidGeometry(reflectiveItemsList);
		drawSolidGeometry(itemsList);
		drawTransparentGeometry(blendedItemsList);

		if (shaderSupported)
			glUseProgramObjectARB(0);
	}

	glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT);
	glEnable(GL_TEXTURE_CUBE_MAP);
	glBindTexture(GL_TEXTURE_CUBE_MAP, environmentProbe->GetTextureAttachmentOfType(GL_COLOR_ATTACHMENT0_EXT)->object);

	glGenerateMipmapEXT(GL_TEXTURE_CUBE_MAP);

	glPopAttrib();

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();

	glPopAttrib();

	if (clipPlaneEnabled)
		glEnable(GL_CLIP_PLANE0);

	clonedCameraScene->SetProjectionParams(clonedCamera->GetID(), clonedCameraBackup->GetWidth(), clonedCameraBackup->GetHeight(), clonedCameraBackup->GetNearPlane(), clonedCameraBackup->GetFarPlane(), clonedCameraBackup->GetFOV(), clonedCameraBackup->GetUpFix());
	clonedCameraScene->SetTransformation(clonedCamera->GetID(), *clonedCameraBackup->GetLocalTransMatrix());
	delete clonedCameraBackup;
}

void RenderGlModule::updateEnvironmentProbes(Scene& scene, const std::vector<LightInfo> &lights, int openVREyeID)
{
	std::vector<smart_pointer<EnvironmentProbe> > environmentProbes;
	scene.GetEnvironmentProbes(environmentProbes);
	for (auto &environmentProbe : environmentProbes)
	{
		if (environmentProbe->IsEnabled())
		{
			drawIntoEnvironmentProbe(environmentProbe, lights);

			if (environmentProbe->GetSingleShot())
				scene.EnableEnvironmentProbe(environmentProbe->GetID(), false);
		}
	}
	updateFrameBufferObjectBinds(openVREyeID);
}

void RenderGlModule::drawIntoVirtualCamera(smart_pointer<Camera> &virtualCamera, const std::vector<LightInfo> &lights)
{
	GLboolean clipPlaneEnabled(glIsEnabled(GL_CLIP_PLANE0));
	if (clipPlaneEnabled)
		glDisable(GL_CLIP_PLANE0);

	glPushAttrib(GL_VIEWPORT_BIT | GL_TRANSFORM_BIT);

	//If the virtual camera render target just got created, let us insert it into the map, so that other nodes can use this the virtual camera in a straightforward manner.
	VirtualCamera *virtualCameraTarget = NULL;
	VirtualCamerasMap::const_iterator it = virtualCamerasMap.find(virtualCamera->GetID());
	if (it != virtualCamerasMap.end())
		virtualCameraTarget=it->second;
	else
	{
		virtualCameraTarget = new VirtualCamera(virtualCamera->GetWidth(), virtualCamera->GetHeight());
		virtualCamerasMap.insert(VirtualCamerasMap::value_type(virtualCamera->GetID(), virtualCameraTarget));
		//		std::cout<<"Created virtual camera target "<<virtualCamera->GetWidth()<<" x "<<virtualCamera->GetHeight()<<"\n";
	}
	virtualCameraTarget->Bind();
	bool ret = virtualCameraTarget->SetupAttachments();
	if (ret)
	{
		wxString textureName(wxString::Format(wxT("virtualCameraID_%lu"), (unsigned long)virtualCamera->GetID()));
		TexObject tex_object;
		tex_object.object = virtualCameraTarget->GetTextureAttachmentOfType(GL_COLOR_ATTACHMENT0_EXT)->object;
		tex_object.has_mipmaps = true;
		texture2DIDMap.insert(TextureIDMap::value_type(textureName, tex_object));
	}

	//Set projection matrix
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();

	glViewport(0, 0, virtualCamera->GetWidth(), virtualCamera->GetHeight());
	// wrong aspect ratio used here previously! (from the window)
	// Matrix projectionMatrix(getProjectionMatrix(scene.getPtr(), virtualCamera));
	float aspect = virtualCamera->GetWidth()/(float)virtualCamera->GetHeight();
	Matrix projectionMatrix = scene.getPtr()->GetProjectionMatrix(virtualCamera->GetID(), aspect, false);

	glLoadMatrixf(projectionMatrix._m);

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();

	//Set view matrix
	Matrix camMatInverseBackup = camMatInverse;
	Matrix virtualCameraWorld(*sceneShadow->GetWorldMatrix(virtualCamera->GetID()));
	camMatInverse = virtualCameraWorld.InverseOrto();
	glLoadMatrixf(camMatInverse._m);
	lastMatrix = Matrix();

	//Cull scene
	SceneShadow::ItemsList itemsList, blendedItemsList, reflectiveItemsList, virtualCameraItemsList;
	Vector3 virtualCameraTranslation(virtualCameraWorld.ExtractTranslation());
	bTree::CullingPlanes planes;
	planes.FromMatrixVPinv(projectionMatrix.Inverse()*virtualCameraWorld);
	sceneShadow->Cull(planes, *virtualCamera->GetBSphere(), virtualCameraTranslation, itemsList, blendedItemsList, reflectiveItemsList, backgroundItemsList, virtualCameraItemsList, renderingRootID);

	//sort for rendering
	if (sortGeometries == 1)
		std::sort(itemsList.begin(), itemsList.end(), SceneShadow::ItemInfo::SortByDistance);
	else if (sortGeometries == 2)
		std::sort(itemsList.begin(), itemsList.end(), SceneShadow::ItemInfo::SortByMaterialID);

	glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);

	glClearColor(defaultClearColor.x, defaultClearColor.y, defaultClearColor.z, defaultClearColor.w);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	drawBackground();

	drawLights(lights);

	drawSolidGeometry(reflectiveItemsList);
	drawSolidGeometry(itemsList);
	drawTransparentGeometry(blendedItemsList);

	if (shaderSupported)
		glUseProgramObjectARB(0);

	glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT);
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, virtualCameraTarget->GetTextureAttachmentOfType(GL_COLOR_ATTACHMENT0_EXT)->object);

	glGenerateMipmapEXT(GL_TEXTURE_2D);

	if (0) {
		static int counter = 0;
		if (counter++ % 100 == 0) {
			// make a debug snapshot of the texture
			snapshotTexture(0, "debug.bmp");
		}
	}
	
	glPopAttrib();

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
	camMatInverse=camMatInverseBackup;
	lastMatrix = Matrix();

	glPopAttrib();

	if (clipPlaneEnabled)
		glEnable(GL_CLIP_PLANE0);
}

void RenderGlModule::updateVirtualCameras(Scene& scene, SceneShadow::ItemsList& itemsList, const std::vector<LightInfo> &lights, int openVREyeID)
{
	bool updateBinds = false;
	for (auto &node : itemsList)
	{
		smart_pointer<Material> mat;
		scene.GetMaterial(mat, node.node->GetMaterialID());
		if (!mat || !mat->shader)
			continue;
		NODE_ID virtualCameraID = NODE_ID_NONE;
		for (auto &param : mat->shader->paramList)
		{
			if (param.name == wxT("virtualCameraID"))
				virtualCameraID = (NODE_ID)param.iVal;
		}

		smart_pointer<Camera> camera;
		scene.GetNode(camera, virtualCameraID);
		if (camera)
		{
			drawIntoVirtualCamera(camera, lights);
			updateBinds = true;
		}
	}
	if  (updateBinds)
		updateFrameBufferObjectBinds(openVREyeID);
}

void RenderGlModule::drawTextOverlay(wxString text, float x, float y, float step)
{
	if (text.empty())
		return;
	/*LOG(text);
	return;*/
	glMatrixMode(GL_PROJECTION);
	// store the current projection matrix
	float W = windowWidth;
	float H = windowHeight;
	// initialize orthographic projection
	glLoadIdentity();
	//following command causes error 501
	gluOrtho2D(0.0f, W, 0.0f, H);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glTranslatef(x, y, 0.0f);
	//glPushAttrib(GL_CURRENT_BIT | GL_LIGHTING_BIT | GL_DEPTH_BUFFER_BIT);
	glPushAttrib(GL_ALL_ATTRIB_BITS);
	glDisable(GL_LIGHTING);
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_TEXTURE_CUBE_MAP);
	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T);
	glDisable(GL_TEXTURE_GEN_R);
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	glColor4f(1, 1, 1, 1);//color of the text
	std::shared_ptr<FontGlyphsTextureOpenGL> font_tex;
	// Do not create font inside display list
	{
		wxFont font;
		//font.SetFamily(wxFONTFAMILY_DEFAULT);
		font_tex = fontFactory.GetOrCreateFont(font);
	}
	if (!font_tex)
		return;

	TextLettersData letters(text, font_tex->GetGlyphs(), 10.0f, 1.0f, GeometryText::Justify_Begin, GeometryText::Justify_First);
	if (letters.GetLetterDesc().empty())
		return;

	float dims[3] = {
		float(font_tex->GetDim(0)),
		float(font_tex->GetDim(1)),
		1.0f
	};

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER, 0.1f);
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, font_tex->GetTexture());

	struct Vertex
	{
		Vector2 tex_coord;
		Vector3 normal;
		Vector3 vertex;
	};
	std::vector<Vertex> data(letters.GetLetterDesc().size() * 4);

	for (size_t c = 0; c < letters.GetLetterDesc().size(); c++)
	{
		float tex[3] = {
			float(letters.GetLetterDesc()[c].glyph % font_tex->GetDim(0)),
			float((letters.GetLetterDesc()[c].glyph / font_tex->GetDim(0)) % font_tex->GetDim(1)),
			float((letters.GetLetterDesc()[c].glyph / font_tex->GetDim(0)) / font_tex->GetDim(1))
		};

		data[c * 4 + 0].vertex = Vector3(letters.GetLetterDesc()[c].position.x, letters.GetLetterDesc()[c].position.y, 0.0f);
		data[c * 4 + 1].vertex = Vector3(letters.GetLetterDesc()[c].position.x + letters.GetRectSize(), letters.GetLetterDesc()[c].position.y, 0.0f);
		data[c * 4 + 2].vertex = Vector3(letters.GetLetterDesc()[c].position.x + letters.GetRectSize(), letters.GetLetterDesc()[c].position.y + letters.GetRectSize(), 0.0f);
		data[c * 4 + 3].vertex = Vector3(letters.GetLetterDesc()[c].position.x, letters.GetLetterDesc()[c].position.y + letters.GetRectSize(), 0.0f);
		data[c * 4 + 0].normal = Vector3(0.0f, 0.0f, 1.0f);
		data[c * 4 + 1].normal = Vector3(0.0f, 0.0f, 1.0f);
		data[c * 4 + 2].normal = Vector3(0.0f, 0.0f, 1.0f);
		data[c * 4 + 3].normal = Vector3(0.0f, 0.0f, 1.0f);
		data[c * 4 + 0].tex_coord = Vector2((0.0f + tex[0]) / dims[0], (0.0f + tex[1]) / dims[1]);
		data[c * 4 + 1].tex_coord = Vector2((1.0f + tex[0]) / dims[0], (0.0f + tex[1]) / dims[1]);
		data[c * 4 + 2].tex_coord = Vector2((1.0f + tex[0]) / dims[0], (1.0f + tex[1]) / dims[1]);
		data[c * 4 + 3].tex_coord = Vector2((0.0f + tex[0]) / dims[0], (1.0f + tex[1]) / dims[1]);
	}
	glInterleavedArrays(GL_T2F_N3F_V3F, 0, &data.front());

	glDrawArrays(GL_QUADS, 0, letters.GetLetterDesc().size() * 4);
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	glPopAttrib();		// Restore blend, alpha test and alpha ref

	// swap back to modelview matrix
	//glMatrixMode(GL_MODELVIEW);
	GLCHECKERROR(wxT("<RenderGLModule::drawTextOverlay>"));
}

void RenderGlModule::Draw()
{
	wxULongLong startTime=VRUTGetTime(), startDrawTime, prepTime=0, CullTime=0, GetLightTime=0, UpdateEnvTime=0, drawBVHTime=0, SortTime=0, RenderReflectiveTime=0, RenderSolidTime=0, RenderTransparentTime=0, currTime=0;
	startDrawTime=startTime;
	if (!scene)
		return;

#ifdef RENDER_GL_PROFILING
	glFinish();
#endif

	smart_pointer<Camera> camera;
	scene->GetNode(camera, cameraID);
	if (!camera)
	{
		glClearColor(0.2f, 0, 0, 0);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		return;
	}
	if (!Initialize())
	{
		glClearColor(0, 0.2f, 0, 0);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		return;
	}
	//LOG(wxString::Format(wxT("<RenderGlModule> Draw %lu"), (unsigned long)drawCount++));
	if (vSyncEnabled!=useVSync)
	{
		if (SetSwapInterval(useVSync?1:0))
			vSyncEnabled=useVSync;
	}


	updateFrameBufferObjectBinds();

	///Clear buffer even if nothing to render
	glClearColor(defaultClearColor.x, defaultClearColor.y, defaultClearColor.z, defaultClearColor.w);
	if (clearColorBuffer)
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	else
		glClear(GL_DEPTH_BUFFER_BIT);

	bool resizeHappened(false);
	int w=640, h=480;

    GLWindow* wnd = KERNEL->renderManager->GetGLWindow(this);
    if (wnd)
        wnd->GetClientSize(&w, &h);

	if (resizeUpdate)
	{
		resizeHappened = true;
		multiSampledRenderTarget.ResizeAttachments(w, h);
		resizeDualDVI30BitColorsProjection();
	}
	RenderModule::Draw();

	if (resizeHappened)
	{
		glClearColor(defaultClearColor.x, defaultClearColor.y, defaultClearColor.z, defaultClearColor.w);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	}

	vboRenderUpdated = false;
	///Check for scene changes and reinitialize modified nodes
	Reset();

	///Set projection matrix
	glMatrixMode(GL_PROJECTION);

	currTime=VRUTGetTime();
	prepTime+=currTime-startTime;
	startTime=currTime;

	//get lights
	std::vector<LightInfo> lights;
	std::vector<smart_pointer<LightNode> > lightNodes;
	sceneShadow->GetNodesActive(lightNodes, SceneNode::LIGHT);
	for (auto &lightNode : lightNodes)
		if (lightNode && sceneShadow->IsActive(lightNode->GetID()))
			lights.push_back(LightInfo((smart_pointer<LightNode>&)lightNode, *sceneShadow->GetWorldMatrix(lightNode->GetID())));

	currTime=VRUTGetTime();
	GetLightTime+=currTime-startTime;
	startTime=currTime;

	clonedCameraScene = new Scene(SCENE_ID_NONE, NULL);
	//Tonda
	Camera *newCam = new Camera(wxT("default_camera"), camera);
	NODE_ID clonedCameraID=clonedCameraScene->Insert(newCam, clonedCameraScene->GetRootID());
	clonedCameraScene->GetNode(clonedCamera, clonedCameraID);

	camMat = *sceneShadow->GetWorldMatrix(cameraID);

	///The original camera's world transform matrix contains within it a translation by the viewer offset, that should by right
	///be a part of the projection matrix. It has been, however, moved into the view matrix in order to make shaders simpler.
	///A side effect is that when copying the world transformation matrix from one camera to another (where the offset has already been copied)
	///you should remove the offset from the matrix being copied (by multiplying it by an inverse offset from the left), to avoid
	///adding the same offset the second time, when the copied scene updates the transformation hierarchy.
	clonedCameraScene->SetTransformation(clonedCameraScene->GetRootID(), camMat);
	/*LOG(wxT("Root matrix: ")+clonedCameraScene->GetWorldTransMatrix(clonedCameraScene->GetRootID())->ToString()
		+wxT(", Camera matrix: ")+clonedCameraScene->GetWorldTransMatrix(clonedCamera->GetID())->ToString());*/

	if (!clonedCamera)
		return;

	glViewport(0, 0, w, h);
	//LOG(wxString::Format(wxT("<RenderGlModule::Draw clonnedCamera width=%i, height=%i"), (int)w, (int)h));

	// TMTMatrix	- T*MT Matrix, part of generalized projection matrix (see camera.h updateProjection())
	//				- moved to view matrix to get correct reflections
	projectionMatrix = (getProjectionMatrix(clonedCameraScene, clonedCamera, cameraLeft));
	TMTMatrix = clonedCamera->GetTMTMatrix();

	glLoadMatrixf(((TMTMatrix->Inverse()) * projectionMatrix)._m);
	
	///Set view matrix
	glMatrixMode(GL_MODELVIEW);
	//glLoadMatrixf(clonedCameraScene->GetWorldTransMatrix(clonedCamera->GetID())->InverseOrto()._m);

	camMatInverse = camMat.InverseOrto() * *TMTMatrix;
	lastMatrix=Matrix();
	glLoadMatrixf(camMatInverse._m);
	//glLoadMatrixf(scene->GetWorldTransMatrix(cameraID)->InverseOrto()._m);

	updateEnvironmentProbes(*scene, lights);

	currTime=VRUTGetTime();
	UpdateEnvTime+=currTime-startTime;
	startTime=currTime;

	// lists used when hud support is enabled 
	SceneShadow::ItemsList hudItemsList, hudBlendedItemsList, hudReflectiveItemsList, hudBackgroundItemsList, hudVirtualCameraItemsList;
	
	Vector3 clonedCameraTranslation(clonedCameraScene->GetWorldTransMatrix(clonedCameraID).ExtractTranslation());
	if (cullingMethod&2)
		sceneShadow->Cull(clonedCameraTranslation, itemsList, blendedItemsList, reflectiveItemsList, backgroundItemsList, virtualCameraItemsList, renderingRootID);//ALL
	else
	{
		bTree::CullingPlanes planes;
		planes.FromMatrixVPinv(projectionMatrix.Inverse()*camMat);

		if (!mHudSupport) {
			sceneShadow->Cull(planes, *clonedCamera->GetBSphere(), clonedCameraTranslation, itemsList, blendedItemsList, reflectiveItemsList, backgroundItemsList, virtualCameraItemsList, renderingRootID);//VFC
		} else {
			// render HUD in a separate pass
			// cull the scene, first without the camera subtree!
			sceneShadow->Cull(planes, *clonedCamera->GetBSphere(), clonedCameraTranslation, itemsList, blendedItemsList, reflectiveItemsList, backgroundItemsList, virtualCameraItemsList, renderingRootID, cameraID);//VFC
			
			bTree::CullingPlanes planesHud;
			planesHud.FromMatrixVPinv(projectionMatrix.Inverse());
			
			
			Vector3 hudCameraTranslation(0,0,0);
			// cull the scene in the camera subtree, do not use camera transformation to compute the wolrd transform!
			sceneShadow->Cull(planesHud, *clonedCamera->GetBSphere(), hudCameraTranslation, hudItemsList, hudBlendedItemsList, hudReflectiveItemsList, hudBackgroundItemsList, hudVirtualCameraItemsList, cameraID, NODE_ID_NONE, false);//VFC
			//		std::cout<<"items = "<<hudItemsList.size()<<" bitems = "<<hudBlendedItemsList.size()<<" ritems = "<<hudReflectiveItemsList.size()<<"\n";
			
			for (size_t i=0; i < hudVirtualCameraItemsList.size(); i++)
				virtualCameraItemsList.push_back(hudVirtualCameraItemsList[i]);
			
			//		for (size_t i = 0; i < hudBlendedItemsList.size(); i++) {
			//			std::cout<<"i="<<i<<"\n m="<<hudBlendedItemsList[i].matrix<<"\n";
			//}
		}
	}

	lastMaterialID = (MATERIAL_ID)-2;

	updateVirtualCameras(*scene, virtualCameraItemsList, lights);

	drawBackground();

	enableClippingPlanes();

	currTime=VRUTGetTime();
	CullTime+=currTime-startTime;
	startTime=currTime;

	if (drawBVH)
	{
		drawBVHHierarchy(itemsList);
		drawBVHHierarchy(blendedItemsList);
		drawBVHHierarchy(reflectiveItemsList);
	}

	currTime=VRUTGetTime();
	drawBVHTime+=currTime-startTime;
	startTime=currTime;

#ifdef RENDER_GL_PROFILING
	VRUTStopWatchPrecise watch;
	watch.Start();
#endif

	//sort for rendering
	if (sortGeometries==1)
		std::sort(itemsList.begin(), itemsList.end(), SceneShadow::ItemInfo::SortByDistance);
	else if (sortGeometries==2)
		std::sort(itemsList.begin(), itemsList.end(), SceneShadow::ItemInfo::SortByMaterialID);

	currTime=VRUTGetTime();
	SortTime+=currTime-startTime;
	startTime=currTime;

	drawReflectiveGeometry(reflectiveItemsList, lights);
	//After rendering the reflected scene, lights need to be re-set to nonreflected positions
	drawLights(lights);

	currTime=VRUTGetTime();
	RenderReflectiveTime+=currTime-startTime;
	startTime=currTime;

	drawSolidGeometry(itemsList); //comment out for debugDraw

	currTime=VRUTGetTime();
	RenderSolidTime+=currTime-startTime;
	startTime=currTime;

	drawTransparentGeometry(blendedItemsList); //comment out for debugDraw

	if (mHudSupport) 
		drawTransparentGeometry(hudBlendedItemsList, false, false); // render without camera matrix


#ifdef RENDER_GL_PROFILING
	drawTimeCPUProf->InsertValue(float(watch.MeanTime()));
#endif
	currTime=VRUTGetTime();
	RenderTransparentTime+=currTime-startTime;
	startTime=currTime;

	disableClippingPlanes();

	if (shaderSupported)
		glUseProgramObjectARB(0);

	if (dualDVI30BitColors)
		glViewport(0, 0, w * 2, h);

	if (drawUsingFBO && GLEW_EXT_framebuffer_object)
		drawFromFBO();

	//snapshotBuffer(w, h, GL_STENCIL_INDEX, wxString::Format(wxT("debug%d.bmp"), frameRate.GetTotalFrames()).char_str()); //DEBUG. Example usage of the utility method snapshotBuffer.

	delete clonedCameraScene;

	if (!activeStereo || !cameraLeft)
	{
		wxString overlayText =
			wxString::Format(wxT("FPS=%3.1f; Size %ix%i"), frameRate.GetFPS(), (int)windowWidth, (int)windowHeight)
			+wxT("; Preparation=")+prepTime.ToString()
			+wxT("; Cull=")+CullTime.ToString()
			+wxT("; GetLight=")+GetLightTime.ToString()
			+wxT("; UpdateEnv=")+UpdateEnvTime.ToString()
			+wxT("; DrawBVH=")+drawBVHTime.ToString()
			+wxT("; Sort=")+SortTime.ToString()
			+wxT("; Reflective render=")+RenderReflectiveTime.ToString()
			+wxT("; Solid render=")+RenderSolidTime.ToString()
			+wxT("; Blended render=")+RenderTransparentTime.ToString()
			+wxT("; Out Draw=")+(startDrawTime-lastEndTime).ToString();

		// $$JB log only if the stats is not shown at the top of the screen
		if (!showStatsString) {
			LOGVERBOSE(wxT("<RenderGLModule::draw> ") + overlayText);
			overlayText=wxT("");
		}
		if (!messageString.empty())
			overlayText+=wxT("\n")+messageString;

		// finally draw text overlay which possibly includes all the gather stats
		// and user defined messages
		drawTextOverlay(overlayText, 0.01f*w, 0.95f*h, 0.05f*h);
		//drawTextOverlay(overlayText, 0.3f*w, 0.2f*h, 0.05f*h);

		lastEndTime=VRUTGetTime();
	}

#ifdef RENDER_GL_PROFILING
	glFinish();
#endif
	GLCHECKERROR(wxT("<RenderGLModule::draw>"));
}

void RenderGlModule::Draw(int openVREyeID, const Matrix &cameraWorldMatrix, const Matrix &cameraProjectionMatrix, GLsizei width, GLsizei height, SceneShadow::ItemsList &itemsList, SceneShadow::ItemsList &blendedItemsList,
	SceneShadow::ItemsList &reflectiveItemsList, SceneShadow::ItemsList &backgroundItemsList, SceneShadow::ItemsList &virtualCameraItemsList, OpenVRWrapper *openVRWrapper)
{
	wxULongLong startTime = VRUTGetTime(), startDrawTime, prepTime = 0, CullTime = 0, GetLightTime = 0, UpdateEnvTime = 0, drawBVHTime = 0, SortTime = 0, RenderReflectiveTime = 0, RenderSolidTime = 0, RenderTransparentTime = 0, currTime = 0;
	startDrawTime = startTime;
	if (!scene)
		return;

#ifdef RENDER_GL_PROFILING
	glFinish();
#endif

	smart_pointer<Camera> camera;
	scene->GetNode(camera, cameraID);
	if (!camera)
	{
		glClearColor(0.2f, 0, 0, 0);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		return;
	}
	if (!Initialize())
	{
		glClearColor(0, 0.2f, 0, 0);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		return;
	}
	//LOG(wxString::Format(wxT("<RenderGlModule> Draw %lu"), (unsigned long)drawCount++));
	if (vSyncEnabled != useVSync)
	{
		if (SetSwapInterval(useVSync ? 1 : 0))
			vSyncEnabled = useVSync;
	}

	updateFrameBufferObjectBinds(openVREyeID);

	///Clear buffer even if nothing to render
	glClearColor(defaultClearColor.x, defaultClearColor.y, defaultClearColor.z, defaultClearColor.w);
	if (clearColorBuffer)
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	else
		glClear(GL_DEPTH_BUFFER_BIT);

	unsigned w, h;
	w = width;
	h = height;
	//camera->GetWidthHeight(&w, &h);
	//bool resizeHappened(false);
	//if (resizeUpdate)
	//{
	//	resizeHappened = true;
	//	multiSampledRenderTarget.ResizeAttachments(w, h);
	//	resizeDualDVI30BitColorsProjection();
	//}
	//RenderModule::Draw();

	//if (resizeHappened)
	//{
	//	glClearColor(defaultClearColor.x, defaultClearColor.y, defaultClearColor.z, defaultClearColor.w);
	//	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	//}

	vboRenderUpdated = false;
	///Check for scene changes and reinitialize modified nodes
	Reset();
	glViewport(0, 0, w, h);

	//draw triangles at near plane over hidden area
	if (openVRWrapper && useHiddenArea)
	{
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		openVRWrapper->DrawHiddenMesh((openVREyeID == RIGHT_EYE)? vr::Hmd_Eye::Eye_Right : vr::Hmd_Eye::Eye_Left);
	}

//	///////////////////////////////////////////////////////////////////
	clonedCameraScene = new Scene(SCENE_ID_NONE, NULL);
	Camera *newCam = new Camera(wxT("default_camera"), camera);
	NODE_ID clonedCameraID = clonedCameraScene->Insert(newCam, clonedCameraScene->GetRootID());
	clonedCameraScene->GetNode(clonedCamera, clonedCameraID);
	clonedCameraScene->SetTransformation(clonedCameraScene->GetRootID(), cameraWorldMatrix);
//	///////////////////////////////////////////////////////////////////
//
//
	///Set projection matrix
	glMatrixMode(GL_PROJECTION);

	glLoadMatrixf(cameraProjectionMatrix._m);
	//glLoadMatrixf(clonedCameraScene->GetProjectionMatrix(clonedCameraID, false, true)._m);

	currTime = VRUTGetTime();
	prepTime += currTime - startTime;
	startTime = currTime;

	//get lights
	std::vector<LightInfo> lights;
	std::vector<smart_pointer<LightNode> > lightNodes;
	scene->GetNodes(lightNodes, SceneNode::LIGHT);
	for (auto &lightNode : lightNodes)
		if (sceneShadow->IsActive(lightNode->GetID()))
			lights.push_back(LightInfo(lightNode, *sceneShadow->GetWorldMatrix(lightNode->GetID())));

	currTime = VRUTGetTime();
	GetLightTime += currTime - startTime;
	startTime = currTime;


	//LOG(wxString::Format(wxT("<RenderGlModule::Draw clonnedCamera width=%i, height=%i"), (int)w, (int)h));


	///Set view matrix
	glMatrixMode(GL_MODELVIEW);
	camMatInverse = cameraWorldMatrix.InverseOrto();
	lastMatrix = Matrix();
	glLoadMatrixf(camMatInverse._m);
//
	updateEnvironmentProbes(*scene, lights, openVREyeID);

	currTime = VRUTGetTime();
	UpdateEnvTime += currTime - startTime;
	startTime = currTime;

	lastMaterialID = (MATERIAL_ID)-2;

	updateVirtualCameras(*scene, virtualCameraItemsList, lights);

	drawBackground();

	enableClippingPlanes();

	currTime = VRUTGetTime();
	CullTime += currTime - startTime;
	startTime = currTime;

	if (drawBVH)
	{
		drawBVHHierarchy(itemsList);
		drawBVHHierarchy(blendedItemsList);
		drawBVHHierarchy(reflectiveItemsList);
	}

	currTime = VRUTGetTime();
	drawBVHTime += currTime - startTime;
	startTime = currTime;

#ifdef RENDER_GL_PROFILING
	VRUTStopWatchPrecise watch;
	watch.Start();
#endif

	//sort for rendering
	if (sortGeometries == 1)
		std::sort(itemsList.begin(), itemsList.end(), SceneShadow::ItemInfo::SortByDistance);
	else if (sortGeometries == 2)
		std::sort(itemsList.begin(), itemsList.end(), SceneShadow::ItemInfo::SortByMaterialID);

	currTime = VRUTGetTime();
	SortTime += currTime - startTime;
	startTime = currTime;

	drawReflectiveGeometry(reflectiveItemsList, lights);
	//After rendering the reflected scene, lights need to be re-set to nonreflected positions
	drawLights(lights);

	currTime = VRUTGetTime();
	RenderReflectiveTime += currTime - startTime;
	startTime = currTime;

	drawSolidGeometry(itemsList); //comment out for debugDraw

	currTime = VRUTGetTime();
	RenderSolidTime += currTime - startTime;
	startTime = currTime;

	drawTransparentGeometry(blendedItemsList); //comment out for debugDraw
#ifdef RENDER_GL_PROFILING
	drawTimeCPUProf->InsertValue(float(watch.MeanTime()));
#endif
	currTime = VRUTGetTime();
	RenderTransparentTime += currTime - startTime;
	startTime = currTime;

	disableClippingPlanes();

	if (shaderSupported)
		glUseProgramObjectARB(0);

	if (dualDVI30BitColors)
		glViewport(0, 0, w * 2, h);

	if (drawUsingFBO && GLEW_EXT_framebuffer_object)
		drawFromFBO(openVREyeID);

	delete clonedCameraScene;


	//snapshotBuffer(w, h, GL_STENCIL_INDEX, wxString::Format(wxT("debug%d.bmp"), frameRate.GetTotalFrames()).char_str()); //DEBUG. Example usage of the utility method snapshotBuffer.

	if (!activeStereo || !cameraLeft)
	{
		wxString overlayText =
			wxString::Format(wxT("FPS=%3.1f; Size %ix%i"), frameRate.GetFPS(), (int)windowWidth, (int)windowHeight)
			+ wxT("; Preparation=") + prepTime.ToString()
			+ wxT("; Cull=") + CullTime.ToString()
			+ wxT("; GetLight=") + GetLightTime.ToString()
			+ wxT("; UpdateEnv=") + UpdateEnvTime.ToString()
			+ wxT("; DrawBVH=") + drawBVHTime.ToString()
			+ wxT("; Sort=") + SortTime.ToString()
			+ wxT("; Reflective render=") + RenderReflectiveTime.ToString()
			+ wxT("; Solid render=") + RenderSolidTime.ToString()
			+ wxT("; Blended render=") + RenderTransparentTime.ToString()
			+ wxT("; Out Draw=") + (startDrawTime - lastEndTime).ToString();

		// $$JB log only if the stats is not shown at the top of the screen
		if (!showStatsString) {
			LOGVERBOSE(wxT("<RenderGLModule::draw> ") + overlayText);
			overlayText = wxT("");
		}
		if (!messageString.empty())
			overlayText += wxT("\n") + messageString;

		// finally draw text overlay which possibly includes all the gather stats
		// and user defined messages
		drawTextOverlay(overlayText, 0.01f*w, 0.95f*h, 0.05f*h);
		//drawTextOverlay(overlayText, 0.3f*w, 0.2f*h, 0.05f*h);

		lastEndTime = VRUTGetTime();
	}

#ifdef RENDER_GL_PROFILING
	glFinish();
#endif
	GLCHECKERROR(wxT("<RenderGLModule::draw>"));
}

void RenderGlModule::drawFromFBO(int openVREyeID)
{
	RenderTarget *msrt;
	RenderTarget *rt;
	if (openVREyeID == RIGHT_EYE)
	{
		msrt = multiSampledRenderTargetR;
		rt = renderTargetR;
	}
	else
	{
		msrt = &multiSampledRenderTarget;
		rt = &renderTarget;
	}
	if (antiAliasingMethod != RenderTarget::NONE)
	{
		const RenderTarget::RenderBufferAttachment *renderBufferAttachment(msrt->GetRenderBufferAttachmentOfType(GL_COLOR_ATTACHMENT0_EXT));
		if (renderBufferAttachment)
		{
			//As of August 2010, the only way to resolve (display) a multisample buffer in a fixed pipeline, is to blit it to a single sample buffer
			glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, msrt->GetFrameBufferObject());
			glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, rt->GetFrameBufferObject());
			const GLsizei &w(renderBufferAttachment->params.GetWidth());
			const GLsizei &h(renderBufferAttachment->params.GetHeight());
			glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
		}
	}

	RenderTarget::Unbind();

	glPushAttrib(GL_ENABLE_BIT | GL_DEPTH_BUFFER_BIT);
	glDepthFunc(GL_ALWAYS);
	glDisable(GL_LIGHTING);
	glEnable(GL_TEXTURE_2D);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	glDisable(GL_TEXTURE_CUBE_MAP);
	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T);
	glDisable(GL_TEXTURE_GEN_R);

	const RenderTarget::TextureAttachment *textureAttachment(rt->GetTextureAttachmentOfType(GL_COLOR_ATTACHMENT0_EXT));
	if (textureAttachment)
	{
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, textureAttachment->object);
	}

	if (wireframe)
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	drawQuad();
	if (wireframe)
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

	glPopAttrib();
}

void RenderGlModule::drawBox()
{
	//TODO: Make this a display list, if the slowdown is measurable.
	glBegin(GL_QUADS);

	//Front
	glNormal3f(0.0f, 0.0f, 1.0f);

	/*
	Note: If you need to add texture coordinates here, do not forget that the background node is dependent on not having any specified here. It generates tex coords
	from object coords.
	*/
	glVertex3f(-1.0f, -1.0f, -1.0f);
	glVertex3f(1.0f, -1.0f, -1.0f);
	glVertex3f(1.0f, 1.0f, -1.0f);
	glVertex3f(-1.0f, 1.0f, -1.0f);

	//Back
	glNormal3f(0.0f, 0.0f, -1.0f);

	glVertex3f(-1.0f, 1.0f, 1.0f);
	glVertex3f(1.0f, 1.0f, 1.0f);
	glVertex3f(1.0f, -1.0f, 1.0f);
	glVertex3f(-1.0f, -1.0f, 1.0f);

	//Left
	glNormal3f(1.0f, 0.0f, 0.0f);

	glVertex3f(-1.0f, -1.0f, 1.0f);
	glVertex3f(-1.0f, -1.0f, -1.0f);
	glVertex3f(-1.0f, 1.0f, -1.0f);
	glVertex3f(-1.0f, 1.0f, 1.0f);

	//Right
	glNormal3f(-1.0f, 0.0f, 0.0f);

	glVertex3f(1.0f, 1.0f, 1.0f);
	glVertex3f(1.0f, 1.0f, -1.0f);
	glVertex3f(1.0f, -1.0f, -1.0f);
	glVertex3f(1.0f, -1.0f, 1.0f);

	//Top
	glNormal3f(0.0f, -1.0f, 0.0f);

	glVertex3f(-1.0f, 1.0f, -1.0f);
	glVertex3f(1.0f, 1.0f, -1.0f);
	glVertex3f(1.0f, 1.0f, 1.0f);
	glVertex3f(-1.0f, 1.0f, 1.0f);

	//Bottom
	glNormal3f(0.0f, 1.0f, 0.0f);

	glVertex3f(-1.0f, -1.0f, 1.0f);
	glVertex3f(1.0f, -1.0f, 1.0f);
	glVertex3f(1.0f, -1.0f, -1.0f);
	glVertex3f(-1.0f, -1.0f, -1.0f);

	glEnd();
}

void RenderGlModule::drawQuadCore()
{
	glBegin(GL_QUADS);

	glNormal3f(0.0f, 0.0f, 1.0f);

	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-1.0f, -1.0f, -1.0f);

	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(1.0f, -1.0f, -1.0f);

	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(1.0f, 1.0f, -1.0f);

	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(-1.0f, 1.0f, -1.0f);

	glEnd();
}

void RenderGlModule::drawQuad()
{
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	if (dualDVI30BitColors)
	{
		//Upper 8 bit equals to the usual rendering. This can be handled by the fixed pipeline.
		glBegin(GL_QUADS);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(0.0f, -1.0f, -1.0f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(0.0f, 1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(-1.0f, 1.0f, -1.0f);
		glEnd();

		//Lower 2 bits have to be handled by a shader
		ShaderHandler &shaderHandler(dualDVI30BitColorsProgramHandler);
		glUseProgramObjectARB(shaderHandler.program);
		glUniform1iARB(shaderHandler.paramList[0].paramLocation, 0);

		glBegin(GL_QUADS);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(0.0f, -1.0f, -1.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, -1.0f, -1.0f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(1.0f, 1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(0.0f, 1.0f, -1.0f);
		glEnd();

		glUseProgramObjectARB(0);
	}
	else
	{
		drawQuadCore();
	}
}

void RenderGlModule::Deinitialize()
{
	// Do nothing
}

void RenderGlModule::Reset()
{
	//reset geometries
	if (resetGeometry)
	{
		for (size_t i = 0; i < resetGeometries.size(); i++)
		{
			if (resetGeometries[i])
			{
				if (i < dispListGeomIDs.size() && dispListGeomIDs[i] != GL_ID_NONE)
				{
					glDeleteLists(dispListGeomIDs[i], 1);
					dispListGeomIDs[i] = GL_ID_NONE;
				}
				if (i < VBOIDs.size())
					VBOIDs[i].DeleteBuffers();

				resetGeometries[i] = false;
			}
		}
		resetGeometry = false;
		///Resize arrays
		if (resetGeometries.size() >= VBOIDs.size())
			VBOIDs.resize(resetGeometries.size() + 1);
		if (resetGeometries.size() >= dispListGeomIDs.size())
			dispListGeomIDs.resize(resetGeometries.size() + 1, GL_ID_NONE);
	}

	//reset materials
	if (resetMaterial)
	{
		for (size_t i = 0; i < resetMaterials.size(); i++)
		{
			if (resetMaterials[i])
			{
				if (i < dispListMatIDs.size() && dispListMatIDs[i] != GL_ID_NONE)
				{
					glDeleteLists(dispListMatIDs[i], 1);
					dispListMatIDs[i] = GL_ID_NONE;
					// Do not reset shaders, they will be recompiled if the source code changed
				}
				resetMaterials[i] = false;
			}
		}
		resetMaterial = false;
		///Resize arrays
		if (resetMaterials.size() > dispListMatIDs.size())
			dispListMatIDs.resize(resetMaterials.size(), GL_ID_NONE);
	}

	//reset textures
	for (auto &resetTexture : resetTextures)
	{
		if (!resetTexture.second)
			continue;
		TextureIDMap::const_iterator texIt = texture2DIDMap.find(resetTexture.first);
		if (texIt != texture2DIDMap.end())
		{
			glDeleteTextures(1, &(texIt->second.object));
			texture2DIDMap.erase(texIt->first);
			//texIt->second=false;
		}
		texIt = textureCubeIDMap.find(resetTexture.first);
		if (texIt != textureCubeIDMap.end())
		{
			glDeleteTextures(1, &(texIt->second.object));
			textureCubeIDMap.erase(texIt->first);
			//texIt->second=false;
		}
		resetTexture.second=false;
	}
	resetTextures.clear();
}

void RenderGlModule::drawBVHHierarchy(const SceneShadow::ItemsList &itemsList)
{
	//this version shows oriented AABB in fact and only for geometries (we have not BVH structure in SceneShadow)
	Vector3 color(1,1,1);
	glDisable(GL_LIGHTING);
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_TEXTURE_CUBE_MAP);
	glColor3fv(color._v);

	int ccw=GL_CCW;
	glFrontFace(ccw);
	for (auto &node : itemsList)
	{
		if (lastMatrix != node.matrix)
		{
			glLoadMatrixf((node.matrix*camMatInverse)._m);
			lastMatrix=node.matrix;
			int ccwt;
			ccwt=(node.matrix.IsMirror())?GL_CW:GL_CCW;
			if (ccw!=ccwt)
			{
				ccw=ccwt;
				glFrontFace(ccw);
			}
		}
		///Draw node
		Vector3 v0(node.node->GetAABB().MinBound);
		Vector3 v7(node.node->GetAABB().MaxBound);
		Vector3 v1(v7.x, v0.y, v0.z);
		Vector3 v2(v0.x, v7.y, v0.z);
		Vector3 v3(v7.x, v7.y, v0.z);
		Vector3 v4(v0.x, v0.y, v7.z);
		Vector3 v5(v7.x, v0.y, v7.z);
		Vector3 v6(v0.x, v7.y, v7.z);
		glBegin(GL_LINE_STRIP);
		glVertex3fv(v0._v);
		glVertex3fv(v1._v);
		glVertex3fv(v3._v);
		glVertex3fv(v2._v);
		glVertex3fv(v0._v);
		glVertex3fv(v4._v);
		glVertex3fv(v6._v);
		glVertex3fv(v7._v);
		glVertex3fv(v5._v);
		glVertex3fv(v4._v);
		glVertex3fv(v6._v);
		glVertex3fv(v2._v);
		glVertex3fv(v3._v);
		glVertex3fv(v7._v);
		glVertex3fv(v5._v);
		glVertex3fv(v1._v);
		glEnd();
	}
	return;
}

void RenderGlModule::processEvent(Event * evt)
{
	RenderModule::processEvent(evt);
	switch (evt->type)
	{
	case Event::EVT_RENDER_SYNCHRONIZATION_BARRIER_RELEASE:
		if (((EventRenderSynchronizationBarrierRelease*)evt)->windowID == windowID)
			lastBarrierState=((EventRenderSynchronizationBarrierRelease*)evt)->renderState;
		break;
	case Event::EVT_RENDER_SYNCHRONIZATION_REGISTER:
#ifdef SYNC_DEBUG
		LOG(wxString::Format(wxT("<RenderGlModule>EVT_RENDER_SYNCHRONIZATION_REGISTER, this windowID=%i, event windowID=%i"), (int)KERNEL->renderManager->GetGLWindow(this)->GetID(),
			(int)((EventRenderSynchronizationRegister*)evt)->windowID));
#endif	
		{
			GLWindow *glwin=KERNEL->renderManager->GetGLWindow(this);
			if (glwin && (glwin->GetID()==((EventRenderSynchronizationRegister*)evt)->windowID))
			{
				windowID=((EventRenderSynchronizationRegister*)evt)->masterWindowID;
				synchronized=true;
				lastRenderedState=0;
				lastBarrierState=0;
			}
		}
		break;
	case Event::EVT_RENDER_SYNCHRONIZATION_UNREGISTER:
		if (synchronized && (windowID==((EventRenderSynchronizationUnregister*)evt)->windowID))
		{
			//windowID=RENDER_WINDOW_ID_NONE;
			synchronized=false;
#ifdef SYNC_DEBUG
			LOG(wxT("<RenderGlModule>EVT_RENDER_SYNCHRONIZATION_UNREGISTER"));
#endif
		}
		break;
	case Event::EVT_PARAM_SET:
		{
			if (((EventParamSet*)evt)->paramIdentifier == sceneIDParamID)
			{
				SAFE_DELETE(sceneShadow);
				if (scene)
					sceneShadow=new SceneShadow(scene);
				REGISTER_PARAM_GUI_NODEID(renderingRootIDParamID, wxT("renderingRootID"), renderingRootID, sceneID, 511, wxT("NODE_ID_NONE or not existing nodeID - render whole scene\nExisting nodeID - render subtree"));
				rerender = true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == showStatsStringParamID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(showStatsStringParamID, showStatsString, evt);
				rerender = true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == messageStringParamID)
			{
				UPDATE_PARAM_FROM_EVENT_STRING(messageStringParamID, messageString, evt);
				rerender = true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == cameraIDParamID)
			{
				/*resetGeometries.clear();
				resetGeometry = false;*/
				rerender = true;
			}
			else if ((((EventParamSet*)evt)->paramIdentifier == useVBOParamID) || (((EventParamSet*)evt)->paramIdentifier == wireframeParamID) || (((EventParamSet*)evt)->paramIdentifier == lightingParamID)
				|| (((EventParamSet*)evt)->paramIdentifier == texturingParamID) || (((EventParamSet*)evt)->paramIdentifier == headlightParamID) || (((EventParamSet*)evt)->paramIdentifier == backFaceCullParamID)
				|| (((EventParamSet*)evt)->paramIdentifier == alphaToCoverageParamID) || (((EventParamSet*)evt)->paramIdentifier == drawBVHParamID) || (((EventParamSet*)evt)->paramIdentifier == sortGeometriesParamID))
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(useVBOParamID, useVBO, evt);
				UPDATE_PARAM_FROM_EVENT_BOOL(wireframeParamID, wireframe, evt);
				UPDATE_PARAM_FROM_EVENT_BOOL(lightingParamID, lighting, evt);
				UPDATE_PARAM_FROM_EVENT_BOOL(texturingParamID, texturing, evt);
				UPDATE_PARAM_FROM_EVENT_BOOL(headlightParamID, headlight, evt);
				UPDATE_PARAM_FROM_EVENT_BOOL(backFaceCullParamID, backFaceCull, evt);
				UPDATE_PARAM_FROM_EVENT_BOOL(alphaToCoverageParamID, alphaToCoverage, evt);
				UPDATE_PARAM_FROM_EVENT_BOOL(drawBVHParamID, drawBVH, evt);
				UPDATE_PARAM_FROM_EVENT_INT(sortGeometriesParamID, sortGeometries, evt);
				initialized = false;
				rerender = true;
				if ((((EventParamSet*)evt)->paramIdentifier == lightingParamID) || (((EventParamSet*)evt)->paramIdentifier == texturingParamID))
				{
					//reset all materials
					for (size_t i=0; i<resetMaterials.size(); i++)
						resetMaterials.at(i)=true;
					resetMaterial=true;
				}
			}
			else if (((EventParamSet*)evt)->paramIdentifier == drawUsingFBOParamID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(drawUsingFBOParamID, drawUsingFBO, evt);
				if (GLEW_EXT_framebuffer_object)
					updateFrameBufferObjectBinds();
				if (drawUsingFBO && GLEW_EXT_framebuffer_object)
				{
					ENABLE_PARAM_GUI_CONTROL(antiAliasingMethodParamID, true);
					ENABLE_PARAM_GUI_CONTROL(fboInternalFormatParamID, true);
					ENABLE_PARAM_GUI_CONTROL(dualDVI30BitColorsParamID, true);
					if (multisampleSupported)
						ENABLE_PARAM_GUI_CONTROL(antiAliasingColorSampleCountParamID, true);
					if (nvMultisampleCoverageSupported)
						ENABLE_PARAM_GUI_CONTROL(antiAliasingCoverageSampleCountParamID, true);
					enableAASampleTextControl(antiAliasingMethod);
					ENABLE_PARAM_GUI_CONTROL(openVRParamID, true);
				}
				else
				{
					ENABLE_PARAM_GUI_CONTROL(antiAliasingMethodParamID, false);
					ENABLE_PARAM_GUI_CONTROL(antiAliasingColorSampleCountParamID, false);
					ENABLE_PARAM_GUI_CONTROL(antiAliasingCoverageSampleCountParamID, false);
					ENABLE_PARAM_GUI_CONTROL(fboInternalFormatParamID, false);
					if (dualDVI30BitColors)
						SET_PARAM(dualDVI30BitColorsParamID, wxT("0"));
					ENABLE_PARAM_GUI_CONTROL(dualDVI30BitColorsParamID, false);
					ENABLE_PARAM_GUI_CONTROL(openVRParamID, false);
				}
				rerender = true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == openVRParamID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(openVRParamID, openVR, evt);
				if (GLEW_EXT_framebuffer_object)
					updateFrameBufferObjectBinds();
				if (!openVR) {
					updateAllSingleSampledRenderTargets(windowWidth, windowHeight);
					updateAllMultiSampledRenderTargets(windowWidth, windowHeight);
					updateFrameBufferObjectBinds();
					ENABLE_PARAM_GUI_CONTROL(runRenderLoopParamID, true);
					ENABLE_PARAM_GUI_CONTROL(sceneUnitsParamID, false);
				}
				rerender = true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == useHiddenAreaParamID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(useHiddenAreaParamID, useHiddenArea, evt);
				rerender = true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == renderingRootIDParamID)
			{
				UPDATE_PARAM_FROM_EVENT_ID(renderingRootIDParamID, renderingRootID, evt);
				rerender = true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == mHudSupportParamID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(mHudSupportParamID, mHudSupport, evt);
				rerender = true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == antiAliasingMethodParamID)
			{
				wxString antiAliasingMethodTmp;
				UPDATE_PARAM_FROM_EVENT_STRING(antiAliasingMethodParamID, antiAliasingMethodTmp, evt);
				antiAliasingMethod=(RenderTarget::AntiAliasingType)FindBestMatch(antiAliasingMethodStringList, antiAliasingMethodTmp);
				enableAASampleTextControl(antiAliasingMethod);
				if (antiAliasingMethod == RenderTarget::NONE)
				{
					//Alpha to coverage works properly only if AA is enabled
					//					SET_PARAM(alphaToCoverageParamID, wxT("0"));
				}
				if (!initialized)
					break;
				updateAllMultiSampledRenderTargets();
				updateFrameBufferObjectBinds();
				rerender = true;
			}
			else if ((((EventParamSet*)evt)->paramIdentifier == antiAliasingColorSampleCountParamID) || (((EventParamSet*)evt)->paramIdentifier == antiAliasingCoverageSampleCountParamID))
			{
				UPDATE_PARAM_FROM_EVENT_INT(antiAliasingColorSampleCountParamID, antiAliasingColorSampleCount, evt);
				UPDATE_PARAM_FROM_EVENT_INT(antiAliasingCoverageSampleCountParamID, antiAliasingCoverageSampleCount, evt);
				updateAllMultiSampledRenderTargets();
				rerender = true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == fboInternalFormatParamID)
			{
				wxString fboInternalFormatTmp;
				UPDATE_PARAM_FROM_EVENT_STRING(fboInternalFormatParamID, fboInternalFormatTmp, evt);
				fboInternalFormat=fboInternalFormatValuesList[FindBestMatch(fboInternalFormatStringList, fboInternalFormatTmp)];
				updateAllSingleSampledRenderTargets();
				updateAllMultiSampledRenderTargets();

				updateFrameBufferObjectBinds();
				rerender = true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == dualDVI30BitColorsParamID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(dualDVI30BitColorsParamID, dualDVI30BitColors, evt);
				if (dualDVI30BitColors)
					SET_PARAM(fboInternalFormatParamID, fboInternalFormatStringList[2]);
				setDualDVI30BitColorsProjection();
				rerender = true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == runRenderLoopParamID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(runRenderLoopParamID, runRenderLoop, evt);
				if (runRenderLoop)
				{
					rerender = true;
					eventTimeout = 0;
				}
				else
					eventTimeout = 1000;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == clearColorBufferParamID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(clearColorBufferParamID, clearColorBuffer, evt);
			}
			else if (((EventParamSet*)evt)->paramIdentifier == cullingMethodParamID)
			{
				wxString cullingMethodTmp;
				UPDATE_PARAM_FROM_EVENT_STRING(cullingMethodParamID, cullingMethodTmp, evt);
				cullingMethod=(RenderTarget::AntiAliasingType)FindBestMatch(cullingMethodStringList, cullingMethodTmp);
			}
			else if (((EventParamSet*)evt)->paramIdentifier == useVSyncParamID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(useVSyncParamID, useVSync, evt);
				rerender = true;
			}


#ifdef RENDER_GL_PROFILING
			LOG(wxT("Profilers reset, param ") + pi.paramName + wxT(" changed, new value is ") + ((wxString *)evt.GetClientData())->AfterFirst('='));
			drawTimeCPUProf->Reset(-10);
#endif
		}
		break;
	case Event::EVT_SCENE_IMAGE_ADDED:
		{
			if (!((EventSceneImageAdded*)evt)->successful)
				break;
			if (scene)
			{
				bool resetTex=false;
				size_t materCount = scene->GetMaterialMaxID();
				if (materCount > resetMaterials.size())
					resetMaterials.resize(materCount, false);
				std::vector<smart_pointer<Material> > mats;
				scene->GetMaterials(mats);
				for (auto &mat : mats)
				{
					if (mat)
					{
						if (mat->imageName==((EventSceneImageAdded*)evt)->texFile)
							resetTex = resetMaterials[mat->mid] = true;
						else if (mat->shader)
						{
							for (auto &param : mat->shader->paramList)
							{
								if (((param.type==ShaderParam::SAMPLER) || (param.type==ShaderParam::SAMPLERCUBE)) && (param.imageName==((EventSceneImageAdded*)evt)->texFile))
								{
									resetTex = resetMaterials[mat->mid] = true;
									break;
								}
							}
						}
					}
				}
				if (resetTex)
				{
					resetMaterial=true;
					ResetTexturesMap ::iterator texIt = resetTextures.find(((EventSceneImageAdded*)evt)->texFile);
					if (texIt != resetTextures.end())
						texIt->second=true;
					else
						resetTextures.insert(ResetTexturesMap::value_type(((EventSceneImageAdded*)evt)->texFile, true));
				}
			}
		}
		break;
	case Event::EVT_RENDER_UPDATE:
		{
#ifdef SYNC_DEBUG
			LOG(wxString::Format(wxT("<RenderGlModule>RENDER_UPDATE (windowID=%i, event windowID=%i, evt.renderState=%i)"), (int)windowID, (int)((EventRenderUpdate*)evt)->windowID,
				(int)((EventRenderUpdate*)evt)->renderState));
			LOG(wxString::Format(wxT("<RenderGlModule> lastRenderedState=%i, lastBarrierState=%i, synchronized="), (int)lastRenderedState, (int)lastBarrierState)+(synchronized?wxT("YES"):wxT("NO")));
#endif
			if (((EventRenderUpdate*)evt)->renderState<0)
				break;
			if (synchronized)
			{
				if (((EventRenderUpdate*)evt)->windowID == windowID)
				{
					lastRenderedState = ((EventRenderUpdate*)evt)->renderState;
					if (lastRenderedState>lastBarrierState)
					{
						rerender = true;
						lastBarrierState = lastRenderedState-1;
					}
				}
			}
			else
				rerender = true;
			break;
		}

	default:
		break;
	}

	///Scene events
	switch (evt->type)
	{
	case Event::EVT_SCENE_MATERIAL_ADDED:
		{
			if (!scene || (((EventSceneMaterialAdded*)evt)->sceneID!=sceneID))
				break;
			std::vector<NODE_ID> nodeIDs;
			if (((EventSceneMaterialAdded*)evt)->materialID==MATERIAL_ID_NONE)
				break;
			scene->GetNodeIDsWithMaterial(nodeIDs, ((EventSceneMaterialAdded*)evt)->materialID);
			if (nodeIDs.empty())
				break;
			sceneShadow->UpdateMaterial(((EventSceneMaterialAdded*)evt)->materialID, nodeIDs);
			if (((EventSceneMaterialAdded*)evt)->materialID >= resetMaterials.size())
				resetMaterials.resize(((EventSceneMaterialAdded*)evt)->materialID + 1, false);
			resetMaterial = resetMaterials[((EventSceneMaterialAdded*)evt)->materialID] = true;
			break;
		}
	case Event::EVT_SCENE_MATERIAL_REMOVED:
		{
			if (((EventSceneMaterialRemoved*)evt)->sceneID!=sceneID)
				break;
			size_t mid=(size_t)((EventSceneMaterialRemoved*)evt)->materialID;
			if (mid < resetMaterials.size())
				resetMaterial = resetMaterials[mid] = true;
			break;
		}
	case Event::EVT_SCENE_NODE_INSERTED:
		if (!scene || (((EventSceneNodeInserted*)evt)->sceneID!=sceneID))
			break;
		sceneShadow->Insert(((EventSceneNodeInserted*)evt)->nodeID);
		rerender = true;
		break;
	case Event::EVT_SCENE_NODE_GEOMETRY_CHANGED:
		{
			if (!scene || (((EventSceneNodeGeometryChanged*)evt)->sceneID!=sceneID))
				break;
			sceneShadow->SetGeometry(((EventSceneNodeGeometryChanged*)evt)->nodeID, ((EventSceneNodeGeometryChanged*)evt)->geometryID);
			rerender = true;
			/*if (scene)
			{
				NODE_ID nodeID = NODE_ID(evt.GetInt());
				smart_pointer<GeometryNode> node;
				scene->GetNode(node, nodeID);
				if (node && node->IsOfType(SceneNode::GEOMETRY))
				{
					size_t maxGeoID = scene->GetGeometryMaxID();
					if (maxGeoID >= resetGeometries.size())
						resetGeometries.resize(maxGeoID + 1, false);
					if (node->GetGeometryID()!=GEOMETRY_ID_NONE)
					{
						resetGeometry = resetGeometries[node->GetGeometryID()] = true;
						rerender = true;
					}
					sceneShadow->Update(*scene, nodeID);
				}
			}*/
		}

		break;
	case Event::EVT_SCENE_NODE_REMOVED:
		{
			if (!scene || (((EventSceneNodeRemoved*)evt)->sceneID!=sceneID))
				break;
			sceneShadow->Remove(((EventSceneNodeRemoved*)evt)->nodeID);
			rerender = true;
			break;
		}
	case Event::EVT_SCENE_NODE_TRANSFORMED:
		{
			if (!scene || (((EventSceneNodeTransformed*)evt)->sceneID!=sceneID))
				break;
			SceneNodeShadow *shnode=sceneShadow->GetNode(((EventSceneNodeTransformed*)evt)->nodeID);
			if (shnode)
			{
				shnode->SetLocalMatrix(&((EventSceneNodeTransformed*)evt)->transMatrix);
				rerender = true;
			}
		}
		break;
	case Event::EVT_SCENE_NODE_MOVED:
		if (!scene || (((EventSceneNodeMoved*)evt)->sceneID!=sceneID))
			break;
		sceneShadow->Move(((EventSceneNodeMoved*)evt)->nodeID, ((EventSceneNodeMoved*)evt)->parentID);
		rerender = true;
		break;
	case Event::EVT_SCENE_NODE_INDEX_CHANGED:
		if (!scene || (((EventSceneNodeIndexChanged*)evt)->sceneID!=sceneID))
			break;
		sceneShadow->ChangeIndex(((EventSceneNodeIndexChanged*)evt)->nodeID, ((EventSceneNodeIndexChanged*)evt)->newIndex);
		rerender = true;
		break;
	case Event::EVT_SCENE_CAM_PROJECTION_CHANGED:
		if (scene && (((EventSceneCamProjectionChanged*)evt)->sceneID==sceneID) && (((EventSceneCamProjectionChanged*)evt)->cameraID==cameraID))
			rerender = true;
		break;
	case Event::EVT_SCENE_GEOMETRY_ADDED:
		{
			if (!scene || (((EventSceneGeometryAdded*)evt)->sceneID!=sceneID))
				break;
			if (((EventSceneGeometryAdded*)evt)->geometryID >= resetGeometries.size())
				resetGeometries.resize(((EventSceneGeometryAdded*)evt)->geometryID+1, false);
			resetGeometry = resetGeometries[((EventSceneGeometryAdded*)evt)->geometryID] = true;
			if (!scene)
				break;
			std::vector<NODE_ID> nodeIDs;
			scene->GetNodeIDsWithGeometry(nodeIDs, ((EventSceneGeometryAdded*)evt)->geometryID);
			sceneShadow->UpdateGeometry(((EventSceneGeometryAdded*)evt)->geometryID, nodeIDs);
			break;
		}
	case Event::EVT_SCENE_GEOMETRY_REMOVED:
		{
			if (!scene || (((EventSceneGeometryRemoved*)evt)->sceneID!=sceneID))
				break;
			if (((EventSceneGeometryRemoved*)evt)->geometryID >= resetGeometries.size())
				resetGeometries.resize(((EventSceneGeometryRemoved*)evt)->geometryID+1, false);
			resetGeometry = resetGeometries[((EventSceneGeometryRemoved*)evt)->geometryID] = true;
			if (!scene)
				break;
			std::vector<NODE_ID> nodeIDs;
			scene->GetNodeIDsWithGeometry(nodeIDs, ((EventSceneGeometryRemoved*)evt)->geometryID);
			sceneShadow->UpdateGeometry(((EventSceneGeometryRemoved*)evt)->geometryID, nodeIDs);
			break;
		}
	case Event::EVT_SCENE_NODE_ACTIVE_CHANGED:
		{
			if (!scene || (((EventSceneNodeActiveChanged*)evt)->sceneID!=sceneID) || ((EventSceneNodeActiveChanged*)evt)->isImplied)
				break;
			SceneNodeShadow *shnode=sceneShadow->GetNode(((EventSceneNodeActiveChanged*)evt)->nodeID);
			if (shnode)
			{
				shnode->SetActive(((EventSceneNodeActiveChanged*)evt)->active);
				rerender = true;
			}
		}
		break;
	case Event::EVT_SCENE_NODE_SWITCH_ACTIVECHILD_CHANGED:
		{
			if (!scene || (((EventSceneNodeSwitchActiveChildChanged*)evt)->sceneID!=sceneID))
				break;
			sceneShadow->SetSwitchActiveChild(((EventSceneNodeSwitchActiveChildChanged*)evt)->nodeID, ((EventSceneNodeSwitchActiveChildChanged*)evt)->activeChild);
			rerender = true;
		}
		break;
	case Event::EVT_SCENE_NODE_MATERIAL_CHANGED:
		if (!scene || (((EventSceneNodeMaterialChanged*)evt)->sceneID!=sceneID))
			break;
		sceneShadow->SetMaterial(((EventSceneNodeMaterialChanged*)evt)->nodeID, ((EventSceneNodeMaterialChanged*)evt)->materialID);
		rerender = true;
		break;
	case Event::EVT_SCENE_NODE_LIGHT_CHANGED:
		if (!scene || (((EventSceneNodeLightChanged*)evt)->sceneID!=sceneID))
			break;
		rerender = true;
		break;
	case Event::EVT_SCENE_CLIPPING_PLANE_ADDED:
		if (!scene || (((EventSceneClippingPlaneAdded*)evt)->sceneID!=sceneID))
			break;
		rerender = true;
		break;
	default:
		break;
	}
}

void RenderGlModule::updateFrameBufferObjectBinds(int openVREyeID)
{
	if (drawUsingFBO)
	{
		if (antiAliasingMethod != RenderTarget::NONE)
		{
			if (openVR && (openVREyeID == RIGHT_EYE))
				multiSampledRenderTargetR->Bind();
			else
				multiSampledRenderTarget.Bind();
		}
		else
		{
			if (openVR && (openVREyeID == RIGHT_EYE))
				renderTargetR->Bind();
			else
				renderTarget.Bind();
		}
	}
	else if (!renderTargetMapMember && (GLEW_EXT_framebuffer_object))
		RenderTarget::Unbind();
}

void RenderGlModule::startViewDependentCubemapCore()
{
	if (clonedCamera && !viewDependentCubemapEnabled)
	{
		glActiveTexture(GL_TEXTURE0);

		glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB);
		glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB);
		glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_ARB);

		glMatrixMode(GL_TEXTURE);
		glLoadMatrixf(clonedCameraScene->GetWorldTransMatrix(clonedCamera->GetID()).ExtractRotation()._m);
		//glLoadMatrixf((clonedCameraScene->GetWorldTransMatrix(clonedCamera->GetID()) * (*clonedCamera->GetTMTMatrix())).ExtractRotation()._m);
		glMatrixMode(GL_MODELVIEW);
		viewDependentCubemapEnabled = true;
	}
}

void RenderGlModule::startViewDependentCubemapReflectionCore()
{
	if (clonedCamera && !viewDependentCubemapEnabled)
	{
		glActiveTexture(GL_TEXTURE0);
		glMatrixMode(GL_TEXTURE);
		glLoadMatrixf(clonedCameraScene->GetWorldTransMatrix(clonedCamera->GetID()).ExtractRotation()._m);
		glMatrixMode(GL_MODELVIEW);
		viewDependentCubemapEnabled = true;
	}
}

void RenderGlModule::stopViewDependentCubemap()
{
	if (viewDependentCubemapEnabled)
	{
		glActiveTexture(GL_TEXTURE0);
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
		viewDependentCubemapEnabled = false;
	}
}

void RenderGlModule::cleanAfterLoop()
{
#if !(defined VRUT_HPC)
	if (initialized)
	{
		GLWindow *glWindow(KERNEL->renderManager->GetGLWindow(this));
		if (glWindow && glWindow->IsShownOnScreen())
		{
			glWindow->SetCurrent(*glWindow->GetGLContext()); //This is needed for AMD graphics, otherwise a crash occurs on the following OpenGL calls. nVidia seems OK without this.

			clearCachedGeometry();
			deleteDualDVI30BitColorsProgram();
		}
		initialized = false;
	}
#endif
	RenderModule::cleanAfterLoop();
}

bool RenderGlModule::SetSwapInterval(const int interval)
{
#ifdef __WXMSW__
	if (wglSwapIntervalEXT)
	{
		BOOL err = wglSwapIntervalEXT(interval);
		if (err == FALSE)
		{
			LOGWARNING(wxT("<RenderGlModule> Vsync not set, will try again"));
			return false;
		}
	}
#else
	// According to the example 1 of https://www.opengl.org/registry/specs/EXT/swap_control.txt
	if (glXSwapIntervalEXT)
	{
		Display *dpy = glXGetCurrentDisplay();
		GLXDrawable drawable = glXGetCurrentDrawable();
		if (drawable)
			glXSwapIntervalEXT(dpy, drawable, interval);
		else
			return false;
	}
#endif
	return true;
}
