/*
* $Id:$
*
* Description  : Multithreading test ray tracer with shading, counting sharp shadow, texturing.
* Author       : Jiri Bittner and Tomas Pojkar
*
* Purpose      :
*     A building block for more sophisticated ray tracer
*/

#include <stack>
#include <algorithm>

#include <wx/version.h>
#if !wxCHECK_VERSION(3, 1, 0)
// fix for visual studio std::max issue with windows
#define NOMINMAX
#include <GL/glew.h>
// Include platform specifics, especially type of OpenGL rendering context
#include <wx/platform.h>
#ifdef __WXMSW__
#include <GL/wglew.h>
#include <wx/msw/winundef.h>		// See http://trac.wxwidgets.org/ticket/10950 , windows.h is included in glew.h
#define CONTEXTTYPE HGLRC
#else
#include <GL/glxew.h>
#define CONTEXTTYPE GLXContext
#endif
#endif

#include "RayTracer.h"
#ifndef VRUT_HPC
#include "rendermanager.h"
#endif
#include "WhittedRenderer.h"
#include "PathRenderer.h"
#include "VisualDebugger.h"
#include "VRUT_NsightCustomEvents.h"

// MD: Enable split screen for active, for testing purpose only. can be removed if necessary.
// MK: Replaced by Navigation::activeStereo
#define SPLIT_SCREEN_STEREO 0

using namespace VRUT;

RayTracer::RayTracer(const MODULE_ID & _id, const wxString & _name, Kernel * _kernel) : RenderModule(_id, _name, 0, _kernel),
	texWidth(0), texHeight(0), texSize(0),
#ifndef USE_TEMPORAL
	texData(NULL), texID(0),
#endif
	synchronized(false), windowID(RENDER_WINDOW_ID_NONE), lastRenderedState(0),
	lastBarrierState(0), renderer(new PathRenderer(this)), initialized(false),
	sceneChanged(true), lightChanged(true), pass(0), openGLVendor(NVIDIA), lastRenderingTime(VRUTGetTime()), slaveMode(false), slaveModeRedraw(0)
#if PBO_COUNT == 2
	, texData1(NULL), currentPBO(0)
#endif
#ifndef VRUT_HPC
	, openVRWrapper(nullptr),
#ifndef NOVR
	vrHeroWrapper(nullptr),
#endif
	shader(nullptr), shaderImageTextureLoc(-1), shaderFilterRadiusLoc(-1), shaderResolutionLoc(-1),
	shaderAvgLuminanceLoc(-1), shaderFrameIDLoc(-1), shaderWhitePointLoc(-1), shaderKeyValueLoc(-1)
#endif
{
#ifdef USE_TEMPORAL
	texData = new GLfloat*[NUM_TEMPORAL_TEXTURES];
	texDataStereo = new GLfloat*[NUM_TEMPORAL_TEXTURES *2];
	texCPU = new OglTexTarget[NUM_TEMPORAL_TEXTURES];
	camMoved = false; camMovedPrev = false;
#if PBO_COUNT == 2
	texData1 = new GLfloat*[NUM_TEMPORAL_TEXTURES];
#endif

	for (int tex = 0; tex < NUM_TEMPORAL_TEXTURES; tex++) {
		texData[tex] = 0;
		//texID[tex] = 0;
#if PBO_COUNT == 2
		texData1[tex] = 0;
#endif
}
#endif

	cameras[0] = nullptr;
	cameras[1] = nullptr;
#ifdef __AVX2__
	if (!KERNEL->IsAVX2Supported())
	{
		LOGERROR(wxT("<RayTracer>AVX2 is not supported by the HW but it is needed by module. Please recompile RayTracer without AVX2 support."));
		return;
	}
#elif defined __AVX__
	if (!KERNEL->IsAVXSupported())
	{
		LOGERROR(wxT("<RayTracer>AVX is not supported by the HW but it is needed by module. Please recompile RayTracer without AVX support."));
		return;
	}
	else if (KERNEL->IsAVX2Supported())
		LOGWARNING(wxT("<RayTracer>Your HW supports AVX2, but the module is compiled without AVX2."));
#else
	if (KERNEL->IsAVX2Supported())
		LOGWARNING(wxT("<RayTracer>Your HW supports AVX2, but the module is compiled without AVX2."));
	else if (KERNEL->IsAVXSupported())
		LOGWARNING(wxT("<RayTracer>Your HW supports AVX, but the module is compiled without AVX."));
#endif

	wxInitAllImageHandlers();

	wxString maxLightsTmp(wxT("3"));
	REGISTER_PARAM_GUI_SLIDER(maxLightsParamID, wxT("maxLights"), maxLightsTmp, 0, 50, wxT("Maximum number of lights to use"));
	int maxLights;
	TextParser(maxLightsTmp.utf8_str()).ParseInt(&maxLights);
	renderer->SetMaxLights(maxLights);

	wxString maxRecursionDepthTmp(wxT("4"));
	REGISTER_PARAM_GUI_SLIDER(maxRecursionDepthParamID, wxT("maxRecursionDepth"), maxRecursionDepthTmp, 0, 50, wxT("Maximum depth of recursion for secondary rays"));
	int maxRecursionDepth;
	TextParser(maxRecursionDepthTmp.utf8_str()).ParseInt(&maxRecursionDepth);
	renderer->SetMaxRecursionDepth(maxRecursionDepth);

	wxString trianglesPerLeafTmp(wxT("1"));
	REGISTER_PARAM_GUI_TEXTCONTROL(trianglesPerLeafParamID, wxT("trianglesPerLeaf"), trianglesPerLeafTmp, wxT("Maximum trianglesper leaf"));
	int trianglesPerLeaf;
	TextParser(trianglesPerLeafTmp.utf8_str()).ParseInt(&trianglesPerLeaf);
	renderer->SetTrianglesPerLeaf(trianglesPerLeaf);

    wxString shadowIntensityStr = wxT("1");
    REGISTER_PARAM_GUI_NUMBER_INPUT(castShadowRaysParamID, wxT("shadows"), shadowIntensityStr, 0.0f, 1.0f, 0.09f, 0, wxT("Set shadow intensity, 0 = shadows off"));
    float shadowAmount;
    TextParser(shadowIntensityStr.utf8_str()).ParseFloat(&shadowAmount);
	renderer->SetShadowIntensity(shadowAmount);

	wxString rayOffsetTmp(wxT("0.005"));
	REGISTER_PARAM_GUI_TEXTCONTROL(rayOffsetParamID, wxT("rayOffset"), rayOffsetTmp, wxT("Ray offset."));
	TextParser rayOffsetParser(rayOffsetTmp.utf8_str());
	float rayOffset;
	rayOffsetParser.ParseFloat(&rayOffset);
	renderer->SetRayOffset(rayOffset);

	wxString headlight(wxT("1,1,1,1"));
	REGISTER_PARAM_GUI_COLOURPICKER(headlightParamID, wxT("headlight"), headlight, ColourDialog::DialogMode::EE_COLOUR_DIALOG_INTENSITY, wxT("Headlight color"));
	Vector4 color(0.0f, 0.0f, 0.0f, 0.0f);
	TextParser(headlight.utf8_str()).ParseVector(color._v, 4, NULL, NULL, false);
	renderer->SetHeadlight(color);

	cameraTypes.Add(wxT("Pinhole Camera"));
	cameraTypes.Add(wxT("Cylindrical Camera (Horizon)"));
	cameraTypes.Add(wxT("Spherical Camera"));
	cameraTypes.Add(wxT("Omni Ortho Sphere Camera"));
	cameraTypes.Add(wxT("3D Panoramic Camera"));
	wxString cameraTypeTmp(cameraTypes[0]);
	REGISTER_PARAM_GUI_COMBOBOX(cameraTypeParamID, wxT("cameraType"), cameraTypeTmp, cameraTypes, wxT("Camera Type."));
	selectedCameraType = FindBestMatch(cameraTypes, cameraTypeTmp);

	useRadialDistortion = false;
	REGISTER_PARAM_GUI_CHECKBOX(useRadialDistortionID, wxT("radialDistortion"), useRadialDistortion, wxT("Switch using radial distortion on/off"));

	wxString hFOVTmp(wxT("120"));
	REGISTER_PARAM_GUI_SLIDER(hFOVParamID, wxT("horizontalFOV"), hFOVTmp, 1, 360, wxT("horizontal FieldOfView"));
	TextParser(hFOVTmp.utf8_str()).ParseInt(&hFOV);
	wxString vFOVTmp(wxT("180"));
	REGISTER_PARAM_GUI_SLIDER(vFOVParamID, wxT("verticalFOV"), vFOVTmp, 1, 180, wxT("vertical FieldOfView"));
	TextParser(vFOVTmp.utf8_str()).ParseInt(&vFOV);

	wxString optimizationTmp(wxT("1,1,1,0"));
	REGISTER_PARAM_GUI_TEXTCONTROL(optimizationParamID, wxT("optimization"), optimizationTmp, wxT("Ratio, max iterations, threshold, random threshold."));
	TextParser optParser(optimizationTmp.utf8_str());
	optParser.ParseInt(&optRatio);
	optParser.ParseInt(&optMaxIterations, " ,\t");
	optParser.ParseInt(&optThreshold, " ,\t");
	optParser.ParseInt(&optRandomThreshold, " ,\t");
	renderer->SetOptRatio(optRatio);
	renderer->SetOptMaxIteraions(optMaxIterations);
	renderer->SetOptThreshold(optThreshold);
	renderer->SetOptRandomThreshold(optRandomThreshold);

	wxString drawTimeoutsTmp(wxT("30,100"));//the image will be redrawn with maximally 1000/30 FPS, the antialiasing with 1000/100 FPS
	REGISTER_PARAM_GUI_TEXTCONTROL(drawTimeoutsParamID, wxT("drawTimeouts"), drawTimeoutsTmp, wxT("First number is waiting interval (in miliseconds) after start of rendering of frame,\nhigher value decrease FPS but at least something is rendered.\nSecond value is time interval (in miliseconds) in which antialiasing is drawn."));
	TextParser grawTimeoutParser(drawTimeoutsTmp.utf8_str());
	grawTimeoutParser.ParseInt(&drawTimeouts[0]);
	grawTimeoutParser.ParseInt(&drawTimeouts[1], " ,\t");
	eventTimeout = __max(0, drawTimeouts[1]);

	bool fullPolarization = true;
	REGISTER_PARAM_GUI_CHECKBOX(fullPolarizationParamID, wxT("fullPolarization"), fullPolarization, wxT("Use both terms of light polarization in Fresnel"));
	renderer->SetFullPolarization(fullPolarization);

	wxString samplesPerPixelTmp(wxT("256"));
	REGISTER_PARAM_GUI_SLIDER(samplesPerPixelParamID, wxT("samplesPerPixel"), samplesPerPixelTmp, -64, 1024, wxT("Samples per pixel"));
	int samplesPerPixel;
	TextParser(samplesPerPixelTmp.utf8_str()).ParseInt(&samplesPerPixel);
	renderer->SetSamplesPerPixel(samplesPerPixel);

	samplingStrategies.Add(wxT("Uniform 8x8"));
	samplingStrategies.Add(wxT("Uniform 4x4"));
	samplingStrategies.Add(wxT("Uniform 2x2"));
	samplingStrategies.Add(wxT("Uniform 1x1"));
	samplingStrategies.Add(wxT("Uniform 0.5x0.5"));
	samplingStrategies.Add(wxT("Uniform 0.25x0.25"));
	samplingStrategies.Add(wxT("Halton"));
	samplingStrategies.Add(wxT("FPS"));
	samplingStrategies.Add(wxT("FPS32"));
#ifdef USE_TEMPORAL
	samplingStrategies.Add(wxT("Infinite Uniform 2x2"));
	samplingStrategies.Add(wxT("Infinite Uniform 4x4 half"));
	samplingStrategies.Add(wxT("Infinite Uniform 4x4"));
	samplingStrategies.Add(wxT("Infinite Uniform 8x8"));
	samplingStrategies.Add(wxT("Infinite Uniform 1spp"));
#endif

	/*samplingStrategies.Add(wxT("Infinite Halton 16"));
	samplingStrategies.Add(wxT("Infinite Halton 8"));
	samplingStrategies.Add(wxT("Infinite Halton Full"));*/

	samplingStrategies.Add(wxT("Adaptive Uniform"));
	wxString samplingStrategyTmp(samplingStrategies.front());
	REGISTER_PARAM_GUI_COMBOBOX(samplingStrategyParamID, wxT("samplingStrategy"), samplingStrategyTmp, samplingStrategies, wxT("Sampling Strategy"));
	size_t isamplingStrategy = FindBestMatch(samplingStrategies, samplingStrategyTmp);
	int samplingStrategy;
	if (isamplingStrategy == (size_t)-1)
		samplingStrategy = 0;
	else
		samplingStrategy = isamplingStrategy;
	renderer->SetSamplingStrategy(samplingStrategy);
	oldSamplingStrategy = samplingStrategy;

	wxString viewConeAnglesTmp(wxT("80,40,10"));
	REGISTER_PARAM_GUI_TEXTCONTROL(viewConeAnglesParamID, wxT("viewConeAngles"), viewConeAnglesTmp, wxT("View cone angle thresholds (towards the cone axis)"));
	TextParser viewConeAnglesParser(viewConeAnglesTmp.utf8_str());
	int anglePrev, angleTmp;
	viewConeAngles.Add(anglePrev = 180);
	while (viewConeAnglesParser.ParseInt(&angleTmp, " ,\t"))
		if (angleTmp <= anglePrev && angleTmp >= 0) {
			viewConeAngles.Add(angleTmp);
			anglePrev = angleTmp;
		}
	viewConeAngles.Add(0);
	renderer->SetViewConeAngles(viewConeAngles);

	filterRadius = 3;
	wxString filterRadiusTmp(wxT("3"));
	REGISTER_PARAM_GUI_SLIDER(filterRadiusParamID, wxT("filterRadius"), filterRadiusTmp, -4, 100, wxT("Maximal filter radius for direct and indirect illumination"));
	TextParser(filterRadiusTmp.utf8_str()).ParseInt(&filterRadius);

	bool syncRendering = false;
	REGISTER_PARAM_GUI_CHECKBOX(syncRenderingParamID, wxT("syncRendering"), syncRendering, wxT("Synchronized rendering"));
	renderer->SetSync(syncRendering);

	bool usePathTracing = false;
	REGISTER_PARAM_GUI_CHECKBOX(usePathTracingParamID, wxT("usePathTracing"), usePathTracing, wxT("Use path tracing for computing indirect illumination"));
	renderer->SetUsePathTracing(usePathTracing);

#ifdef USE_TEMPORAL
	useTemporalPathTracing = false; texturesUsed = TEXTURES_NOTERMPORAL;
	REGISTER_PARAM_GUI_CHECKBOX(useTemporalPathTracingParamID, wxT("useTemporalPathTracing"), useTemporalPathTracing, wxT("Use spatio-temporal filtering when path tracing"));
	renderer->SetUseTemporalPathTracing(useTemporalPathTracing);

	showAlbedo = true;
	texturesUsed = TEXTURES_NOTERMPORAL;
	REGISTER_PARAM_GUI_CHECKBOX(showAlbedoID, wxT("showAlbedo"), showAlbedo, wxT("showAlbedo"));

	useReproject = false;
	texturesUsed = TEXTURES_NOTERMPORAL;
	REGISTER_PARAM_GUI_CHECKBOX(useReprojectID, wxT("useReproject"), useReproject, wxT("useReproject"));

	// temporal smoothness
	wxString smoothTmp(wxT("100")), steepTmp(wxT("20"));
	REGISTER_PARAM_GUI_SLIDER(rrSmoothParamID, wxT("Temporal smoothness"), smoothTmp, 0, 200, wxT("Temporal filter: spatial smoothing coefficient"));
	TextParser(smoothTmp.utf8_str()).ParseFloat(&smooth);

	// reproj speed
	REGISTER_PARAM_GUI_SLIDER(fStrengthParamID, wxT("Filter strength"), smoothTmp, 0, 100, wxT("Temporal filter: amount of bilateral blending"));
	TextParser(smoothTmp.utf8_str()).ParseFloat(&filterStrength);

	// steepness masking
	REGISTER_PARAM_GUI_SLIDER(fSteepParamID, wxT("Steepness mask"), steepTmp, 0, 100, wxT("Temporal filter: disabling depth weights (bilateral filter) for steep angles"));
	TextParser(steepTmp.utf8_str()).ParseFloat(&filterSteep);
#endif

	int renderThreads = std::max(1, wxThread::GetCPUCount() - 1);

	wxString renderThreadsTmp;
	renderThreadsTmp<<renderThreads;
	REGISTER_PARAM_GUI_SLIDER(renderThreadsParamID, wxT("renderThreads"), renderThreadsTmp, 0, renderThreads+1, wxT("Number of active rendering threads"));
	TextParser(renderThreadsTmp.utf8_str()).ParseInt(&renderThreads);
	renderer->SetRenderThreads(renderThreads);

	int remoteTiles = 0;
	wxString remoteTilesTmp;
	remoteTilesTmp<<remoteTiles;
	REGISTER_PARAM_GUI_SLIDER(remoteTilesParamID, wxT("remoteTiles"), remoteTilesTmp, 0, 300, wxT("Max. number of simultaneously remotely requested tiles per active rendering thread"));
    TextParser(remoteTilesTmp.utf8_str()).ParseInt(&remoteTiles);
	renderer->SetRemoteTiles(remoteTiles);

	// Reinhard's tone mapping params.
	toneMappingWhitePoint = 100;
	wxString tmwp;
	tmwp << toneMappingWhitePoint;
	REGISTER_PARAM_GUI_SLIDER(toneMappingWhitePointParamID, wxT("whitePoint"), tmwp, 1, 2000, wxT("Reinhard's tone mapping parameter. Smallest luminance mapped to white color."));
	TextParser(tmwp.utf8_str()).ParseInt(&toneMappingWhitePoint);

	toneMappingKeyValue = 50;
	wxString tmkv;
	tmkv << toneMappingKeyValue;
	REGISTER_PARAM_GUI_SLIDER(toneMappingKeyValueParamID, wxT("keyValue"), tmkv, 1, 2000, wxT("Reinhard's tone mapping parameter. Scaling parameter ('key value')."));
	TextParser(tmkv.utf8_str()).ParseInt(&toneMappingKeyValue);

	// russian roulette
	wxString rrStartDepthTmp(wxT("2"));
	REGISTER_PARAM_GUI_SLIDER(rrStartDepthParamID, wxT("rrStartDepth"), rrStartDepthTmp, 0, 20, wxT("The depth from which the Russian roulette is applied."));
	int rrStartDepth;
	TextParser(rrStartDepthTmp.utf8_str()).ParseInt(&rrStartDepth);
	renderer->SetRRStartDepth(rrStartDepth);

	stereoTypes.resize(StereoType::STEREO_MAX_STEREO_TYPES);
	stereoTypes[StereoType::MONO] = wxT("Mono");
	stereoTypes[StereoType::LEFT_TOP] = wxT("Left on top");
	stereoTypes[StereoType::LEFT_BOTTOM] = wxT("Left on bottom");
	stereoTypes[StereoType::OPENVR] = wxT("OpenVR");
	stereoTypes[StereoType::VRHERO] = wxT("VRHero");

	wxString stereoTmp(stereoTypes[0]);
	REGISTER_PARAM_GUI_COMBOBOX(stereoParamID, wxT("Stereo"), stereoTmp, stereoTypes, wxT("Method of stereo rendering"));
	selectedStereo = FindBestMatch(stereoTypes, stereoTmp);
	setActiveStereo(selectedStereo>0 && selectedStereo != StereoType::MONO);

	trackingEnable = true;
	REGISTER_PARAM_GUI_CHECKBOX(trackingEnableParamID, wxT("trackingEnable"), trackingEnable, wxT("Enable tracking from HMD"));
	createGeometries = true;
	REGISTER_PARAM_GUI_CHECKBOX(createGeometriesParamID, wxT("createGeometries"), createGeometries, wxT("Create geometries for devices tracked by HMD"));
	trackingBodyID = 0;
	wxString trackingBodyIDTmp(wxT("0"));
	REGISTER_PARAM_GUI_TEXTCONTROL(trackingBodyIDParamID, wxT("trackingBodyID"), trackingBodyIDTmp, wxT("ID of first HMD tracking device"));
	TextParser(trackingBodyIDTmp.utf8_str()).ParseInt(&trackingBodyID);
	sceneUnits = 1000.0f;
	wxString sceneUnitsTmp(wxT("1000.0"));
	REGISTER_PARAM_GUI_TEXTCONTROL(sceneUnitsParamID, wxT("sceneUnits"), sceneUnitsTmp, wxT("Ratio to meters, so for mm it must be 1000"));
	TextParser(sceneUnitsTmp.utf8_str()).ParseFloat(&sceneUnits);

	useOpenVrSubsampling = false;
	REGISTER_PARAM_GUI_CHECKBOX(openVrSubsamplingParamID, wxT("openVrSubsampling"), useOpenVrSubsampling, wxT("Enable/disable lower resolution render target in combination with OpenVR"));
	if (useOpenVrSubsampling)
		isOpenVrSubsamplingChanged = true;

	///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_RENDER_IMAGE);
	REGISTER_LISTENER(Event::EVT_BVH_OPTIMIZE);

	if (scene)
	{
		sceneShadow = new SceneShadow(scene);
		bvh = new RayTracerBHV(sceneShadow, RayTracerBHV::BVH_FAST, false);
		bvh->Rebuild(optRatio, optMaxIterations, optThreshold, optRandomThreshold);
		rerender=true;//TODO: is it necessary?
	}
	else
	{
		sceneShadow = nullptr;
		bvh = nullptr;
	}
#ifndef USE_TEMPORAL

#if PBO_COUNT > 0
	pboIds[0] = pboIds[1] = 0;
#endif

#else

#if PBO_COUNT > 0
	for (int tex = 0; tex < NUM_TEMPORAL_TEXTURES; tex++) {
		pboIds[tex][0] = pboIds[tex][1] = 0;
	}
#endif

#endif

#ifndef VRUT_HPC
	useVertexArray = false;
	useIndirectDraw = false;
#endif

	texDownscaleFactor = 1;
	isOpenVrSubsamplingChanged = false;
	srand(time(nullptr));
}

RayTracer::~RayTracer()
{
#ifndef VRUT_HPC
	KERNEL->renderManager->DestroyGLWindow(this);
#endif
	SAFE_DELETE(cameras[0]);
	SAFE_DELETE(cameras[1]);
	SAFE_DELETE(bvh);
	Deinitialize();
}

void RayTracer::initQuadData()
{
	//Initialize VBO, VAO, IBO
	float quad[] = { //pos, tcoord
		1, -1, 1, 0,
		1,  1, 1, 1,
		-1, -1, 0, 0,
		-1,  1, 0, 1
	};

	if (glDrawArraysIndirect)
		useIndirectDraw = true;

	if (glBindVertexArray)
		useVertexArray = true;

	glGenBuffers(1, &fsquadVBO);
	glBindBuffer(GL_ARRAY_BUFFER, fsquadVBO);
	glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float), quad, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	if (useVertexArray)
	{
		glGenVertexArrays(1, &fsquadVAO);
		glBindVertexArray(fsquadVAO);
		glEnableVertexAttribArray(0);
		glEnableVertexAttribArray(1);
		glBindBuffer(GL_ARRAY_BUFFER, fsquadVBO);
		glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
		glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
		glBindVertexArray(0);
	}

	if (useIndirectDraw)
	{
		typedef  struct {
			GLuint  count;
			GLuint  instanceCount;
			GLuint  first;
			GLuint  baseInstance;
		} DrawArraysIndirectCommand;

		DrawArraysIndirectCommand c;
		c.baseInstance = 0;
		c.count = 4;
		c.first = 0;
		c.instanceCount = 1;

		glGenBuffers(1, &fsquadIBO);
		glBindBuffer(GL_DRAW_INDIRECT_BUFFER, fsquadIBO);
		glBufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(DrawArraysIndirectCommand), &c, GL_STATIC_READ);
		glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
	}
}

bool RayTracer::Initialize()
{
	if (!initialized)
	{
#ifndef VRUT_HPC
		GLWindow *glWindow(KERNEL->renderManager->GetGLWindow(this));
		if (!glWindow) {
			// run in slave mode - just wait for tiles to be rendered
			slaveMode = true;
			REGISTER_LISTENER(Event::EVT_RENDER_TILE);
			renderer->SetSlaveMode(slaveMode);
			initialized = true;
			return true; //false;
		} else {
			// run in master mode
			slaveMode = false;
			//			REGISTER_LISTENER(Event::EVT_RENDER_TILE_DONE);
			REGISTER_LISTENER(Event::EVT_RENDER_TILE_DONE_SYNC);
			renderer->SetSlaveMode(slaveMode);
		}

		if (!glWindow->IsShownOnScreen())
		{
			LOG(wxT("<RayTracer>!glWindow->IsShownOnScreen()"));
			return false;
		}
		wxGLContext *glcontext=glWindow->GetGLContext();
		if (!glcontext)
		{
#if wxCHECK_VERSION(3, 1, 0)
			wxGLContextAttrs attrs;
			attrs.MajorVersion(3).MinorVersion(3).CoreProfile().EndList();

			if (!glWindow->CreateGLContext(nullptr, &attrs))
				LOGERROR(wxT("<RayTracer> Failed to create GL context"));
#else
#ifdef __WXMSW__
			int attribList[7];
			attribList[0] = WGL_CONTEXT_MAJOR_VERSION_ARB;
			attribList[1] = 3;
			attribList[2] = WGL_CONTEXT_MINOR_VERSION_ARB;
			attribList[3] = 3;
			attribList[4] = WGL_CONTEXT_PROFILE_MASK_ARB;
			attribList[5] = WGL_CONTEXT_CORE_PROFILE_BIT_ARB;
			attribList[6] = 0;
			if (!glWindow->CreateGLContext(nullptr, attribList))
				LOGERROR(wxT("<RayTracer> Failed to create GL context"));

#else	
			int attribList[7];
			attribList[0]=GLX_CONTEXT_MAJOR_VERSION_ARB;
			attribList[1]=3;
			attribList[2]=GLX_CONTEXT_MINOR_VERSION_ARB;
			attribList[3]=3;
			attribList[4]=GLX_CONTEXT_PROFILE_MASK_ARB;
			attribList[5]=GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
			attribList[6]=0;
			if (!glWindow->CreateGLContext(nullptr, attribList))
				LOGERROR(wxT("<RayTracer> Failed to create GL context"));

#endif
#endif
			glcontext = glWindow->GetGLContext();
			if (!glcontext)
			{
				LOG(wxT("<RayTracer>!glcontext"));
				return false;
			}
			else
				LOG(wxT("<RayTracer>glcontext created"));
		}
#if wxCHECK_VERSION(2, 9, 0)
		if (!glWindow->SetCurrent(*glcontext))
		{
			LOG(wxT("<RayTracer>!glWindow->SetCurrent(*glcontext)"));
			return false;
		}
#else
		glWindow->SetCurrent(*glcontext);
#endif
#if PBO_COUNT > 0 || USE_GL_FILTER
		if (!glGenBuffers)
		{
			glewExperimental = GL_TRUE;
			KERNEL->InitGlew();
			GLCHECKERROR("Glew Init");
		}
#endif

#ifdef USE_TEMPORAL
		glWindow->GetSize((int*)&texWidth, (int*)&texHeight);
		initGl();
#else

		//hack to speedup glDrawPixels from http://www.cs.rit.edu/~ncs/Courses/570/UserGuide/OpenGLonWin-31.html
		glDisable(GL_BLEND);
		glDisable(GL_DEPTH_TEST);
		glDisable(GL_DITHER);
		glDisable(GL_STENCIL_TEST);

		//prepare for QUAD drawing
		glGenTextures( 1, &texID );
		glBindTexture( GL_TEXTURE_2D, texID );
		glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

#if PBO_COUNT > 0
		glGenBuffers(PBO_COUNT, pboIds);
#endif
#if USE_GL_FILTER
		wxString shaderPrefix = KERNEL->GetAppPath() + wxT("modules/render/");
		//wxString shaderPrefix = wxT("../base_modules/RayTracer/shaders/");

		initQuadData();

		// Init GLSL shader used for filtering the ray tracing results and tone mapping
		shader = CompileShaders(shaderPrefix + wxT("rt_filter.vert"), shaderPrefix + wxT("rt_filter.frag"));

		if (shader == nullptr)
		{
			shaderPrefix = wxT("../base_modules/RayTracer/shaders/");
			shader = CompileShaders(shaderPrefix + wxT("rt_filter.vert"), shaderPrefix + wxT("rt_filter.frag"));
		}

		if (shader) 
		{
			shaderImageTextureLoc = glGetUniformLocation(shader->program, "imageTexture");
			shaderFilterRadiusLoc = glGetUniformLocation(shader->program, "filterRadius");
			shaderResolutionLoc = glGetUniformLocation(shader->program, "resolution");
			shaderAvgLuminanceLoc = glGetUniformLocation(shader->program, "avgLuminance");
			shaderFrameIDLoc = glGetUniformLocation(shader->program, "frameId");
			shaderKeyValueLoc = glGetUniformLocation(shader->program, "keyValue");
			shaderWhitePointLoc = glGetUniformLocation(shader->program, "whitePoint");
			shaderDownscaleLoc = glGetUniformLocation(shader->program, "texDownscaleRatio");

			glUseProgram(shader->program);

			glUniform1f(shaderFilterRadiusLoc, (float)filterRadius);
			glUniform1ui(shaderFrameIDLoc, 0);
			glUniform1f(shaderKeyValueLoc, ((float)toneMappingKeyValue) * 1.0e-2f);
			glUniform1f(shaderWhitePointLoc, ((float)toneMappingWhitePoint) * 1.0e-2f);
			glUniform1f(shaderDownscaleLoc, 1.0f);

			int idTextureUnit = 0;
			glUniform1i(shaderImageTextureLoc, idTextureUnit);
			glUniform2f(shaderResolutionLoc, 1, 1);
		}
#else
		shader = nullptr;
#endif
		GLCHECKERROR(wxT("RayTracer::Initialize "));

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

#endif
#else //VRUT_HPC
#ifndef USE_TEMPORAL
		texData = nullptr;
		texWidth = 0;
		texHeight = 0;
#else
	for (int tex = 0; tex<TEXTURES; tex++)
		texData[tex] = nullptr;
	texWidth = 0;
	texHeight = 0;
#endif
#endif

		initialized = true;
	}

	return initialized;
}

#ifndef VRUT_HPC
ShaderHandler * RayTracer::CompileShaders(const wxString &vsSourceFileName, const wxString &fsSourceFileName)
{
	// Load Shader Sources
	wxString vsSource, fsSource;
	bool readed = true;

	wxFFile vsFile(vsSourceFileName);
	if (!vsFile.IsOpened() || !vsFile.ReadAll(&vsSource))
		readed = false;

	wxFFile fsFile(fsSourceFileName);
	if (!fsFile.IsOpened() || !fsFile.ReadAll(&fsSource))
		readed = false;
	if (!readed)
	{
		LOGERROR(wxT("<rayTracer> Cannot read shader files!"));
		return nullptr;
	}

	//crate new shaderHandler
	ShaderHandler * shaderHandler = new ShaderHandler();

	// Create Shader And Program Objects
	shaderHandler->program = glCreateProgram();
	shaderHandler->fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	shaderHandler->vertexShader   = glCreateShader(GL_VERTEX_SHADER);

	wxCharBuffer buff = vsSource.ToAscii();
	const char * vss = buff.data();
	wxCharBuffer buff2 = fsSource.ToAscii();
	const char * fss = buff2.data();
	glShaderSource(shaderHandler->vertexShader, 1, (const GLchar **)(&vss), nullptr);
	glShaderSource(shaderHandler->fragmentShader, 1, (const GLchar **)(&fss), nullptr);

	// Compile The Shaders
	glCompileShader(shaderHandler->vertexShader);
	GLCHECKERROR("");
	glCompileShader(shaderHandler->fragmentShader);
	GLCHECKERROR("");
	LOGDEBUG(wxT("<RayTracer>Compiling shaders: ")+vsSourceFileName+wxT(", ")+fsSourceFileName);
	ShaderInfoMessage(shaderHandler->vertexShader, 1);
	ShaderInfoMessage(shaderHandler->fragmentShader, 1);

	//check for compiling errors
	int isCompiled;
	glGetShaderiv(shaderHandler->vertexShader, GL_COMPILE_STATUS, &isCompiled);
	if(!isCompiled)
	{
		LOGERROR(wxT("<RayTracer>Shader: ")+vsSourceFileName);
		ShaderInfoMessage(shaderHandler->vertexShader, 3);
		delete shaderHandler;
		return nullptr;
	}

	glGetShaderiv(shaderHandler->fragmentShader, GL_COMPILE_STATUS, &isCompiled);
	if(!isCompiled)
	{
		LOGERROR(wxT("<RayTracer>Shader: ")+fsSourceFileName);
		ShaderInfoMessage(shaderHandler->fragmentShader, 3);
		delete shaderHandler;
		return nullptr;
	}

	// Attach The Shader Objects To The Program Object
	glAttachShader(shaderHandler->program, shaderHandler->fragmentShader);
	glAttachShader(shaderHandler->program, shaderHandler->vertexShader);

	// Link The Program Object
	glLinkProgram(shaderHandler->program);

	///Info message
	int isLinked;
	glGetProgramiv(shaderHandler->program, GL_LINK_STATUS, &isLinked);
	if (!isLinked)
	{
		int loglen = 0;
		glGetProgramiv(shaderHandler->program, GL_INFO_LOG_LENGTH, &loglen);
		char* errbuf = new char[loglen];
		glGetProgramInfoLog(shaderHandler->program, loglen, 0, errbuf);
		LOGERROR(wxString::Format("Shader linking error! : %s", errbuf));
		delete [] errbuf;

		delete shaderHandler;
		return nullptr;
	}

	return shaderHandler;
}

void RayTracer::ShaderInfoMessage(const GLuint handle, const unsigned int type)
{
	int infoLogLength = 0;
	glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &infoLogLength);
	char * buffer = new char[infoLogLength];
	glGetShaderInfoLog(handle, infoLogLength, 0, buffer);
	wxString info(buffer, wxConvUTF8);
	delete [] buffer;
	if (infoLogLength>0)
	{
		switch(type)
		{
		case 0:
			LOG(wxT("<RayTracer>Shader program compile message:\n") + info);
			break;
		case 1:
			LOGDEBUG(wxT("<RayTracer>Shader program compile message:\n") + info);
			break;
		case 2:
			LOGWARNING(wxT("<RayTracer>Shader program compile message:\n") + info);
			break;
		case 3:
			LOGERROR(wxT("<RayTracer>Shader program compile message:\n") + info);
			break;
		default:
			LOG(wxT("<RayTracer>Shader program compile message:\n") + info);
		}
	}
}
#endif

void RayTracer::Deinitialize()
{
	if (initialized)
	{
#ifndef VRUT_HPC
		GLWindow *glWindow(KERNEL->renderManager->GetGLWindow(this));
		if (glWindow && (openGLVendor == AMD))
			glWindow->SetCurrent(*glWindow->GetGLContext()); //This is needed for AMD graphics, otherwise a crash occurs on the following OpenGL calls. nVidia seems OK without this.
#endif
		initialized = false;
	}
}

void RayTracer::onDone()
{
	LOG(wxString::Format(wxT("<RayTracer>onDone called.")));
	SAFE_DELETE(renderer);
#if PBO_COUNT != 2
	SAFE_DELETE_ARR(texData);
#endif
}

void RayTracer::processEvent(Event * evt)
{
	//LOG("RayTracer::processEvent");
	//Always call base method
	RenderModule::processEvent(evt);

	// Update SceneShadow
	if (sceneShadow)
	{
		sceneShadow->ProcessEvent(evt, sceneID);
	}

	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:
	{
#ifndef VRUT_HPC
		{
			GLWindow *glwin=KERNEL->renderManager->GetGLWindow(this);
			if (glwin && (glwin->GetID()==((EventRenderSynchronizationRegister*)evt)->windowID)) {
				windowID=((EventRenderSynchronizationRegister*)evt)->masterWindowID;
				synchronized=true;
				lastRenderedState=0;
				lastBarrierState=0;
			}
		}
#endif
		break;
	}
	case Event::EVT_RENDER_SYNCHRONIZATION_UNREGISTER:
	{
		if (synchronized && (windowID == ((EventRenderSynchronizationUnregister*)evt)->windowID))
			synchronized = false;
		break;
	}
	case Event::EVT_PARAM_SET:
		{
			if (((EventParamSet*)evt)->paramIdentifier == sceneIDParamID)
			{
				SAFE_DELETE(sceneShadow);
				SAFE_DELETE(bvh);
				if (scene)
				{
					sceneShadow = new SceneShadow(scene);
					bvh = new RayTracerBHV(sceneShadow, RayTracerBHV::BVH_FAST, false);
					bvh->Rebuild(optRatio, optMaxIterations, optThreshold, optRandomThreshold);
				}
#if VISUAL_DEBUGGER
				if (renderer->mVisualDebugger == nullptr)
					renderer->mVisualDebugger = new VisualDebugger(scene, this);
				/*else {
					renderer->mVisualDebugger->mSceneId = sceneID;
				}*/
#endif
				renderer->DeleteGeometryCache();
				rerender=true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier==maxLightsParamID)
			{
				int maxLights;
				UPDATE_PARAM_FROM_EVENT_INT(maxLightsParamID, maxLights, evt);
				renderer->SetMaxLights(maxLights);
				rerender=true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier==maxRecursionDepthParamID)
			{
				int maxRecursionDepth;
				UPDATE_PARAM_FROM_EVENT_INT(maxRecursionDepthParamID, maxRecursionDepth, evt);
				renderer->SetMaxRecursionDepth(maxRecursionDepth);
				rerender=true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier==rrStartDepthParamID)
			{
				int rrStartDepth;
				UPDATE_PARAM_FROM_EVENT_INT(rrStartDepthParamID, rrStartDepth, evt);
				renderer->SetRRStartDepth(rrStartDepth);
				rerender=true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier==castShadowRaysParamID)
			{
				float shadowRaysIntensity;
				UPDATE_PARAM_FROM_EVENT_FLOAT(castShadowRaysParamID, shadowRaysIntensity, evt);
				renderer->SetShadowIntensity(shadowRaysIntensity);
				rerender=true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier==syncRenderingParamID)
			{
				bool syncRendering;
				UPDATE_PARAM_FROM_EVENT_BOOL(syncRenderingParamID, syncRendering, evt);
				renderer->SetSync(syncRendering);
				rerender=true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier==usePathTracingParamID)
			{
				bool usePathTracing;
				UPDATE_PARAM_FROM_EVENT_BOOL(usePathTracingParamID, usePathTracing, evt);
				renderer->SetUsePathTracing(usePathTracing);
				rerender=true;
			}
#ifdef USE_TEMPORAL
			else if (((EventParamSet*)evt)->paramIdentifier == useTemporalPathTracingParamID)
			{
				static bool wasPathTracing;
				UPDATE_PARAM_FROM_EVENT_BOOL(usePathTracingParamID, wasPathTracing, evt);

				UPDATE_PARAM_FROM_EVENT_BOOL(useTemporalPathTracingParamID, useTemporalPathTracing, evt);
				renderer->SetUseTemporalPathTracing(useTemporalPathTracing);
				texturesUsed = (useTemporalPathTracing) ? NUM_TEMPORAL_TEXTURES : TEXTURES_NOTERMPORAL;
				rerender = true;

				/*if (useTemporalPathTracing) {
					///set temporal params
					KERNEL->GetMessageSink()->PostEvent(GET_EVT_PARAM_SET(usePathTracingParamID, wxString("1")));
					KERNEL->GetMessageSink()->PostEvent(GET_EVT_PARAM_SET(showAlbedoID, wxString("1")));
					//KERNEL->GetMessageSink()->PostEvent(GET_EVT_PARAM_SET(samplingStrategyParamID, wxT("Infinite Halton 8")));

					GUIEnvironment::GUIParamInfo guiPI;
					guiPI.paramID = samplingStrategyParamID;
					KERNEL->GetMessageSink()->PostEvent(GET_EVT_PARAM_GUIENABLE(guiPI, false));
					//KERNEL->GetMessageSink()->PostEvent(GET_EVT_PARAM_SET(drawTimeoutsParamID, wxT("8,8")));
					showAlbedo = true;

					renderer->SetSync(true);
				}
				else {
					bool syncRendering = false;
					REGISTER_PARAM_GUI_CHECKBOX(syncRenderingParamID, wxT("syncRendering"), syncRendering, wxT("Synchronized rendering"));
					renderer->SetSync(syncRendering);

					GUIEnvironment::GUIParamInfo guiPI;
					guiPI.paramID = samplingStrategyParamID;
					KERNEL->GetMessageSink()->PostEvent(GET_EVT_PARAM_GUIENABLE(guiPI, true));
					//KERNEL->GetMessageSink()->PostEvent(GET_EVT_PARAM_SET(samplingStrategyParamID, wxT("Uniform 8x8")));
					
					KERNEL->GetMessageSink()->PostEvent(GET_EVT_PARAM_SET(usePathTracingParamID, wxString(wasPathTracing ? "1" : "0")));
				}*/
			}
			else if (((EventParamSet*)evt)->paramIdentifier == showAlbedoID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(showAlbedoID, showAlbedo, evt);
				//texturesUsed = (showAlbedo) ? TEXTURES : TEXTURES_NOTERMPORAL;
				//rerender = true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == useReprojectID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(useReprojectID, useReproject, evt);
				//texturesUsed = (showAlbedo) ? TEXTURES : TEXTURES_NOTERMPORAL;
				rerender = true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier == rrSmoothParamID)
			{
				UPDATE_PARAM_FROM_EVENT_FLOAT(rrSmoothParamID, smooth, evt);
			}
			else if (((EventParamSet*)evt)->paramIdentifier == fStrengthParamID)
			{
				UPDATE_PARAM_FROM_EVENT_FLOAT(fStrengthParamID, filterStrength, evt);
			}
			else if (((EventParamSet*)evt)->paramIdentifier == fSteepParamID)
			{
				UPDATE_PARAM_FROM_EVENT_FLOAT(fSteepParamID, filterSteep, evt);
			}
#endif
			else if (((EventParamSet*)evt)->paramIdentifier==renderThreadsParamID)
			{
				int renderThreads;
				UPDATE_PARAM_FROM_EVENT_INT(renderThreadsParamID, renderThreads, evt);
				renderer->SetRenderThreads(renderThreads);
				rerender=true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier==remoteTilesParamID)
			{
				int remoteTiles;
				UPDATE_PARAM_FROM_EVENT_INT(remoteTilesParamID, remoteTiles, evt);
				renderer->SetRemoteTiles(remoteTiles);
				rerender=true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier==headlightParamID)
			{
				Vector4 headlight(0.0f, 0.0f, 0.0f, 0.0f);
				UPDATE_PARAM_FROM_EVENT_VECTOR(headlightParamID, headlight._v, 4, evt);
				renderer->SetHeadlight(headlight);
				rerender=true;
				lightChanged = true;
			}
			else if ((((EventParamSet*)evt)->paramIdentifier==hFOVParamID) || (((EventParamSet*)evt)->paramIdentifier==vFOVParamID) || (((EventParamSet*)evt)->paramIdentifier==useRadialDistortionID))
			{

				UPDATE_PARAM_FROM_EVENT_INT(hFOVParamID, hFOV, evt);
				UPDATE_PARAM_FROM_EVENT_INT(vFOVParamID, vFOV, evt);
				UPDATE_PARAM_FROM_EVENT_BOOL(useRadialDistortionID, useRadialDistortion, evt);
				rerender=true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier==cameraTypeParamID)
			{
				wxString cameraTypeTmp;
				UPDATE_PARAM_FROM_EVENT_STRING(cameraTypeParamID, cameraTypeTmp, evt);
				int cCam=FindBestMatch(cameraTypes, cameraTypeTmp);
				if (cCam >= 0)
					selectedCameraType = cCam;
				rerender=true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier==trianglesPerLeafParamID)
			{
				int tt;
				UPDATE_PARAM_FROM_EVENT_INT(trianglesPerLeafParamID, tt, evt);
				if (tt != renderer->GetTrianglesPerLeaf())
				{
					// rebuild the cache
					LOG(wxString::Format(wxT("<RayTracer>Deleting accelerators")));
					renderer->SetTrianglesPerLeaf(tt);
					renderer->DeleteGeometryCache();
					sceneChanged = true;
					rerender=true;
				}
			}
			else if (((EventParamSet*)evt)->paramIdentifier==samplesPerPixelParamID)
			{
				int samplesPerPixel;
				UPDATE_PARAM_FROM_EVENT_INT(samplesPerPixelParamID, samplesPerPixel, evt);
				renderer->SetSamplesPerPixel(samplesPerPixel);
				rerender=true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier==optimizationParamID)
			{
				wxString optimizationTmp;
				UPDATE_PARAM_FROM_EVENT_STRING(optimizationParamID, optimizationTmp, evt);
				TextParser optParser(optimizationTmp.utf8_str());
				int tmpRatio, tmpMaxIterations, tmpThreshold, tmpRandomThreshold;
				optParser.ParseInt(&tmpRatio);
				optParser.ParseInt(&tmpMaxIterations, " ,\t");
				optParser.ParseInt(&tmpThreshold, " ,\t");
				optParser.ParseInt(&tmpRandomThreshold, " ,\t");
				if (tmpRatio > 0 && tmpMaxIterations >= 0 && tmpThreshold >= 0 && tmpRandomThreshold >= 0)
				{
					//sceneChanged = (tmpRatio != optRatio) || (tmpMaxIterations != optMaxIterations) || (tmpThreshold != optThreshold) || (tmpRandomThreshold != optRandomThreshold);
					optRatio = tmpRatio;
					optMaxIterations = tmpMaxIterations;
					optThreshold = tmpThreshold;
					optRandomThreshold = tmpRandomThreshold;
					renderer->SetOptRatio(optRatio);
					renderer->SetOptMaxIteraions(optMaxIterations);
					renderer->SetOptThreshold(optThreshold);
					renderer->SetOptRandomThreshold(optRandomThreshold);
					if (bvh)
						bvh->Rebuild(optRatio, optMaxIterations, optThreshold, optRandomThreshold);
				}
			}
			else if (((EventParamSet*)evt)->paramIdentifier==drawTimeoutsParamID)
			{
				wxString drawTimeoutsTmp;
				UPDATE_PARAM_FROM_EVENT_STRING(drawTimeoutsParamID, drawTimeoutsTmp, evt);
				TextParser grawTimeoutParser(drawTimeoutsTmp.utf8_str());
				grawTimeoutParser.ParseInt(&drawTimeouts[0]);
				grawTimeoutParser.ParseInt(&drawTimeouts[1], " ,\t");
				eventTimeout = __max(0, drawTimeouts[1]);
			}
			else if (((EventParamSet*)evt)->paramIdentifier==rayOffsetParamID)
			{
				wxString rayOffsetTmp;
				float rayOffset;
				UPDATE_PARAM_FROM_EVENT_STRING(rayOffsetParamID, rayOffsetTmp, evt);
				TextParser grawTimeoutParser(rayOffsetTmp.utf8_str());
				grawTimeoutParser.ParseFloat(&rayOffset);
				renderer->SetRayOffset(rayOffset);
				rerender=true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier==fullPolarizationParamID)
			{
				bool fullPolarization;
				UPDATE_PARAM_FROM_EVENT_BOOL(fullPolarizationParamID, fullPolarization, evt);
				renderer->SetFullPolarization(fullPolarization);
				rerender=true;
			}
			else if (((EventParamSet*)evt)->paramIdentifier==samplingStrategyParamID)
			{
				wxString samplingStrategyTmp;
				UPDATE_PARAM_FROM_EVENT_STRING(samplingStrategyParamID, samplingStrategyTmp, evt);
				size_t isamplingStrategy=FindBestMatch(samplingStrategies, samplingStrategyTmp);
				renderer->SetSamplingStrategy(isamplingStrategy == (size_t)-1 ? 0 : isamplingStrategy);
				rerender=true;
			}
                        else if (((EventParamSet*)evt)->paramIdentifier == viewConeAnglesParamID)
                        {
                                wxString viewConeAnglesTmp;
                                UPDATE_PARAM_FROM_EVENT_STRING(viewConeAnglesParamID, viewConeAnglesTmp, evt);
                                TextParser viewConeAnglesParser(viewConeAnglesTmp.utf8_str());
                                int anglePrev, angleTmp;
                                viewConeAngles.Empty();
                                viewConeAngles.Add(anglePrev = 180);
                                while (viewConeAnglesParser.ParseInt(&angleTmp, " ,\t"))
                                    if (angleTmp <= anglePrev && angleTmp >= 0) {
                                        viewConeAngles.Add(angleTmp);
                                        anglePrev = angleTmp;
                                    }
                                viewConeAngles.Add(0);
                                renderer->SetViewConeAngles(viewConeAngles);
                                rerender=true;
                        }
			else if (((EventParamSet*)evt)->paramIdentifier == stereoParamID)
			{
				wxString stereoTmp;
				UPDATE_PARAM_FROM_EVENT_STRING(stereoParamID, stereoTmp, evt);
				selectedStereo = FindBestMatch(stereoTypes, stereoTmp);
				setActiveStereo(selectedStereo !=StereoType::MONO);
				rerender = true;
			}
#ifndef NOVR
			else if (((EventParamSet*)evt)->paramIdentifier == trackingEnableParamID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(trackingEnableParamID, trackingEnable, evt);
#ifndef VRUT_HPC
				if (openVRWrapper)
					openVRWrapper->SetTrackingEnable(trackingEnable);
				if (vrHeroWrapper)
					vrHeroWrapper->SetTrackingEnable(trackingEnable);
#endif
			}
			else if (((EventParamSet*)evt)->paramIdentifier == createGeometriesParamID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(createGeometriesParamID, createGeometries, evt);
#ifndef VRUT_HPC
				if (openVRWrapper)
					openVRWrapper->SetCreateGeometries(createGeometries);
				if (vrHeroWrapper)
					vrHeroWrapper->SetCreateGeometries(createGeometries);
#endif
			}
			else if (((EventParamSet*)evt)->paramIdentifier == trackingBodyIDParamID)
			{
				UPDATE_PARAM_FROM_EVENT_INT(trackingBodyIDParamID, trackingBodyID, evt);
#ifndef VRUT_HPC
				if (openVRWrapper)
					openVRWrapper->SetTrackingBodyID(trackingBodyID);
				if (vrHeroWrapper)
					vrHeroWrapper->SetTrackingBodyID(trackingBodyID);
#endif
			}
			else if (((EventParamSet*)evt)->paramIdentifier == sceneUnitsParamID)
			{
				UPDATE_PARAM_FROM_EVENT_FLOAT(sceneUnitsParamID, sceneUnits, evt);
#ifndef VRUT_HPC
				if (openVRWrapper)
					openVRWrapper->SetScale(sceneUnits);
				if (vrHeroWrapper)
					vrHeroWrapper->SetScale(sceneUnits);
#endif
			}
#endif
			else if (((EventParamSet*)evt)->paramIdentifier == openVrSubsamplingParamID)
			{
				UPDATE_PARAM_FROM_EVENT_BOOL(openVrSubsamplingParamID, useOpenVrSubsampling, evt);

				isOpenVrSubsamplingChanged = true; //will be set to false once processed

				if (/*openVRWrapper && activeStereo*/1)
				{
					if (useOpenVrSubsampling)
					{
						oldSamplingStrategy = renderer->GetSamplingStrategy();
						size_t isamplingStrategy = FindBestMatch(samplingStrategies, "Uniform 2x2");
						renderer->SetSamplingStrategy(isamplingStrategy == (size_t)-1 ? 0 : isamplingStrategy);
						texDownscaleFactor = 4;
					}
					else
					{
						renderer->SetSamplingStrategy(oldSamplingStrategy);
						texDownscaleFactor = 1;
					}

					rerender = true;
				}
			}
#ifndef VRUT_HPC
#ifndef USE_TEMPORAL
			if (((EventParamSet*)evt)->paramIdentifier==filterRadiusParamID && shader) {
				//int filterRadius;
				UPDATE_PARAM_FROM_EVENT_INT(filterRadiusParamID, filterRadius, evt);
				glUniform1f(shaderFilterRadiusLoc, (float)filterRadius);
				//rerender=true;
			}
			if (((EventParamSet*)evt)->paramIdentifier==toneMappingWhitePointParamID && shader) {
				UPDATE_PARAM_FROM_EVENT_INT(toneMappingWhitePointParamID, toneMappingWhitePoint, evt);
				glUniform1f(shaderWhitePointLoc, ((float)toneMappingWhitePoint) * 1.0e-2f);
			}
			if (((EventParamSet*)evt)->paramIdentifier==toneMappingKeyValueParamID && shader) {
				UPDATE_PARAM_FROM_EVENT_INT(toneMappingKeyValueParamID, toneMappingKeyValue, evt);
				glUniform1f(shaderKeyValueLoc, ((float)toneMappingKeyValue) * 1.0e-2f);
			}
#else
			if (((EventParamSet*)evt)->paramIdentifier == filterRadiusParamID/* && shader*/) {
				UPDATE_PARAM_FROM_EVENT_INT(filterRadiusParamID, filterRadius, evt);
				shProg.importValue("filterRadius", (float)(float)filterRadius);
			}
			if (((EventParamSet*)evt)->paramIdentifier == toneMappingWhitePointParamID) {
				UPDATE_PARAM_FROM_EVENT_INT(toneMappingWhitePointParamID, toneMappingWhitePoint, evt);
				shProg.importValue("whitePoint", (float)((float)toneMappingWhitePoint) * 1.0e-2f);
			}
			if (((EventParamSet*)evt)->paramIdentifier == toneMappingKeyValueParamID) {
				UPDATE_PARAM_FROM_EVENT_INT(toneMappingKeyValueParamID, toneMappingKeyValue, evt);
				shProg.importValue("keyValue", (float)((float)toneMappingWhitePoint) * 1.0e-2f);
			}
#endif
#endif
			break;
		}
	case Event::EVT_SCENE_NODE_INSERTED:
		{
			if (!scene || (((EventSceneNodeInserted*)evt)->sceneID!=sceneID))
				break;
			smart_pointer<SceneNode> node;
			scene->GetNode(node, ((EventSceneNodeInserted*)evt)->nodeID);
			if (node)
			{
				if (node->IsOfType(SceneNode::GEOMETRY))
				{
					smart_pointer<GeometryNode> &gnode=(smart_pointer<GeometryNode> &)node;
					if (gnode->GetGeometryID() != GEOMETRY_ID_NONE)
					{
						bvh->Insert(gnode);
						sceneChanged = true;
						rerender = true;
					}
				}
				else if (node->IsOfType(SceneNode::LIGHT))
					lightChanged=true;
			}
			break;
		}
	case Event::EVT_SCENE_NODE_GEOMETRY_CHANGED:
		{
			if (!scene || (((EventSceneNodeGeometryChanged*)evt)->sceneID!=sceneID))
				break;
			RayTracerBHVNode *bvhNode=bvh->GetBVHNodeAssignedTo(((EventSceneNodeGeometryChanged*)evt)->nodeID);
			if (!bvhNode)
			{
				smart_pointer<GeometryNode> node;
				scene->GetNode(node, ((EventSceneNodeGeometryChanged*)evt)->nodeID);
				bvh->Insert(node);
			}
			else
			{
				smart_pointer<Geometry> geo;
				scene->GetGeometry(geo, ((EventSceneNodeGeometryChanged*)evt)->geometryID);
				bvhNode->SetUntransformedAABB(geo?geo->GetAABB():AABB());
			}
			sceneChanged = true;
			rerender=true;
			break;
	}
	case Event::EVT_SCENE_NODE_REMOVED:
		{
			if (!scene || (((EventSceneNodeRemoved*)evt)->sceneID!=sceneID))
				break;
			RayTracerBHVNode *bvhNode=bvh->Remove(((EventSceneNodeRemoved*)evt)->nodeID);
			if (bvhNode)
			{
				sceneChanged = true;
				rerender=true;
				bvh->DeleteRemovedNode(bvhNode, ((EventSceneNodeRemoved*)evt)->nodeID);
			}
			else
			{
				if (renderer->HasLight(((EventSceneNodeRemoved*)evt)->nodeID))
				{
					sceneChanged = true;
					rerender=true;
					lightChanged=true;
				}
			}
			break;
		}
	case Event::EVT_SCENE_MATERIAL_ADDED:
	{
		if (((EventSceneMaterialAdded*)evt)->sceneID!=sceneID)
			break;
		renderer->InvalidateImageCacheEntry(((EventSceneMaterialAdded*)evt)->materialID);
		sceneChanged = true;
		rerender=true;
		break;
	}
	case Event::EVT_SCENE_MATERIAL_REMOVED:
	{
		if (((EventSceneMaterialRemoved*)evt)->sceneID!=sceneID)
			break;
		renderer->InvalidateImageCacheEntry(((EventSceneMaterialRemoved*)evt)->materialID);
		sceneChanged = true;
		rerender=true;
		break;
		}
	case Event::EVT_SCENE_GEOMETRY_ADDED:
		{
			if (!scene || (((EventSceneGeometryAdded*)evt)->sceneID!=sceneID))
				break;
			smart_pointer<Geometry> geo;
			scene->GetGeometry(geo, ((EventSceneGeometryAdded*)evt)->geometryID);
			std::vector<NODE_ID> nodeIDs;
			scene->GetNodeIDsWithGeometry(nodeIDs, ((EventSceneGeometryAdded*)evt)->geometryID);
			for (auto nodeID : nodeIDs)
			{
				RayTracerBHVNode *bvhNode=bvh->GetBVHNodeAssignedTo(nodeID);
				if (bvhNode)
					bvhNode->SetUntransformedAABB(geo?geo->GetAABB():AABB());
				else
				{
					smart_pointer<GeometryNode> node;
					scene->GetNode(node, nodeID);
					bvh->Insert(node);
				}
			}
			renderer->InvalidateGeometryCacheEntry(((EventSceneGeometryAdded*)evt)->geometryID);
			sceneChanged = true;
			rerender=true;
			break;
		}
	case Event::EVT_SCENE_GEOMETRY_REMOVED:
		{
			if (!scene || (((EventSceneGeometryRemoved*)evt)->sceneID!=sceneID))
				break;
			std::vector<NODE_ID> nodeIDs;
			scene->GetNodeIDsWithGeometry(nodeIDs, ((EventSceneGeometryRemoved*)evt)->geometryID);
			for (auto nodeID : nodeIDs)
			{
				bvh->GetBVHNodeAssignedTo(nodeID)->SetUntransformedAABB(AABB());
			}
			renderer->InvalidateGeometryCacheEntry(((EventSceneGeometryRemoved*)evt)->geometryID);
			sceneChanged = true;
			rerender = true;
			break;
		}
	case Event::EVT_SCENE_IMAGE_ADDED:
	{
		if (!((EventSceneImageAdded*)evt)->successful)
			break;
		if (scene)
		{
			std::vector<smart_pointer<Material> > mats;
			scene->GetMaterials(mats);
			for (auto &mat : mats)
			{
				if (mat)
				{
					if (mat->imageName == ((EventSceneImageAdded*)evt)->texFile)
					{
						renderer->InvalidateImageCacheEntry(mat->mid);
						sceneChanged = true;
						rerender = 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))
							{
								renderer->InvalidateImageCacheEntry(mat->mid);
								sceneChanged = true;
								rerender = true;
								break;
							}
						}
					}
				}
			}
		}
		break;
		}
	case Event::EVT_SCENE_NODE_MATERIAL_CHANGED:
	{
		if (((EventSceneNodeMaterialChanged*)evt)->sceneID!=sceneID)
			break;
		sceneChanged = true;
		rerender=true;
		break;
		}
	case Event::EVT_SCENE_NODE_ACTIVE_CHANGED:
	{
		if (((EventSceneNodeActiveChanged*)evt)->sceneID!=sceneID)
			break;
		sceneChanged = true;
		lightChanged = true;//TODO: filter only necessary cases where light was really changed
		rerender=true;
		break;
	}
	case Event::EVT_SCENE_NODE_SWITCH_ACTIVECHILD_CHANGED:
	{
		if (((EventSceneNodeSwitchActiveChildChanged*)evt)->sceneID != sceneID)
			break;
		sceneChanged = true;
		lightChanged = true;//TODO: filter only necessary cases where light was really changed
		rerender = true;
		break;
	}
	case Event::EVT_SCENE_NODE_LIGHT_CHANGED:
	{
		sceneChanged = true;//TODO: is it necessary?
		rerender=true;
		lightChanged=true;//TODO: filter only necessary cases where light was really changed
		break;
	}
	case Event::EVT_SCENE_NODE_TRANSFORMED:
		{
			if (!sceneShadow || (((EventSceneNodeTransformed*)evt)->sceneID != sceneID))
				break;
			//LOG("<RayTracer> Node transformed");
#if 0 // MD this is the old code, can be removed when it is sure that new one is faster
			std::vector<SceneNodeShadow*> nodes;
			sceneShadow->GetSubNodes(nodes, ((EventSceneNodeTransformed*)evt)->nodeID, true);
			for (auto &node : nodes)
			{
				if (node->IsOfType(SceneNode::GEOMETRY))
				{
					bvh->SetInvalid(node->GetID());
					sceneChanged = true;
					rerender = true;
				}
				else if (node->IsOfType(SceneNode::LIGHT))
				{
					//sceneChanged = true;
					lightChanged = true;
					rerender = true;
				}
				else if (node->IsOfType(SceneNode::CAMERA) && (node->GetID() == cameraID))
				{
					rerender = true;
				}
			}
#else
			sceneShadow->TraverseSubNodes(
				[this](SceneNodeShadow *node)
			{
				if (node->IsOfType(SceneNode::GEOMETRY))
				{
					this->bvh->SetInvalid(node->GetID());
					this->sceneChanged = true;
					this->rerender = true;
				}
				else if (node->IsOfType(SceneNode::LIGHT))
				{
					//sceneChanged = true;
					this->lightChanged = true;
					this->rerender = true;
				}
				else if (node->IsOfType(SceneNode::CAMERA) && ((node->GetID() == cameraID)))
				{
					//this->rerender = true;
#ifdef USE_TEMPORAL
					//camMoved = true;
#endif
				}
			},
				((EventSceneNodeTransformed*)evt)->nodeID, true);
#endif
			break;
		}
	case Event::EVT_SCENE_NODE_MOVED:
		{
			if (!scene || (((EventSceneNodeMoved*)evt)->sceneID!=sceneID))
				break;
			std::vector<SceneNodeShadow*> nodes;
			sceneShadow->GetSubNodes(nodes, ((EventSceneNodeMoved*)evt)->nodeID, true);
			for (auto &node : nodes)
			{
				if (node->IsOfType(SceneNode::GEOMETRY))
				{
					bvh->SetInvalid(node->GetID());
					sceneChanged = true;
					rerender=true;
				}
				else if (node->IsOfType(SceneNode::LIGHT))
				{
					//sceneChanged = true;
					lightChanged = true;
					rerender=true;
				}
				else if (node->IsOfType(SceneNode::CAMERA) && (node->GetID()==cameraID))
				{
					rerender=true;
				}
			}
			break;
		}
	case Event::EVT_RENDER_WIN_RESIZED:
	{
		if (scene && (((EventRenderWinResized*)evt)->windowID == windowID))
			rerender = true;
		break;
	}
	case Event::EVT_SCENE_CAM_PROJECTION_CHANGED:
	{
		if (scene && (((EventSceneCamProjectionChanged*)evt)->sceneID==sceneID) && ((((EventSceneCamProjectionChanged*)evt)->cameraID==cameraID) || slaveMode) )
			rerender=true;
		break;
		}
	case Event::EVT_RENDER_UPDATE:
	{
		//LOG("Dosiel RENDER UPDATE");
		if (((EventRenderUpdate*)evt)->renderState < 0)
		{
			break;
		}
		if (synchronized)
		{
			if (((EventRenderUpdate*)evt)->windowID == windowID)
			{
#ifndef RAYTRACER_CLUSTER_SYNCHRONIZE
#ifdef SYNC_DEBUG
				LOG(wxString::Format(wxT("<RayTracer> EVT_RENDER_SYNCHRONIZATION_FINISHED_RENDERING renderState=%i)"), ((EventRenderUpdate*)evt)->renderState));
#endif
				//send FINISHED event immediately
				PostToKernel(GET_EVT_RENDER_SYNCHRONIZATION_FINISHED_RENDERING(windowID, ((EventRenderUpdate*)evt)->renderState), true);
#endif //RAYTRACER_CLUSTER_SYNCHRONIZE
				//if (rerender)
				{
#ifdef RAYTRACER_CLUSTER_SYNCHRONIZE
					lastRenderedState = ((EventRenderUpdate*)evt)->renderState;
					if (lastRenderedState > lastBarrierState)
					{
						lastBarrierState = lastRenderedState - 1;
						DrawFrame(lastRenderedState);
					}
					else
						break;
#else
					DrawFrame(lastRenderedState);
#endif //RAYTRACER_CLUSTER_SYNCHRONIZE
				}
			}
		}
		else if (slaveMode /*&& ((EventRenderUpdate*)evt)->renderState == 0*/)
		{
			//Stop the draw
			if (slaveModeRedraw == 0)
			{
				renderer->Rerender();
				renderer->WaitUntilAllWorkDone();
			}
			renderer->frameId = ((EventRenderUpdate*) evt)->renderState;
			//Discard tile queue
			{
				wxCriticalSectionLocker lock(renderer->tileRenderQueueLock);
				std::queue<RenderTileData*> tmpQueue;
				auto frameId = renderer->frameId;

				//Keep tiles from the new frame, if present
				const int p = renderer->tileRenderQueue.size();
				for (int i = 0; i < p; ++i)
				{
					auto item = renderer->tileRenderQueue.front();
					
					if (item->frameId >= frameId)
					{
						tmpQueue.push(item);
					}

					renderer->tileRenderQueue.pop();
				}
				if(tmpQueue.size() < p)
					LOGWARNING(wxString::Format("RayTracer Dumping %d tiles in queue, saving %d (current renderer->frameId = %d)", p - int(tmpQueue.size()), int(tmpQueue.size()), frameId));
				std::swap(renderer->tileRenderQueue, tmpQueue);
			}

			slaveModeRedraw = 1;
		}
		//Message from window to redraw
		else if (windowID == ((EventRenderUpdate*)evt)->windowID /*&& ((EventRenderUpdate*)evt)->renderState == 0*/)
		{
			//if(slaveMode)
			//	LOG("RERENDER");
			rerender = true;
			LOGERROR("WINDOW RERENDER");
		}

		break;
		}
	case Event::EVT_SCENE_NODE_INDEX_CHANGED:
	{
		if (scene && (((EventSceneNodeIndexChanged*)evt)->sceneID==sceneID))
			rerender=true;
		break;
		}
	case Event::EVT_RENDER_IMAGE:
		{
			if ((((EventRenderImage*)evt)->sceneID == sceneID) && (((EventRenderImage*)evt)->cameraID == cameraID))
			{
				renderer->Rerender();

				unsigned int uwidth = (((EventRenderImage*)evt)->width == -1) ? texWidth : ((EventRenderImage*)evt)->width;
				unsigned int uheight = (((EventRenderImage*)evt)->height == -1) ? texHeight : ((EventRenderImage*)evt)->height;
				RayTracerCamera * cam = GetCamera(uwidth, uheight, cameraID);
				if (cam)
				{
					renderer->RenderImage(sceneShadow, cam, uwidth, uheight, ((EventRenderImage*)evt)->filename, bvh, sceneChanged, lightChanged, ((EventRenderImage*)evt)->save, ((EventRenderImage*)evt)->addImage,
						((EventRenderImage*)evt)->cubeSide, ((EventRenderImage*)evt)->imageRefName);
					SAFE_DELETE(cam);
				}

				PostToKernel(GET_EVT_RENDER_IMAGE_DONE(sceneID, cameraID, ((EventRenderImage*)evt)->width, ((EventRenderImage*)evt)->height, ((EventRenderImage*)evt)->filename));
				rerender = true;
			}
			break;
		}
	case Event::EVT_BVH_OPTIMIZE:
	{
		bvh->Rebuild(optRatio, optMaxIterations, optThreshold, optRandomThreshold);
		sceneChanged = true;
		break;
		}

	case Event::EVT_RENDER_TILE:
		{
            EventRenderTile* e = (EventRenderTile*)evt;

            if (!slaveMode || (e->sceneID != sceneID))
            {
                break;
            }

			auto & tileData = e->renderTileData;
#if LUKAS_RUZICKA_DEBUG
			auto now = VRUTGetTime();
			auto diff = now - tileData.time;
			tileData.time = now;
			LOG(wxString::Format(wxT("LUKAS_RUZICKA_DEBUG 4, %i, %i, %i, %llu, -1"), tileData.frameId, tileData.tileIndex, tileData.LOD, diff.GetValue()));
#endif


			if (e->renderTileData.frameId != renderer->frameId) {
				LOGERROR(wxString::Format("RT Tile not from current state! CurrentFrame = %d, tile frame = %d, tileIndex = %d, LOD = %d, rank = %d", renderer->frameId, e->renderTileData.frameId, e->renderTileData.tileIndex, e->renderTileData.LOD, e->renderTileData.samples));
				if (e->renderTileData.frameId < renderer->frameId)
					break;
			}
            //New frame?
            if (slaveModeRedraw)
			{
                wxCriticalSectionLocker lock(renderer->tileRenderQueueLock);
                renderer->tileRenderQueue.push(new RenderTileData(tileData));

				unsigned int w = e->renderTileData.imageWidth;
				unsigned int h = e->renderTileData.imageHeight;

                RayTracerCamera * cam = GetCamera(w, h, e->cameraID);
                renderer->RenderSlave(sceneShadow, 1, &cam, bvh, sceneChanged, lightChanged);
                rerender = false;
				slaveModeRedraw = 0;
            }
            else
            {
                wxCriticalSectionLocker lock(renderer->tileRenderQueueLock);
                renderer->tileRenderQueue.push(new RenderTileData(tileData));
            }

			break;
		}
	case Event::EVT_RENDER_TILE_DONE:
		{
            if (slaveMode || (((EventRenderTileDone*)evt)->sceneID != sceneID))
                break;

			EventRenderTileDone* e = (EventRenderTileDone*)evt;

			if (e->renderTileData.frameId == renderer->frameId)
			{
				renderer->EnqueueReceivedTile(std::make_shared<RenderTileData>(((EventRenderTileDone*)evt)->renderTileData));
			}
			break;
		}
    case Event::EVT_EXIT:
        {
            rerender = false;
            if (rerender)
            {
                renderer->WaitUntilAllWorkDone();
            }
        }
	default:
		break;
	}
}

Module::ProcessResult RayTracer::processSyncEvent(Event::EVENT_TYPE evtType, void *data)
{
	// UNRESOLVED Should the parent processSyncEvent method be called?
	if (evtType == Event::EVT_RENDER_TILE_DONE_SYNC)
	{
		EventRenderTileDoneSync *e = (EventRenderTileDoneSync*)data;
		if (slaveMode || (e->sceneID != sceneID))
			return Module::Denied;


		if (e->renderTileData->frameId == renderer->frameId)
		{
			//LOGWARNING(wxString::Format("Arrived tile %d frame %d LOD %d", e->renderTileData.tileIndex, e->renderTileData.frameId, e->renderTileData.LOD));
			renderer->EnqueueReceivedTile(e->renderTileData);
		}
	}
	return Module::Allowed;
}
//#define SYNC_DEBUG 1
void RayTracer::DrawFrame(int renderState)
{
	PUSH_RANGE("DrawFrame init", 4);
#ifndef VRUT_HPC
	if (!Initialize() || !scene)
	{
		POP_RANGE;
		return;
	}
	//if nic se nezmenilo return;
	if (pass == 0)
	{
		//LOG(wxT("<RayTracer::DrawFrame> WaitForFirstLOD start"));
		renderer->WaitForFirstLOD();
		//LOG(wxT("<RayTracer::DrawFrame> WaitForFirstLOD end"));
	}

	GLWindow *glWindow = KERNEL->renderManager->GetGLWindow(this);
	if (!glWindow)
	{
		POP_RANGE;
		return;
	}

	switch (selectedStereo)
	{
	case StereoType::OPENVR:
		{
			if (!GLEW_EXT_framebuffer_object) 
			{
				LOGWARNING(wxT("<RayTracer::DrawFrame>Yer olde GPU chippe does not support frame buffer objects. Try updating thy drivers. Many advanced rendering techniques will be unavailable."));
				selectedStereo = StereoType::MONO;
				SET_PARAM(stereoParamID, wxT("Mono"));
				return;
			}
			if (openVRWrapper == nullptr)
			{
				Initialize();
				openVRWrapper = new OpenVRWrapper(scene, cameraID, sceneUnits, true, true/*trackingEnable*/, createGeometries, trackingBodyID);
				if (!openVRWrapper->IsInitialized()) 
				{
					selectedStereo = StereoType::MONO;
					SET_PARAM(stereoParamID, wxT("Mono"));
					SAFE_DELETE(openVRWrapper);
					return;
				}
				// Prepare FBO
				glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
				updateRenderTarget(openVRWrapper->GetRenderWidth(), openVRWrapper->GetRenderHeight() * 2);
			}
			renderTarget.Bind();
			wxCriticalSectionLocker lock(openVRWrapper->openvrLock);
			// Render loop
			//if (trackingEnable)
				rerender = true;
		}
		break;

	case StereoType::LEFT_TOP:
		{
		}
		break;
#ifndef NOVR
	case StereoType::VRHERO:
		{
			if (vrHeroWrapper == nullptr)
			{
				Initialize();
				vrHeroWrapper = new VRHeroWrapper(scene, cameraID, sceneUnits, true, true/*trackingEnable*/, createGeometries, trackingBodyID);
				if (!vrHeroWrapper->IsInitialized()) {
					selectedStereo = StereoType::MONO;
					SET_PARAM(stereoParamID, wxString::Format("%d", StereoType::MONO));
					SAFE_DELETE(vrHeroWrapper);
					return;
				}
			}
			wxCriticalSectionLocker lock(vrHeroWrapper->openvrLock);
			// Render loop
			//if (trackingEnable)
			rerender = true;
		}
		break;
#endif
	case StereoType::MONO:
	default:
		if (openVRWrapper != nullptr)
		{
			renderTarget.DeleteFrameBufferObject();
			RenderTarget::Unbind();
			SAFE_DELETE(openVRWrapper);
		}
#ifndef NOVR
		if (vrHeroWrapper != nullptr)
		{
			SAFE_DELETE(vrHeroWrapper);
		}
		break;
#endif
	}

#ifdef SYNC_DEBUG
	LOG(wxT("<RayTracer::DrawFrame> start"));
#endif
#ifndef __WXMSW__
	wxMutexGuiEnter();
#endif
#ifdef SYNC_DEBUG
	LOG(wxT("<RayTracer::DrawFrame> before glWindow->IsShownOnScreen"));
#endif
	if (glWindow->IsShownOnScreen())
	{
#ifdef SYNC_DEBUG
		LOG(wxT("<RayTracer::DrawFrame> before glWindow->SetCurrent"));
#endif
		glWindow->SetCurrent(*glWindow->GetGLContext());
#ifdef SYNC_DEBUG
		LOG(wxT("<RayTracer::DrawFrame> before DrawPixels"));
#endif
		//LOG(wxString::Format(wxT("<RayTracer::DrawFrame> before DrawPixels, pass=%i"), pass));
		POP_RANGE;

		DrawPixels(rerender);
	}
	else
		POP_RANGE;
	PUSH_RANGE("DrawFrame init 2", 4);
#ifndef __WXMSW__
	wxMutexGuiLeave();
#endif

	PostToKernel(GET_EVT_SCENE_FRAME_RENDERED(sceneID, cameraID));
	IncFramerate();
	POP_RANGE;
	if (rerender && trackingEnable)
	{
		PUSH_RANGE("DrawFrame UpdateBV", 4);
#ifdef SYNC_DEBUG
		LOG(wxString::Format(wxT("<RayTracer::DrawFrame> before bvh->UpdateBV renderState=%i)"), renderState));
#endif
		bvh->UpdateBV();
		POP_RANGE;
		PUSH_RANGE("DrawFrame Rerender", 4);
#ifdef SYNC_DEBUG
		LOG(wxString::Format(wxT("<RayTracer::DrawFrame> before renderer->WaitUntilAllWorkDone renderState=%i)"), renderState));
#endif
		renderer->Rerender();
		POP_RANGE;
		PUSH_RANGE("DrawFrame WaitUntilAllWorkDone", 4);
		// wait until all workers are done
		renderer->WaitUntilAllWorkDone();
		POP_RANGE;
		PUSH_RANGE("DrawFrame InitializeToDrawNewFrame", 4);
#ifdef SYNC_DEBUG
		LOG(wxString::Format(wxT("<RayTracer::DrawFrame> before InitializeToDrawNewFrame renderState=%i)"), renderState));
#endif
		// initialize variables to render new picture
		InitializeToDrawNewFrame();
#if PBO_COUNT == 2
		std::swap(texData, texData1);
		currentPBO = 1 - currentPBO;
#endif
		POP_RANGE;
		PUSH_RANGE("DrawFrame Render", 4);
#ifdef SYNC_DEBUG
		LOG(wxString::Format(wxT("<RayTracer::DrawFrame> before renderer->Render renderState=%i)"), renderState));
#endif
		// render
		//#if SPLIT_SCREEN_STEREO
		//		float *texDataArr[2];
		//		texDataArr[0] = texData;
		//		texDataArr[1] = texData + (texWidth * (texHeight / 2) * COLOR_COMPONENTS);
		//		renderer->Render(sceneShadow, 2, cameras, texWidth, texHeight / 2, texDataArr, bvh, sceneChanged, lightChanged);
		//#else
		//		renderer->Render(sceneShadow, 1, cameras, texWidth, texHeight, (float**)&texData, bvh, sceneChanged, lightChanged);
		//#endif

#ifndef USE_TEMPORAL
		switch (selectedStereo)
		{
		default:
		case StereoType::MONO:
			renderer->Render(sceneShadow, 1, cameras, texWidth / texDownscaleFactor, texHeight / texDownscaleFactor, (float**)&texData, bvh, sceneChanged, lightChanged);
			break;

		case StereoType::OPENVR:
			if (openVRWrapper != nullptr)
			{
				float *texDataArr[2];
				texDataArr[0] = texData;
				texDataArr[1] = texData + (texWidth / texDownscaleFactor * (texHeight / texDownscaleFactor / 2) * COLOR_COMPONENTS);
				renderer->Render(sceneShadow, 2, cameras, texWidth / texDownscaleFactor, (texHeight / texDownscaleFactor) / 2, texDataArr, bvh, sceneChanged, lightChanged);
			}
			break;
#ifndef NOVR
		case StereoType::VRHERO:
			if (vrHeroWrapper != nullptr)
			{
				float *texDataArr[2];
				texDataArr[0] = texData + (texWidth / texDownscaleFactor * (texHeight / texDownscaleFactor / 2) * COLOR_COMPONENTS);
				texDataArr[1] = texData;
				renderer->Render(sceneShadow, 2, cameras, texWidth / texDownscaleFactor, (texHeight / texDownscaleFactor) / 2, texDataArr, bvh, sceneChanged, lightChanged);
			}
			break;
#endif

		case StereoType::LEFT_TOP:
			{
				float *texDataArr[2];
				texDataArr[0] = texData + (texWidth / texDownscaleFactor * (texHeight / texDownscaleFactor / 2) * COLOR_COMPONENTS);
				texDataArr[1] = texData;
				renderer->Render(sceneShadow, 2, cameras, texWidth / texDownscaleFactor, (texHeight / texDownscaleFactor) / 2, texDataArr, bvh, sceneChanged, lightChanged);
			}
			break;

		case StereoType::LEFT_BOTTOM:
			{
				float *texDataArr[2];
				texDataArr[0] = texData;
				texDataArr[1] = texData + (texWidth / texDownscaleFactor * (texHeight / texDownscaleFactor / 2) * COLOR_COMPONENTS);
				renderer->Render(sceneShadow, 2, cameras, texWidth / texDownscaleFactor, (texHeight / texDownscaleFactor) / 2, texDataArr, bvh, sceneChanged, lightChanged);
			}
			break;
		}
#else
		switch (selectedStereo)
		{
		default:
		case StereoType::MONO:
			renderer->Render(sceneShadow, 1, cameras, texWidth / texDownscaleFactor, texHeight / texDownscaleFactor, (float ***)&texData, bvh, sceneChanged, lightChanged);
			break;

		case StereoType::OPENVR:
			if (openVRWrapper != nullptr)
			{
				for (int i = 0; i < NUM_TEMPORAL_TEXTURES; i++) {
					texDataStereo[i + 0 * NUM_TEMPORAL_TEXTURES] = texData[i];
					texDataStereo[i + 1 * NUM_TEMPORAL_TEXTURES] = texData[i] + (texWidth / texDownscaleFactor * (texHeight / texDownscaleFactor / 2) * COLOR_COMPONENTS);
				}
				renderer->Render(sceneShadow, 2, cameras, texWidth / texDownscaleFactor, (texHeight / texDownscaleFactor) / 2, &texDataStereo, bvh, sceneChanged, lightChanged);
			}
			break;
		case StereoType::VRHERO:
			if (vrHeroWrapper != nullptr)
			{
				for (int i = 0; i < NUM_TEMPORAL_TEXTURES; i++) {
					texDataStereo[i + 0 * NUM_TEMPORAL_TEXTURES] = texData[i] + (texWidth / texDownscaleFactor * (texHeight / texDownscaleFactor / 2) * COLOR_COMPONENTS);
					texDataStereo[i + 1 * NUM_TEMPORAL_TEXTURES] = texData[i];
				}
				renderer->Render(sceneShadow, 2, cameras, texWidth / texDownscaleFactor, (texHeight / texDownscaleFactor) / 2, &texDataStereo, bvh, sceneChanged, lightChanged);
			}
			break;

		case StereoType::LEFT_TOP:
		{
			for (int i = 0; i < NUM_TEMPORAL_TEXTURES; i++) {
				texDataStereo[i + 0 * NUM_TEMPORAL_TEXTURES] = texData[i] + (texWidth / texDownscaleFactor * (texHeight / texDownscaleFactor / 2) * COLOR_COMPONENTS);
				texDataStereo[i + 1 * NUM_TEMPORAL_TEXTURES] = texData[i];
			}
			renderer->Render(sceneShadow, 2, cameras, texWidth / texDownscaleFactor, (texHeight / texDownscaleFactor) / 2, &texDataStereo, bvh, sceneChanged, lightChanged);

		}
		break;

		case StereoType::LEFT_BOTTOM:
		{
			for (int i = 0; i < NUM_TEMPORAL_TEXTURES; i++) {
				texDataStereo[i + 0 * NUM_TEMPORAL_TEXTURES] = texData[i];
				texDataStereo[i + 1 * NUM_TEMPORAL_TEXTURES] = texData[i] + (texWidth / texDownscaleFactor * (texHeight / texDownscaleFactor / 2) * COLOR_COMPONENTS);
			}
			renderer->Render(sceneShadow, 2, cameras, texWidth / texDownscaleFactor, (texHeight / texDownscaleFactor) / 2, &texDataStereo, bvh, sceneChanged, lightChanged);
		}
		break;
		}
#endif

		pass = 0;
		rerender = false;
#ifdef RAYTRACER_CLUSTER_SYNCHRONIZE
		if (synchronized) {
			glFinish(); //we must wait to complete whole rendering to have more exact synchronization
#ifdef SYNC_DEBUG
			LOG(wxString::Format(wxT("<RayTracer::DrawFrame> EVT_RENDER_SYNCHRONIZATION_FINISHED_RENDERING renderState=%i)"), ((EventRenderUpdate*)evt)->renderState));
#endif
			PostToKernel(GET_EVT_RENDER_SYNCHRONIZATION_FINISHED_RENDERING(windowID, renderState), true);

			wxULongLong syncWait = VRUTGetTime(), syncWaitEnd = syncWait + 1000000;
			while ((lastBarrierState < lastRenderedState) && (syncWait < syncWaitEnd))
			{
#ifdef SYNC_DEBUG
				LOG(wxString::Format(wxT("<RayTracer::DrawFrame> before WaitForEvent (lastBarrier=%i)"), lastBarrierState));
#endif
				Event * evt(WaitForEvent(GET_EVT_RENDER_SYNCHRONIZATION_BARRIER_RELEASE(windowID, -1), 1000));
				if (evt)
				{
					lastBarrierState = ((EventRenderSynchronizationBarrierRelease*)evt)->renderState;
#ifdef SYNC_DEBUG
					LOG(wxString::Format(wxT("<RayTracer::DrawFrame>WaitForEvent(GET_EVT_RENDER_SYNCHRONIZATION_BARRIER_RELEASE) renderState=%i)"), lastBarrierState));
#endif
					ReleaseEvent(evt);
				}
#ifdef SYNC_DEBUG
				else
					LOG(wxT("<RayTracer::DrawFrame>WaitForEvent(GET_EVT_RENDER_SYNCHRONIZATION_BARRIER_RELEASE) timeout"));
#endif
				syncWait = VRUTGetTime();
				}
		}
#endif //RAYTRACER_CLUSTER_SYNCHRONIZE
		POP_RANGE;
	}
	else
	{
		pass = 1;
	}
	PUSH_RANGE("DrawFrame finish", 4);
#ifdef SYNC_DEBUG
	LOG(wxT("<RayTracer::DrawFrame> before glWindow->SwapBuffers"));
#endif

	if (glWindow->IsShownOnScreen())
		glWindow->SwapBuffers();

#ifndef __WXMSW__
	wxMutexGuiLeave();
#endif
#if PBO_COUNT == 2
	POP_RANGE;
	PUSH_RANGE("DrawFrame map buffer", 4);
#ifdef SYNC_DEBUG
	LOG(wxString::Format(wxT("<RayTracer::DrawFrame> before map buffer renderState=%i)"), renderState));
#endif
#ifndef USE_TEMPORAL
	if (!glMapBufferRange)
	{
		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[1 - currentPBO]);
		texData1 = (float*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
	}
	POP_RANGE;
#else
	if (!glMapBufferRange)
	{
		for (int tex = 0; tex < texturesUsed; tex++) {
			glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[tex][1 - currentPBO]);
			texData1[tex] = (float*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
		}
	}
	POP_RANGE;
#endif

	//A must-do!
	//There is no other mechanism to force the asynchronous pixel transfer to sync
	glFinish();

	PUSH_RANGE("DrawFrame memset", 4);
	//memset(texData1, 0, sizeof(float) * texWidth * texHeight * COLOR_COMPONENTS);
	//for (size_t i=0; i<texWidth * texHeight * COLOR_COMPONENTS; ++i)
	//	texData1[i] = 0.0f;
#endif

#ifdef SYNC_DEBUG
	LOG(wxT("<RayTracer::DrawFrame> end"));
#endif
#endif //VRUT_HPC
	POP_RANGE;
}

void RayTracer::run()
{
#ifndef VRUT_HPC
	if (evtQueue.IsEmpty())
	{
		wxULongLong currentTime = VRUTGetTime();
		if ((pass == 0) || !rerender)
		{
			int evtTm = __max(0, drawTimeouts[pass] - (currentTime - lastRenderingTime).ToDouble() / 1000);
			if (evtTm != 0)
			{
				eventTimeout = evtTm;
				//LOG(wxString::Format(wxT("<RayTracer::run> eventTimeout=%lu"), eventTimeout));
				return;
			}
			#if !LUKAS_RUZICKA_DEBUG
			if (pass == 0)
				LOG(wxString::Format(wxT("<RayTracer::run> rendering time was=%lu"), (currentTime - lastRenderingTime).ToULong()));
			#endif
		}
		lastRenderingTime = currentTime;
		DrawFrame(lastRenderedState);
		wxULongLong currentTime2 = VRUTGetTime();
		eventTimeout = __max(0, drawTimeouts[pass] - (currentTime2 - lastRenderingTime).ToDouble() / 1000);
		//LOG(wxString::Format(wxT("<RayTracer::run> Leaving method eventTimeout=%lu"), eventTimeout));
	}
#endif //VRUT_HPC
}

wxString RayTracer::GetDesc() const
{
	return wxT("Ray tracing renderer");
}

#ifdef USE_TEMPORAL
void RayTracer::bindCPUTexture(int tex) 
{
	PUSH_RANGE("DrawPixels bind", 4);
	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
	//glBindTexture(GL_TEXTURE_2D, texID[tex]);
	texCPU[tex].bind();

#if PBO_COUNT > 0
#if PBO_COUNT == 2

	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[tex][currentPBO]);
	if (texData[tex] && !glMapBufferRange)
		glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); // release pointer to mapping buffer*/

#else //PBO_COUNT == 2
	glBindBufferARB(GL_PIXEL_UNPACK_BUFFER, pboIds[tex][0]);
	if (!glMapBufferRange)
	{
		GLCHECKERROR(wxT("RayTracer::DrawPixels glBindBufferARB"));
		glBufferDataARB(GL_PIXEL_UNPACK_BUFFER, texSize * 4, 0, GL_STREAM_DRAW);//GL_WRITE_ONLY
		GLCHECKERROR(wxT("RayTracer::DrawPixels glBufferDataARB"));
		GLubyte* ptr = (GLubyte*)glMapBufferARB(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
		GLCHECKERROR(wxT("RayTracer::DrawPixels glMapBufferARB"));
		POP_RANGE;
		PUSH_RANGE("DrawPixels update", 4);
		if (ptr)
		{
			// update data directly on the mapped buffer
			if (texData[tex])
			{
				PUSH_RANGE("DrawPixels memcpy", 4);
				memcpy(ptr, texData[tex], texSize * 4);
				POP_RANGE;
			}
			PUSH_RANGE("DrawPixels glUnmapBufferARB", 4);
			glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER); // release pointer to mapping buffer
			POP_RANGE;
			GLCHECKERROR(wxT("RayTracer::DrawPixels glUnmapBufferARB"));
		}
		else
			LOGERROR(wxString::Format(wxT("RayTracer::DrawPixels glMapBufferARB returns nullptr, when alocating %i"), texSize * 4));
	}
#endif //PBO_COUNT == 2
#endif
	POP_RANGE;
}

void RayTracer::subImage2D() 
{
	PUSH_RANGE("DrawPixels glTexSubImage2D", 4);
	GLCHECKERROR("");
#if PBO_COUNT > 0
#if COLOR_COMPONENTS == 3
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texWidth / texDownscaleFactor, texHeight / texDownscaleFactor, GL_RGB, GL_FLOAT, 0);
#else //COLOR_COMPONENTS == 3
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texWidth / texDownscaleFactor, texHeight / texDownscaleFactor, GL_RGBA, GL_FLOAT, 0);

#endif //COLOR_COMPONENTS == 3
#else //PBO_COUNT > 0
#if COLOR_COMPONENTS == 3
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texWidth / texDownscaleFactor, texHeight / texDownscaleFactor, GL_RGB, GL_FLOAT, texData);
#else //COLOR_COMPONENTS == 3
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texWidth / texDownscaleFactor, texHeight / texDownscaleFactor, GL_RGBA, GL_FLOAT, texData);
#endif //COLOR_COMPONENTS == 3
#endif //PBO_COUNT > 0
	GLCHECKERROR("");

	POP_RANGE;
}

int RayTracer::getFrame() {
	return frame;
}
#endif

void RayTracer::DrawFullscreenQuad()
{
	PUSH_RANGE("DrawQuad", 4);

	//Draw fullscreen quad
	if (useVertexArray)
	{
		glBindVertexArray(fsquadVAO);
	}
	else
	{
		glEnableVertexAttribArray(0);
		glEnableVertexAttribArray(1);
		glBindBuffer(GL_ARRAY_BUFFER, fsquadVBO);
		glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
		glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
	}

	if (useIndirectDraw)
	{
		glBindBuffer(GL_DRAW_INDIRECT_BUFFER, fsquadIBO);
		glDrawArraysIndirect(GL_TRIANGLE_STRIP, 0);
	}
	else
	{
		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	}

	POP_RANGE;
}

void RayTracer::DrawPixels(bool reset)
{
#ifdef USE_TEMPORAL
#ifndef VRUT_HPC
	if (texSize == 0)
		return;

	///use rendering pipeline from Temporal.cpp
	renderGl();
	
	

#endif //VRUT_HPC

	GLCHECKERROR("");
#else

#ifndef VRUT_HPC
	if (texSize == 0)
		return;
	PUSH_RANGE("DrawPixels bind", 4);
	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
	glBindTexture(GL_TEXTURE_2D, texID);

#if PBO_COUNT > 0
#if PBO_COUNT == 2

	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[currentPBO]);
	if (texData && !glMapBufferRange)
		glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); // release pointer to mapping buffer*/
#else //PBO_COUNT == 2
	glBindBufferARB(GL_PIXEL_UNPACK_BUFFER, pboIds[0]);
	if (!glMapBufferRange)
	{
		GLCHECKERROR(wxT("RayTracer::DrawPixels glBindBufferARB"));
		glBufferDataARB(GL_PIXEL_UNPACK_BUFFER, texSize * 4, 0, GL_STREAM_DRAW);//GL_WRITE_ONLY
		GLCHECKERROR(wxT("RayTracer::DrawPixels glBufferDataARB"));
		GLubyte* ptr = (GLubyte*)glMapBufferARB(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
		GLCHECKERROR(wxT("RayTracer::DrawPixels glMapBufferARB"));
		POP_RANGE;
		PUSH_RANGE("DrawPixels update", 4);
		if (ptr)
		{
			// update data directly on the mapped buffer
			if (texData)
			{
				PUSH_RANGE("DrawPixels memcpy", 4);
				memcpy(ptr, texData, texSize * 4);
				POP_RANGE;
			}
			PUSH_RANGE("DrawPixels glUnmapBufferARB", 4);
			glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER); // release pointer to mapping buffer
			POP_RANGE;
			GLCHECKERROR(wxT("RayTracer::DrawPixels glUnmapBufferARB"));
		}
		else
			LOGERROR(wxString::Format(wxT("RayTracer::DrawPixels glMapBufferARB returns nullptr, when alocating %i"), texSize * 4));
	}
#endif //PBO_COUNT == 2
	POP_RANGE;
	PUSH_RANGE("DrawPixels glTexSubImage2D", 4);
	GLCHECKERROR("");
#if COLOR_COMPONENTS == 3
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texWidth / texDownscaleFactor, texHeight / texDownscaleFactor, GL_RGB, GL_FLOAT, 0);
#else //COLOR_COMPONENTS == 3
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texWidth / texDownscaleFactor, texHeight / texDownscaleFactor, GL_RGBA, GL_FLOAT, 0);

#endif //COLOR_COMPONENTS == 3
#else //PBO_COUNT > 0
#if COLOR_COMPONENTS == 3
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texWidth / texDownscaleFactor, texHeight / texDownscaleFactor, GL_RGB, GL_FLOAT, texData);
#else //COLOR_COMPONENTS == 3
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texWidth / texDownscaleFactor, texHeight / texDownscaleFactor, GL_RGBA, GL_FLOAT, texData);
#endif //COLOR_COMPONENTS == 3
#endif //PBO_COUNT > 0
	GLCHECKERROR("");
#if USE_GL_FILTER
	if (filterRadius < 0)
	{
		// automatic filter radius
		float avgSamples = renderer->GetAvgSamples();
		float radius = 3;
		if ((avgSamples >= 1))
		{
			radius = avgSamples * std::abs(filterRadius) + 2;
			LOG(wxString::Format(wxT("radius %f"), radius));
		}
		glUniform1f(shaderFilterRadiusLoc, radius);
	}
	float avgLuminance = renderer->GetAvgLuminance();
	glUniform1f(shaderAvgLuminanceLoc, avgLuminance);
	glUniform1ui(shaderFrameIDLoc, (renderer->frameId) & 0x3FFF);
#endif // USE_GL_FILTER

	POP_RANGE;
	PUSH_RANGE("DrawPixels quad", 4);

	//Draw fullscreen quad
	DrawFullscreenQuad();

	// Render from FBO to HMD
	if (openVRWrapper)
		submitFrameToOpenVR();

#if PBO_COUNT == 2
	if (!reset && texData)
	{
		PUSH_RANGE("DrawPixels glMapBuffer", 4);
		if (!glMapBufferRange)
			texData = (float*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
		POP_RANGE;
	}
#endif //PBO_COUNT == 2
#endif //VRUT_HPC
	POP_RANGE;
	GLCHECKERROR("");
#endif
}

void RayTracer::IncFramerate()
{
	frameRate.IncFrame();
}

void RayTracer::InitializeToDrawNewFrame()
{
	if (!scene /*|| !bvh*/)
		return;

	///Some projection adjustments
#ifndef USE_TEMPORAL
	unsigned int w, h;
#endif

	getScreenWidthHeightFromHardwareOrWindow(w, h);

	SAFE_DELETE(cameras[0]);
	SAFE_DELETE(cameras[1]);
#ifndef VRUT_HPC
	if (selectedStereo==StereoType::OPENVR)
	{
		openVRWrapper->ComputeTransformations(Matrix(*sceneShadow->GetWorldMatrix(cameraID)));
		
		cameras[0] = GetCamera(w, h, cameraID, false, selectedStereo);
		cameras[1] = GetCamera(w, h, cameraID, true, selectedStereo);
	}
	else if (selectedStereo == StereoType::VRHERO)
	{
#ifndef NOVR
		vrHeroWrapper->ComputeTransformations(Matrix(*sceneShadow->GetWorldMatrix(cameraID)));

		cameras[0] = GetCamera(w, h, cameraID, false, selectedStereo);
		cameras[1] = GetCamera(w, h, cameraID, true, selectedStereo);
#endif
	}
	else
#endif
	{
		cameras[0] = GetCamera(w, h, cameraID, false, selectedStereo);
		//#if SPLIT_SCREEN_STEREO
		//	cameras[1] = GetCamera(w, h, true);
		//#endif
		if (activeStereo)
			cameras[1] = GetCamera(w, h, cameraID, true, selectedStereo);
	}

#ifndef USE_TEMPORAL
	if ((texData == nullptr || texWidth != w || texHeight != h || (isOpenVrSubsamplingChanged /*&& openVRWrapper*/)))
	{
		texWidth = w;
		texHeight = h;

		if (!slaveMode)
		{
			texSize = (size_t)(w/texDownscaleFactor) * (size_t)(h/texDownscaleFactor) * COLOR_COMPONENTS;

#if PBO_COUNT > 0
#ifndef VRUT_HPC
			if (texData)
			{
				GLCHECKERROR("");
				glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[0]);
				GLCHECKERROR("");
				glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); // release pointer to mapping buffer
				GLCHECKERROR("");
#if PBO_COUNT == 2
				glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[1]);
				GLCHECKERROR("");
				glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); // release pointer to mapping buffer
#endif //PBO_COUNT == 2
				GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glUnmapBuffer "));
			}
#endif
#else //PBO_COUNT > 0
			SAFE_DELETE_ARR(texData);
			texData = new GLfloat[texSize];
#endif //PBO_COUNT < 2

#ifndef VRUT_HPC
#if PBO_COUNT > 0
			glDeleteBuffers(PBO_COUNT, pboIds);
			glGenBuffers(PBO_COUNT, pboIds);
			GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glDeleteBuffers "));
			glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[0]);
			GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glBindBuffer 1 "));
			if (glMapBufferRange)
			{
				glBufferStorage(GL_PIXEL_UNPACK_BUFFER, texSize * 4, 0, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
				texData = (float*)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, texSize * 4, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
				GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glMapBufferRange "));
			}
			else
			{
				glBufferData(GL_PIXEL_UNPACK_BUFFER, texSize * 4, 0, GL_STREAM_DRAW);
				GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glBufferData "));
			}
			glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
			glBindTexture(GL_TEXTURE_2D, texID);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, w, h, 0, GL_RGBA, GL_FLOAT, nullptr);
#endif //PBO_COUNT > 0
#if PBO_COUNT == 2
			if (glMapBufferRange)
			{
				glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[1]);
				glBufferStorage(GL_PIXEL_UNPACK_BUFFER, texSize * 4, 0, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
				texData1 = (float*)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, texSize * 4, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
			}
			else
			{
				texData1 = (float*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
				glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[1]);
				GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame ke1 "));
				glBufferData(GL_PIXEL_UNPACK_BUFFER, texSize * 4, 0, GL_STREAM_DRAW);
				GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame ke2 "));
			}
#if COLOR_COMPONENTS == 3
			//glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, w, h, 0, GL_RGB, GL_FLOAT, nullptr);
			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w / texDownscaleFactor, h / texDownscaleFactor, GL_RGB, GL_FLOAT, nullptr);
#else //COLOR_COMPONENTS == 3
			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w/texDownscaleFactor, h/texDownscaleFactor, GL_RGBA, GL_FLOAT, nullptr);
#endif //COLOR_COMPONENTS == 3
			GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glTexImage2D "));
			glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[0]);
			currentPBO = 0;
			//texData = (float*)glMapBufferARB(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
			//memset(texData, 0, sizeof(float) * w * h * COLOR_COMPONENTS);
			GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glMapBuffer "));
#else //PBO_COUNT == 2
#if COLOR_COMPONENTS == 3
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, w, h, 0, GL_RGB, GL_FLOAT, nullptr);
#else //COLOR_COMPONENTS == 3
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, w, h, 0, GL_RGBA, GL_FLOAT, nullptr);
#endif //COLOR_COMPONENTS == 3
			GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glTexImage2D "));
#endif //PBO_COUNT == 2
			glViewport(0, 0, w, h);
			if (shader)
			{
				glUniform1f(shaderDownscaleLoc, float(texDownscaleFactor));
				glUniform2f(shaderResolutionLoc, w, h);
				GLCHECKERROR("");
			}
#endif //VRUT_HPC
		}

		isOpenVrSubsamplingChanged = false;
	}
#else
bool doIt = false;
for (int tex = 0; tex < NUM_TEMPORAL_TEXTURES; tex++) {
	if ((doIt || texData[tex] == nullptr || texWidth != w || texHeight != h || (isOpenVrSubsamplingChanged /*&& openVRWrapper*/)))
	{
		doIt = true;
		texWidth = w;
		texHeight = h;

		if (!slaveMode)
		{
			texSize = (size_t)(w / texDownscaleFactor) * (size_t)(h / texDownscaleFactor) * COLOR_COMPONENTS;

#if PBO_COUNT > 0
#ifndef VRUT_HPC
			if (texData[tex])
			{
				GLCHECKERROR("");
				glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[tex][0]);
				GLCHECKERROR("");
				glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); // release pointer to mapping buffer
				GLCHECKERROR("");
#if PBO_COUNT == 2
				glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[tex][1]);
				GLCHECKERROR("");
				glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); // release pointer to mapping buffer
#endif //PBO_COUNT == 2
				GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glUnmapBuffer "));
			}
#endif
#else //PBO_COUNT > 0
			SAFE_DELETE_ARR(texData[tex]);
			texData[tex] = new GLfloat[texSize];
#endif //PBO_COUNT < 2

#ifndef VRUT_HPC
#if PBO_COUNT > 0
			glDeleteBuffers(PBO_COUNT, pboIds[tex]);
			glGenBuffers(PBO_COUNT, pboIds[tex]);
			GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glDeleteBuffers "));
			glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[tex][0]);
			GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glBindBuffer 1 "));
			if (glMapBufferRange)
			{
				glBufferStorage(GL_PIXEL_UNPACK_BUFFER, texSize * 4, 0, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
				texData[tex] = (float*)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, texSize * 4, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
				GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glMapBufferRange "));
			}
			else
			{
				glBufferData(GL_PIXEL_UNPACK_BUFFER, texSize * 4, 0, GL_STREAM_DRAW);
				GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glBufferData "));
			}
			glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
			//glBindTexture(GL_TEXTURE_2D, texID[tex]);
			texCPU[tex].bind();
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, w, h, 0, GL_RGBA, GL_FLOAT, nullptr);
#endif //PBO_COUNT > 0
#if PBO_COUNT == 2
			if (glMapBufferRange)
			{
				glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[tex][1]);
				glBufferStorage(GL_PIXEL_UNPACK_BUFFER, texSize * 4, 0, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
				texData1[tex] = (float*)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, texSize * 4, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
			}
			else
			{
				texData1[tex] = (float*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
				glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[tex][1]);
				GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame ke1 "));
				glBufferData(GL_PIXEL_UNPACK_BUFFER, texSize * 4, 0, GL_STREAM_DRAW);
				GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame ke2 "));
			}
#if COLOR_COMPONENTS == 3
			//glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, w, h, 0, GL_RGB, GL_FLOAT, nullptr);
			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w / texDownscaleFactor, h / texDownscaleFactor, GL_RGB, GL_FLOAT, nullptr);
#else //COLOR_COMPONENTS == 3
			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w / texDownscaleFactor, h / texDownscaleFactor, GL_RGBA, GL_FLOAT, nullptr);
#endif //COLOR_COMPONENTS == 3
			GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glTexImage2D "));
			glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[tex][0]);
			currentPBO = 0;
			//texData = (float*)glMapBufferARB(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
			//memset(texData, 0, sizeof(float) * w * h * COLOR_COMPONENTS);
			GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glMapBuffer "));
#else //PBO_COUNT == 2
#if COLOR_COMPONENTS == 3
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, w, h, 0, GL_RGB, GL_FLOAT, nullptr);
#else //COLOR_COMPONENTS == 3
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, w, h, 0, GL_RGBA, GL_FLOAT, nullptr);
#endif //COLOR_COMPONENTS == 3
			GLCHECKERROR(wxT("RayTracer::InitializeToDrawNewFrame glTexImage2D "));
#endif //PBO_COUNT == 2
			glViewport(0, 0, w, h);
			//if (shader)
			{
				//glUniform1f(shaderDownscaleLoc, float(texDownscaleFactor));
				//glUniform2f(shaderResolutionLoc, w, h);
				shProg.importValue("texDownscaleRatio", float(texDownscaleFactor));
				shProg.importValue("resolution", Vector2(w, h));
				GLCHECKERROR("");
			}
#endif //VRUT_HPC
		}

		isOpenVrSubsamplingChanged = false;
	}
}
if (doIt)
	resize();
#endif

	//Clear remote tiles
	renderer->remoteTilesCount = 0;
	std::queue<std::shared_ptr<RenderTileData> > tmpQ;
	wxCriticalSectionLocker receivedTilesLocker(renderer->receivedTilesLock);
	std::swap(renderer->receivedTiles, tmpQ);
}

void RayTracer::getScreenWidthHeightFromHardwareOrWindow(unsigned int& width, unsigned int& height)
{
	switch (selectedStereo)
	{
		case StereoType::OPENVR:
			{
				width = openVRWrapper->GetRenderWidth();
				height = openVRWrapper->GetRenderHeight() * 2;
			}
			break;

		case StereoType::VRHERO:
			{
				width = vrHeroWrapper->GetRenderWidth();
				height = vrHeroWrapper->GetRenderHeight() * 2;
			}
			break;

		case StereoType::LEFT_BOTTOM:
		case StereoType::LEFT_TOP:
		case StereoType::MONO:
		{
			GLWindow* wnd = KERNEL->renderManager->GetGLWindow(this);
			if (wnd != nullptr)
				wnd->GetClientSize((int*)&width, (int*)&height);
			else
			{
				width = 640;
				height = 480;
			}
		}
		break;

		default:
			break;
	}
}

RayTracerCamera * RayTracer::GetCamera(unsigned int width, unsigned int height, NODE_ID cID, bool activeStereoEye, int selectedStereo)
{
    RayTracerCamera * cam = nullptr;
    smart_pointer<Camera> cameraNode;
    scene->GetNode(cameraNode, cID);
    /*if (!cameraNode)
    {
		width = height = 0;
        return nullptr;
    }*/

    Matrix worldMat;
    Matrix projMat;
	switch (selectedStereo)
	{
#ifndef VRUT_HPC  // TODO: the TMTMat
	case StereoType::OPENVR:
		{
			// LEFT eye
			if (!activeStereoEye)
			{
				worldMat = *openVRWrapper->GetCameraWorldMatrixL();
				projMat = *openVRWrapper->GetCameraProjectionMatrixL();
			}
			// RIGHT eye
			else
			{
				worldMat = *openVRWrapper->GetCameraWorldMatrixR();
				projMat = *openVRWrapper->GetCameraProjectionMatrixR();
			}
		}
		break;
#ifndef NOVR
	case StereoType::VRHERO:
		{
			// LEFT eye
			if (!activeStereoEye)
			{
				worldMat = *vrHeroWrapper->GetCameraWorldMatrixL();
				projMat = *vrHeroWrapper->GetCameraProjectionMatrixL();
			}
			// RIGHT eye
			else
			{
				worldMat = *vrHeroWrapper->GetCameraWorldMatrixR();
				projMat = *vrHeroWrapper->GetCameraProjectionMatrixR();
			}
		}
		break;
#endif
	case StereoType::LEFT_BOTTOM:
	case StereoType::LEFT_TOP:
		{
			worldMat = *sceneShadow->GetWorldMatrix(cID);

			//Get eye separation distance
			const float eyeSeparation = (cameraNode ? cameraNode->GetEyeSeparation() : 0);
			Vector3 pos = worldMat.ExtractTranslation();

			Matrix invWorld = worldMat;
			invWorld.InvertThisPrecise();
			const Vector3 right = Vector3(invWorld._m11, invWorld._m21, invWorld._m31).Normalize();

			Matrix m;
			if (!activeStereoEye) // LEFT eye
				pos += -right*0.5*eyeSeparation;
			else // RIGHT eye
				pos += right*0.5*eyeSeparation;

			worldMat._m41 = pos.x;
			worldMat._m42 = pos.y;
			worldMat._m43 = pos.z;

			const float aspectRatio = float(width) / float(height);
			projMat = scene->GetProjectionMatrix(cID, aspectRatio, activeStereoEye);
		}
		break;
#endif
	default:
	case StereoType::MONO:
		{
			const float aspectRatio = float(width) / float(height);
			worldMat = *sceneShadow->GetWorldMatrix(cID);

			projMat = scene->GetProjectionMatrix(cID, aspectRatio, activeStereoEye);
		}
		break;
	}
	Vector3 lookAt = (cameraNode ? cameraNode->GetLookAt() : Vector3(1, 0, 0));

    // setup a local camera for rendering
    switch (selectedCameraType)
    {
    case 0:
            cam = new PinholeCamera(worldMat, projMat, lookAt, 0, 0, useRadialDistortion);
            break;
    case 1:
            cam = new CylindricalCamera(worldMat, projMat, lookAt, 0, 0, hFOV, useRadialDistortion);
            break;
    case 2:
            cam = new SphericalCamera(worldMat, projMat, lookAt, hFOV, vFOV);
            break;
    case 3:
            cam = new OmniOrthoSphereCamera(worldMat, projMat, lookAt);
            break;
    case 4:
            cam = new Spherical3DPanoramaCamera(worldMat, projMat, lookAt);
            break;
    default:
            return nullptr;
    }
    return cam;
}

void RayTracer::updateRenderTarget(GLsizei w, GLsizei h)
{
#ifndef VRUT_HPC
	renderTarget.DeleteAllTextureAttachments();
	GLCHECKERROR(wxT("RayTracer::DrawFrame updateRenderTarget() deleteAllTexAttachments "));
	renderTarget.AddTextureAttachment(RenderTarget::TextureAttachmentParams(w, h, GL_RGBA8, GL_COLOR_ATTACHMENT0_EXT, GL_RGBA, GL_UNSIGNED_BYTE));
	GLCHECKERROR(wxT("RayTracer::DrawFrame updateRenderTarget() AddTextureAttachment "));
#endif
}

void RayTracer::submitFrameToOpenVR()
{
#ifndef VRUT_HPC
	//wxImage img(openVRWrapper->GetRenderWidth(), openVRWrapper->GetRenderHeight()*2);
	//glPixelStorei(GL_PACK_ALIGNMENT, 1);
	////glReadPixels(0, 0, 400, 800, GL_RGB, GL_UNSIGNED_BYTE, img.GetData());
	//renderTarget.Bind();
	//glReadPixels(0, 0, openVRWrapper->GetRenderWidth(), openVRWrapper->GetRenderHeight()*2, GL_RGB, GL_UNSIGNED_BYTE, img.GetData());
	//img.Mirror(false).SaveFile("ke.png");

	if (openVRWrapper)
		openVRWrapper->SubmitToHMDHorizontalSplit((renderTarget.GetTextureAttachmentOfType(GL_COLOR_ATTACHMENT0))->object);
	glFinish();

#endif
}

EXPORT_VRUT_BASE_MODULE_FUNCTIONS(RayTracer)
