#include "RendererUtil.h"
#include "WhittedRenderer.h"
#include "modulehandle.h"
#include "VisualDebugger.h"
#include "VRUT_NsightCustomEvents.h"
#include <wx/filename.h>
#include <fstream>
#include <cmath>

using namespace VRUT;


bool WhittedRenderer::GetTile(unsigned int & tileIndex)
{
	while (true) {
		// atomic operations used instead of critical section locker
		int index = ++tileCounter;
		if ((samplingStrategy != HALTON16_STRATEGY) && (samplingStrategy != HALTON8_STRATEGY)
			&& (samplingStrategy != HALTONFULL_STRATEGY))
		{
			if (index >= tileCounterStop)
			{
				// Decrease counter back just to be sure it never overflows
				--tileCounterStop;
				return false;
			}
		}
		tileIndex = index % tileCount;

		if (++tiles[tileIndex].skip == 1)
		{
			if (!tiles[tileIndex].Complete(samplesPerPixel))
				break;
		}
	}

	// MD: Do not set LOD. Before the start, each Tile has to set it to 0 and after each render cycle it is increased
	// tiles[tileIndex].LOD = index / tileCount;

	// if it reaches last block, the time is measured and displayed
	if (tileIndex == (tileCount - 1))
	{
		wxULongLong newTime = VRUTGetTime();
		wxULongLong time = newTime - lastTime;
		unsigned int samples = samplesOverall - lastSamples;

		lastSamples = samplesOverall;
		lastTime = newTime;

		// evaluate performance in MSamples/s assuming only primary rays are cast
		float avgSamples = samplesOverall / float(texWidth * texHeight);
		float performance = samples / time.ToDouble();
#if 1
		LOG(wxString::Format("<RayTracer> (%ix%i) Avg. Samples %f, time ", texWidth, texHeight, avgSamples) +
			time.ToString() + wxString::Format(wxT(", %f MSamples/s"), performance));
#endif
		//		samplesOverall = 0;
	}
	return true;
}

void WhittedRenderer::RenderTile(unsigned int tileIndex, const int workerIndex)
{
	Vector2 *pixels = workers[workerIndex]->renderBuffers.pixels;
	Vector4 *colors = workers[workerIndex]->renderBuffers.colors;
#ifdef USE_TEMPORAL
	Vector4 *diffuse = workers[workerIndex]->renderBuffers.diffuse;
	Vector4 *normalDepth = workers[workerIndex]->renderBuffers.normalDepth;
#endif
	RayStream & rayStream = *workers[workerIndex]->inputRayStream;
	int samples = 0;
	int skip = tiles[tileIndex].skip;
	bool isComplete = tiles[tileIndex].Complete(samplesPerPixel);
	while (skip > 0 && !isComplete)
	{
		tiles[tileIndex].Reset();
		unsigned int cameraIndex = tiles[tileIndex].cameraIndex;
		RayTracerCamera *camera = cameras[cameraIndex];
		while (SamplePixels(pixels, tiles[tileIndex]))
		{
			rayStream.Clear();
			// fill bundle
			for (int i = 0; i < int(tiles[tileIndex].samples); ++i)
			{
				Ray ray = camera->PixelToRay(pixels[i].x, pixels[i].y, texWidth, texHeight);
				ray.t = FLT_MAX;
				rayStream.PushRay(ray, i, 1.0f);
			}
			memset(colors[0]._v, 0, tiles[tileIndex].samples * sizeof(float) * 4);
			// trace bundle
			CastAndShade(rayStream, colors, cameraIndex, workerIndex);

#ifdef USE_TEMPORAL
			//TODO: probably slow
			if (useTemporalPathTracing) {
				for (int y = tiles[tileIndex].min.y; y < tiles[tileIndex].max.y; y++) {
					for (int x = tiles[tileIndex].min.x; x < tiles[tileIndex].max.x; x++) {
						WriteColorToTexData4(cameraIndex, x, y, Vector4(0, 0, 0, 0), 0);
						//WriteColorToTexData4(cameraIndex, x, y, Vector4(0, 0, 0, 0), 2);
					}
				}
			}
#endif

			if (tiles[tileIndex].antialiasing)
			{
#ifndef USE_TEMPORAL
				for (unsigned int i = 0; i < tiles[tileIndex].samples; ++i)
				{
					colors[i].w = 1.0f;
					if (pixels[i].x != unsigned(pixels[i].x) || pixels[i].y != unsigned(pixels[i].y))
						WriteColorToTexDataAdd(cameraIndex, unsigned(pixels[i].x), unsigned(pixels[i].y), colors[i]);
					else
					{
						WriteColorToTexData(cameraIndex, unsigned(pixels[i].x), unsigned(pixels[i].y), colors[i]);
#if COMPUTE_LUMINANCE
						if (tiles[tileIndex].LOD == 0)
							workers[workerIndex]->sumLuminance += Vector2(log(Luminance(colors[i]) + 1.0e-3f), colors[i].w);
#endif
					}
				}
#else
				for (unsigned int i = 0; i < tiles[tileIndex].samples; ++i)
				{
					colors[i].w = 1.0f;

					if (pixels[i].x != unsigned(pixels[i].x) || pixels[i].y != unsigned(pixels[i].y))
						WriteColorToTexDataAdd(cameraIndex, unsigned(pixels[i].x), unsigned(pixels[i].y), colors[i], 0);
					else
					{
						WriteColorToTexData(cameraIndex, unsigned(pixels[i].x), unsigned(pixels[i].y), colors[i], 0);
#if COMPUTE_LUMINANCE
						if (tiles[tileIndex].LOD == 0)
							workers[workerIndex]->sumLuminance += Vector2(log(Luminance(colors[i]) + 1.0e-3f), colors[i].w);
#endif
					}
				}
#endif
			}
			else
			{
#ifndef USE_TEMPORAL
				for (unsigned int i = 0; i < tiles[tileIndex].samples; ++i)
				{
					colors[i].w = 1.0f;
					WriteColorToTexData(cameraIndex, unsigned(pixels[i].x), unsigned(pixels[i].y), colors[i]);
#if COMPUTE_LUMINANCE
					if (tiles[tileIndex].LOD == 0)
						workers[workerIndex]->sumLuminance += Vector2(log(Luminance(colors[i]) + 1.0e-3f), colors[i].w);
#endif
				}
#else
				for (unsigned int i = 0; i < tiles[tileIndex].samples; ++i)
				{
					colors[i].w = 1.0f;
					if (useTemporalPathTracing) {
						WriteColorToTexData(cameraIndex, unsigned(pixels[i].x), unsigned(pixels[i].y), colors[i], 0);
					}
					else {
						WriteColorToTexData(cameraIndex, unsigned(pixels[i].x), unsigned(pixels[i].y), colors[i], 0);
					}


					if (useTemporalPathTracing) {
						WriteColorToTexData4(cameraIndex, unsigned(pixels[i].x), unsigned(pixels[i].y), diffuse[i], 1);
						WriteColorToTexData4(cameraIndex, unsigned(pixels[i].x), unsigned(pixels[i].y), normalDepth[i], 2);
					}
#if COMPUTE_LUMINANCE
					if (tiles[tileIndex].LOD == 0)
						workers[workerIndex]->sumLuminance += Vector2(log(Luminance(colors[i]) + 1.0e-3f), colors[i].w);
#endif
				}
#endif
			}
			samples += tiles[tileIndex].samples;

			if (rerender)
				return;
		}

		if (tiles[tileIndex].LOD == 0)
		{
			unsigned int tmpPixelsHit = ++lod0ProcessedTiles;
			if (tmpPixelsHit == tileCount)
			{
				firstLODSemaphore.Post();
			}
		}
		++tiles[tileIndex].LOD;
		isComplete = tiles[tileIndex].Complete(samplesPerPixel);
		// Store result of the decrementaion to the local variable. The value of the member may change before the loop test
		skip = --tiles[tileIndex].skip;
	}
	//	samplesOverall += tiles[tileIndex].samplesOverall;
	samplesOverall += samples;
}


void WhittedRenderer::RenderRemoteTile(RenderTileData & tileData, const int workerIndex)
{
	// copy the geometric tile bounds - $$JB temporary solution - will not work over the network!
	//Tile *tile = &tiles[tileData.tileIndex];
	Tile tmpTile;
	Tile * tile = &tmpTile;

#if 0
	if ((tileData.tileIndex % 100) == 0)
		LOG(wxString::Format("o=%f %f %f, w=%i h=%i", camera->origin.x, camera->origin.y, camera->origin.z, tileData.imageWidth, tileData.imageHeight));
#endif
	//LOG(wxString::Format("Tile render idx=%i skip=%i lod=%i", tileData.tileIndex, skip, tile->LOD));

	RayStream & rayStream = *workers[workerIndex]->inputRayStream;
	Vector2 *pixels = workers[workerIndex]->renderBuffers.pixels;
	Vector4 *colors = workers[workerIndex]->renderBuffers.colors;

	tile->LOD = tileData.LOD;
	tile->min = tileData.minVal;
	tile->max = tileData.maxVal;
	tile->skip = tileData.skip;
	tile->cameraIndex = tileData.cameraIndex;
	tile->antialiasing = tileData.antialiasing;
    tile->samplesOverall = 0;

    std::map<uint64_t, unsigned int> pixelMap;
    const bool isMapUsed = GetSampler(*tile)->GenPixMapIsNecessary(pixelMap, tile, 0);
    const unsigned int numPx = pixelMap.empty() ? tile->Size() : pixelMap.size();

    tileData.numPixels = numPx;
    tileData.pixels = std::shared_ptr<Vector4>(new Vector4[tileData.numPixels], std::default_delete<Vector4[]>());

	memset(tileData.pixels.get(), 0, tileData.numPixels*sizeof(Vector4));
	int sizex = tile->max.x - tile->min.x;

	int samples = 0;

	int skip = tile->skip;
	while (skip > 0)
	{
		// update the pass info
		tile->Reset();
		unsigned int cameraIndex = tile->cameraIndex;
		RayTracerCamera *camera = cameras[cameraIndex];
		// loop until the current LOD is handled
		while (SamplePixels(pixels, *tile))
        {
			rayStream.Clear();
			// fill bundle
			for (int i = 0; i < int(tile->samples); ++i) {
				Ray ray = camera->PixelToRay(pixels[i].x, pixels[i].y, tileData.imageWidth, tileData.imageHeight);
				ray.t = FLT_MAX;
				rayStream.PushRay(ray, i, 1.0f);
			}
			memset(colors[0]._v, 0, tile->samples * sizeof(float) * 4);
			// trace bundle
			CastAndShade(rayStream, colors, cameraIndex, workerIndex);
			for (int i = 0; i < int(tile->samples); ++i) {
				int index = isMapUsed ? pixelMap[AbstractSampler::Vec2iToU64(int(pixels[i].x), int(pixels[i].y))] :
					(unsigned(pixels[i].y) - tile->min.y)*sizex +
					unsigned(pixels[i].x) - tile->min.x;
				// make sure one sample is added
				colors[i].w = 1.0f;
				tileData.pixels.get()[index] += colors[i];
			}
			samples+=tile->samples;
		}
		++tile->LOD;
		// Store result of the decrementaion to the local variable. The value of the member may change before the loop test
		skip = --tile->skip;
	}
	tileData.LOD = tile->LOD;
	tileData.antialiasing = tile->antialiasing;
	// set the number of samples computed for this tile
	tileData.samples = samples;
}

void WhittedRenderer::StartWorkers(int count)
{
	// Workers shall not be started before they finish previous work cycle
	assert(0 == activeWorkers);
	// Decrease semaphore counter if it is positive. The first LOD may not have been finished in previous run, so only try.
	firstLODSemaphore.TryWait();
	// Start workers
	activatedWorkers = std::min<int>(workers.size(), count);
	activeWorkers = activatedWorkers;
	if (activatedWorkers)
	{
		for (int i = 0; i < activeWorkers; ++i)
		{
			workers[i]->ClearTileQueue();
			workers[i]->semaphore.Post();
#if COMPUTE_LUMINANCE
			workers[i]->sumLuminance = Vector2(0.0f, 0.0f);
#endif
		}
	}
	else
	{
		// No worker was started, this means none is working
		notWorkingSemaphore.Post();
	}
}

void WhittedRenderer::WorkerFinished(int id)
{
	if (wxAtomicDec(activeWorkers) == 0)
	{
		notWorkingSemaphore.Post();
	}
}

WhittedRenderer::WhittedRenderer(RayTracer *_rayTracer) : AbstractRenderer(_rayTracer), activatedWorkers(0), activeWorkers(0), firstLODSemaphore(1, 1), tiles(nullptr), tileCount((unsigned int)-1), lod0ProcessedTiles(0), remoteTilesCount(0)
{
	// set number of created working threads
	int numThreads = __max(1, wxThread::GetCPUCount());
#ifndef NDEBUG
	//numThreads = 1;
#endif
	// create working threads
	for (int i = 0; i < numThreads; ++i)
	{
		workers.push_back(new WhittedRendererWorker(this, i));
		workers[i]->Create();
		workers[i]->SetPriority(WXTHREAD_MIN_PRIORITY);
		workers[i]->Run();
	}

	//we must increment the semaphore, because when it is used first time the threads are not initialized
	auto a = notWorkingSemaphore.Post();
	if (a != wxSEMA_NO_ERROR) LOGERROR(wxString::Format("Semaphore error %d", a));
	LOG(wxString::Format(wxT("<RayTracer> number of threads: %i"), numThreads));
}

void WhittedRenderer::SetSlaveMode(const bool isSlave)
{
	if (isSlave)
	{
		LOG(wxString::Format("<RayTracer> Setting slave mode"));
		// set all workers as servers
		for (auto &worker : workers)
			worker->mode = WhittedRendererWorker::SLAVE;
	}
	else
	{
		LOG(wxString::Format("<RayTracer> Setting master mode"));
		// set half of the clients as remote clients
		int numClients = workers.size()/2;
		for (int i = 0; i < numClients; i++)
			workers[workers.size() - i - 1]->mode = WhittedRendererWorker::MASTER;
	}
}

WhittedRenderer::~WhittedRenderer()
{
	// Stop work and wait until workers finish current work cycle
	rerender = true;
	WaitUntilAllWorkDone();
	// Stop all worker threads
	for (auto &worker : workers)
	{
		worker->Exit();
	}
	// Wait until all workers are destroyed. Each worker resets the pointer to itself.
	// Active waiting should be sufficient, because workers are destroyed almost immediatelly.
	bool workerExists = true;
	while (workerExists)
	{
		workerExists = false;
		for (auto &worker : workers)
		{
			if (worker)
			{
				workerExists = true;
			}
		}
	}
	// Rest of the destrutor
	SAFE_DELETE_ARR(tiles);
	SAFE_DELETE_ARR(this->cameras);
	SAFE_DELETE_ARR(this->texData);
}

void WhittedRenderer::WaitForFirstLOD()
{
	// If there is no active worker, noone will ever finish the first LOD, so the waiting may cause deadlock
	if (sync && activatedWorkers) // sync rendering
	{
		//firstLODSemaphore.Wait();
		//AM: workaround for deadlock; I not understand why sometimes the firstLODSemaphore.Post() is not called by the last thread, so at least detect these cases and prevent deadlock
		//LOGWARNING("Waiting on semaphore to signal");
		while (firstLODSemaphore.WaitTimeout(10) == wxSEMA_TIMEOUT)
		{
			//unsigned int tmpPixelsHit = lod0ProcessedTiles;
			//LOG(wxString::Format(wxT("<RayTracer> WaitForFirstLOD lod0ProcessedTiles=%i/%i"), tmpPixelsHit, tileCount));
			if (activeWorkers == 0)
			{
				LOGERROR(wxT("WhittedRenderer::WaitForFirstLOD error occured, no workers and not signaled"));
				return;
			}
		}
		//LOGERROR("SEMAPHORE SIGNALED");
	}
}

void WhittedRenderer::RenderSlave(SceneShadow * sceneShadow, unsigned int camerasCount, RayTracerCamera ** cameras, RayTracerBHV * bvh, bool & sceneChanged, bool & lightChanged)
{
	// save camera, width, height etc. here
	this->sceneShadow = sceneShadow;
	this->scene = sceneShadow->GetScene();
	this->camerasCount = camerasCount;
	SAFE_DELETE_ARR(this->cameras);
	this->cameras = new RayTracerCamera*[camerasCount];
	for (unsigned int i = 0; i < camerasCount; ++i)
	{
		this->cameras[i] = cameras[i];
	}

	// This must be before workers are started. If it is true, workers stops current work cycle
	rerender = false;
	this->bvh.WorkersBuildGeoAccStart();
	StartWorkers(renderThreads);

	//	if need rebuild ray tracer data structure
	if (sceneChanged) {
		this->bvh.Update(bvh, sceneShadow, materialCache);
		sceneChanged = false;
	}

	if (lightChanged) {
		CacheLightInfo();
		lightChanged = false;
	}
	// Update headlights even when other lights are not changed, camera may have moved or changed
	CacheHeadlightInfo();

	materialCache.CacheEnvironmentalMap(sceneShadow);

	// initialize variables to render new picture
	this->bvh.WorkersBuildGeoAccStop();

	// sync rendering
	if (!workers.empty()) {
		wxThread::Sleep(1);
	}
}

#ifndef USE_TEMPORAL
void WhittedRenderer::Render(SceneShadow * sceneShadow, unsigned int camerasCount, RayTracerCamera ** cameras, unsigned int texWidth, unsigned int texHeight, float ** texData, RayTracerBHV * bvh, bool & sceneChanged, bool & lightChanged)
#else
void WhittedRenderer::Render(SceneShadow * sceneShadow, unsigned int camerasCount, RayTracerCamera ** cameras, unsigned int texWidth, unsigned int texHeight, float*** texData, RayTracerBHV * bvh, bool & sceneChanged, bool & lightChanged)
#endif
{
	PUSH_RANGE("WR Render start", 4);
	// increment the frame id counter
	++frameId;

	//Notify slave(s) to redraw
	rayTracer->PostToKernel(GET_EVT_RENDER_UPDATE(0, frameId));//TODO: use correct WINDOW_ID 27.10.2017

	// should rebuild tiles?
	bool rebuildTiles = (this->texWidth != texWidth) || (this->texHeight != texHeight);

	// save camera, width, height etc. here
	this->sceneShadow = sceneShadow;
	this->scene = sceneShadow->GetScene();
	this->camerasCount = camerasCount;
	SAFE_DELETE_ARR(this->cameras);
#ifndef USE_TEMPORAL
	SAFE_DELETE_ARR(this->texData);
#else
	if (this->texData)
	for (int tex = 0; tex<TEXTURES; tex++)
		SAFE_DELETE_ARR(this->texData[tex]);
	SAFE_DELETE_ARR(this->texData);
#endif
	this->cameras = new RayTracerCamera*[camerasCount];
#ifndef USE_TEMPORAL
	this->texData = new float*[camerasCount];
#else
	this->texData = new float**[TEXTURES];
	for (int tex = 0; tex<TEXTURES; tex++)
		this->texData[tex] = new float*[camerasCount];
#endif
	for (unsigned int i = 0; i < camerasCount; ++i)
	{
#ifndef USE_TEMPORAL
		this->cameras[i] = cameras[i];
		this->texData[i] = texData[i];
		if (!this->cameras[i])
			this->camerasCount = 0;
#else
		this->cameras[i] = cameras[i];
		for (int tex = 0; tex<TEXTURES; tex++)
			this->texData[tex][i] = (*texData)[tex + i*TEXTURES];
		if (!this->cameras[i])
			this->camerasCount = 0;
#endif
	}
	this->texWidth = texWidth;
	this->texHeight = texHeight;

	// clear memory before rendering the image
#if PBO_COUNT != 2
#if USE_GL_FILTER
/*	POP_RANGE;
	PUSH_RANGE("WR Render memset", 4);
	for (unsigned int i = 0; i < camerasCount; ++i)
	{
		memset(texData[i], 0, sizeof(float) * texWidth * texHeight * COLOR_COMPONENTS);
	}*/
#endif
#endif
	POP_RANGE;
	PUSH_RANGE("WR Render WorkersBuildGeoAccStart", 4);

	// This must be before workers are started. If it is true, workers stops current work cycle
	rerender = false;
	this->bvh.WorkersBuildGeoAccStart();
	StartWorkers(renderThreads);
	POP_RANGE;
	PUSH_RANGE("WR Render updates", 4);

	//	if need rebuild ray tracer data structure
	if (sceneChanged) {
		this->bvh.Update(bvh, sceneShadow, materialCache);
		sceneChanged = false;
	}

	if (lightChanged) {
		CacheLightInfo();
		lightChanged = false;
	}
	// Update headlights even when other lights are not changed, camera may have moved or changed
	CacheHeadlightInfo();

	POP_RANGE;
	PUSH_RANGE("WR Render cache", 4);
	materialCache.CacheEnvironmentalMap(sceneShadow);

	POP_RANGE;
	PUSH_RANGE("WR Render rebuildTiles", 4);
	// number of 32x32x tiles covering one level of screen
	unsigned int actualTileCount = IntDivCeilLog(texWidth, LOG_TILE_SIZE) * IntDivCeilLog(texHeight, LOG_TILE_SIZE) * camerasCount;
	rebuildTiles = rebuildTiles || (tiles == nullptr) || (tileCount != actualTileCount);
	//if (tiles == nullptr || tileCount != actualTileCount)
	if (rebuildTiles)
	{
		SAFE_DELETE_ARR(tiles);
		tileCount = actualTileCount;
		tiles = new Tile[tileCount];
		for (unsigned int i = 0; i < tileCount; ++i)
		{
			// Cycle tiles for cameras
			unsigned int pos = i / camerasCount;
			tiles[i].min.x = (pos % IntDivCeilLog(texWidth, LOG_TILE_SIZE)) * TILE_SIZE;
			tiles[i].min.y = (pos / IntDivCeilLog(texWidth, LOG_TILE_SIZE)) * TILE_SIZE;
			tiles[i].max.x = __min(tiles[i].min.x + TILE_SIZE, int(texWidth));
			tiles[i].max.y = __min(tiles[i].min.y + TILE_SIZE, int(texHeight));
			tiles[i].cameraIndex = i % camerasCount;
		}
	}
	POP_RANGE;
	PUSH_RANGE("WR Render reset tile", 4);

	// reset tile
	for (unsigned int i = 0; i < tileCount; ++i)
	{
		tiles[i].samplesOverall = 0;
		tiles[i].skip = 0;
		tiles[i].LOD = 0;
		tiles[i].antialiasing = false;
	}

	// index of current tiles index
	tileCounter = -1;
	tileCounterStop = tileCount * MaxLODForSamples(samplesPerPixel);

	// init sampled pixels and time
	samplesOverall = 0;
	lod0ProcessedTiles = 0;
	lastTime = VRUTGetTime();
	lastSamples = 0;

	// log BVH cost
	//LOG(wxString::Format("The global cost of BVH is %f.", this->bvh.GetCost()));

	// initialize variables to render new picture
	this->bvh.WorkersBuildGeoAccStop();
	POP_RANGE;

}

void WhittedRenderer::RenderImage(SceneShadow * sceneShadow, RayTracerCamera * camera, unsigned int texWidth, unsigned int texHeight, const wxString & filename, RayTracerBHV * bvh, bool & sceneChanged,
	bool & lightChanged, bool save, bool addImage, int cubeSide, const wxString & imageRefName)
{
	// wait while some thread do some work
	WaitUntilAllWorkDone();

	// backup
	smart_pointer_v2<Scene> sceneBackup = this->scene;
	SceneShadow * sceneShadowBackup = this->sceneShadow;
	unsigned int camerasCountBackup = camerasCount;
	RayTracerCamera ** camerasBackup = cameras;
	unsigned int texWidthBackup = texWidth;
	unsigned int texHeightBackup = texHeight;
#ifndef USE_TEMPORAL
	float ** texDataBackup = texData;
#else
	float ** texDataBackup[TEXTURES];
	for (int tex = 0; tex < TEXTURES; tex++)
		texDataBackup[tex] = texData[tex];
#endif

	// initialize variables to render new picture
	this->sceneShadow = sceneShadow;
	this->scene = sceneShadow->GetScene();
	this->camerasCount = 1;
	this->cameras = new RayTracerCamera*[1];
	this->cameras[0] = camera;
	this->texWidth = texWidth;
	this->texHeight = texHeight;
	size_t tmpSize = (size_t)texWidth * (size_t)texHeight * COLOR_COMPONENTS;
#ifndef USE_TEMPORAL
	texData = new float*[1];
	texData[0] = new float[tmpSize];
	memset(texData[0], 0, sizeof(float)*tmpSize);
#else
	for (int tex = 0; tex < TEXTURES; tex++) {
		texData[tex] = new float*[1];
		texData[tex][0] = new float[tmpSize];
		memset(texData[tex][0], 0, sizeof(float)*tmpSize);
	}
#endif

	// This must be before workers are started. If it is true, workers stops current work cycle
	rerender = false;
	this->bvh.WorkersBuildGeoAccStart();
	StartWorkers(renderThreads);

	//	if need rebuild ray tracer data structure
	if (sceneChanged) {
		this->bvh.Update(bvh, sceneShadow, materialCache);
		sceneChanged = false;
	}

	if (lightChanged) {
		CacheLightInfo();
		lightChanged = false;
	}
	// Update headlights even when other lights are not changed, camera may have moved or changed
	CacheHeadlightInfo();

	materialCache.CacheEnvironmentalMap(sceneShadow);

	unsigned int actualTileCount = IntDivCeilLog(texWidth, LOG_TILE_SIZE) * IntDivCeilLog(texHeight, LOG_TILE_SIZE);
	if (tiles == nullptr || tileCount != actualTileCount)
	{
		SAFE_DELETE_ARR(tiles);
		tileCount = actualTileCount;
		tiles = new Tile[tileCount];
		for (unsigned int i = 0; i < tileCount; ++i)
		{
			tiles[i].min.x = (i % IntDivCeilLog(texWidth, LOG_TILE_SIZE)) * TILE_SIZE;
			tiles[i].min.y = (i / IntDivCeilLog(texWidth, LOG_TILE_SIZE)) * TILE_SIZE;
			tiles[i].max.x = __min(tiles[i].min.x + TILE_SIZE, int(texWidth));
			tiles[i].max.y = __min(tiles[i].min.y + TILE_SIZE, int(texHeight));
			tiles[i].cameraIndex = 0;
			//tiles[i].cameraIndex = i % camerasCount;
		}
	}

	// reset tile
	for (unsigned int i = 0; i < tileCount; ++i)
	{
		tiles[i].samplesOverall = 0;
		tiles[i].skip = 0;
		tiles[i].LOD = 0;
		tiles[i].antialiasing = false;
	}

	// atomic init here
	tileCounter = -1;
	tileCounterStop = tileCount * MaxLODForSamples(samplesPerPixel);

	// init sampled pixels and time
	samplesOverall = 0;
	//lod0ProcessedTiles = 0;
	lastTime = VRUTGetTime();
	lastSamples = 0;

	this->bvh.WorkersBuildGeoAccStop();

	LOG(wxString::Format("The global cost of BVH is %f.", this->bvh.GetCost()));

	// workaround, it must somewhere before img.SaveFile
	// wait while some thread do some work
	//
	WaitUntilAllWorkDone();

	// save image
	unsigned char *rgbdata = (unsigned char *)malloc((size_t)texWidth * (size_t)texHeight * 3);
	if (rgbdata)
	{
		size_t posF = 0;
		for (unsigned int j = 0; j < texHeight; ++j)
		{
			size_t posI = (size_t)(texWidth) * (size_t)(texHeight - j - 1) * 3;
			for (unsigned int i = 0; i < texWidth; ++i)
			{
#ifndef USE_TEMPORAL
				float rtmp = *(texData[0] + posF);
				float gtmp = *(texData[0] + posF + 1);
				float btmp = *(texData[0] + posF + 2);
#else
				float rtmp = *(texData[0][0] + posF);
				float gtmp = *(texData[0][0] + posF + 1);
				float btmp = *(texData[0][0] + posF + 2);
#endif
#if COLOR_COMPONENTS == 4
#if USE_GL_FILTER
				float atmp = *((unsigned int*)(texData[0] + posF + 3)) & 0xFFFF;
				if (atmp > 0)
				{
					rtmp /= atmp;
					gtmp /= atmp;
					btmp /= atmp;
				}
				else
				{
					rtmp = 0.0f;
					gtmp = 0.0f;
					btmp = 0.0f;
				}
#endif
#endif
				ToneMapReinhard(rtmp, gtmp, btmp, rayTracer->GetToneMappingKeyValue(), rayTracer->GetToneMappingWhitePoint());
				rtmp = __min(1.0f, __max(0.0f, rtmp));
				gtmp = __min(1.0f, __max(0.0f, gtmp));
				btmp = __min(1.0f, __max(0.0f, btmp));
				*(rgbdata + posI) = (unsigned char)(rtmp * 255.0f);
				*(rgbdata + posI + 1) = (unsigned char)(gtmp * 255.0f);
				*(rgbdata + posI + 2) = (unsigned char)(btmp * 255.0f);
				posF += COLOR_COMPONENTS;
				posI += 3;
			}
		}
		wxImage img(texWidth, texHeight, rgbdata, false);
		if (img.IsOk())
		{
			if (save)
			{
#ifdef VRUT_HPC
				wxFileName fname(filename);
				wxString filenameExt = filename;
				if (!fname.GetPath().empty())
					filenameExt = fname.GetPath() + fname.GetPathSeparator() + fname.GetName();
				else
					filenameExt = fname.GetName();

				img.SaveFile(filenameExt + wxT("_") + wxGetHostName() + /*wxT("_RayTracer.") +*/ fname.GetExt());
#else
				img.SaveFile(filename);
#endif
			}

			if (addImage)
			{
				ImageCommon * imcommon = new ImageCommon(nullptr);
				img = img.Mirror(false);
				imcommon->SetImage(&img);
				if (imcommon->IsOk())
					rayTracer->PostToKernel(GET_EVT_SCENE_IMAGE_ADD(filename, imcommon, cubeSide, imageRefName));
				else
					delete imcommon;
			}
		}
		else
			LOGERROR(wxT("<WhittedRenderer::RenderImage> Cannot create image"));
	}
	else
		LOGERROR(wxT("<WhittedRenderer::RenderImage> Cannot allocate image"));

	Render(sceneShadowBackup, camerasCountBackup, camerasBackup, texWidthBackup, texHeightBackup, texDataBackup, bvh, sceneChanged, lightChanged);

}

void WhittedRenderer::WaitUntilAllWorkDone()
{
	// wait while some thread do some work
	notWorkingSemaphore.Wait();
}

void WhittedRenderer::Shade(const RayStream & rayStream, Vector4 bounceColors[], Vector4 diffuseColors[], Vector4 texColors[], bool usedTexs[], unsigned int cameraIndex, const int workerIndex)
{
	RayStream & shadowRayStream = *workers[workerIndex]->shadowRayStream;
	Vector4 *lightColors = workers[workerIndex]->renderBuffers.shadeBuffers.lightColors;
	Vector4 *shadowColors = workers[workerIndex]->renderBuffers.shadeBuffers.shadowColors;

	// bvh node infos
	auto & nodeInfos = bvh.GetNodeInfos();

	// intersection info
	const RayIntersectionInfo * intersectInfos = rayStream.GetRayIntersectionInfos();

	// rays
	const Ray * rays = rayStream.GetRays();

	// normals
	Vector3 *normals = workers[workerIndex]->renderBuffers.shadeBuffers.normals;

	// bounding box radius
	float radius = bvh.Radius();
	const int * pixelIndices = rayStream.GetPixelIndices(); //JK 24.1.2018 DEBUG
	// reset direct colors
	memset((void*)bounceColors, 0, sizeof(Vector4) * rayStream.GetNumberOfRays());

	for (unsigned int i = 0; i < rayStream.GetNumberOfRays(); ++i)
	{
		if (intersectInfos[i].nodeID != NODE_ID_NONE)
		{
			// test normal direction
			normals[i] = intersectInfos[i].normal;
			float cosPhi1 = normals[i].Dot(rays[i].direction);
			if (cosPhi1 > 0.0f)
				normals[i] = -normals[i];
			// cache diffuse materials
			if (intersectInfos[i].nodeID < nodeInfos.size())
			{
				usedTexs[i] = false;
				GEOMETRY_ID gid = nodeInfos[intersectInfos[i].nodeID].geometryID;
				BVHBottom * geomAccelerator = (intersectInfos[i].nodeID == NODE_ID_NONE) ? nullptr : bvh.GetGeomAccelerators()[gid];
				float u = 0.0f, v = 0.0f;
				Vector2 * texCoord1, * texCoord2, * texCoord3;
				if (geomAccelerator)
				{
					geomAccelerator->GetTextureCoordsAt(intersectInfos[i].index, texCoord1, texCoord2, texCoord3);
					materialCache.GetTriangleTexCoords(intersectInfos[i], texCoord1, texCoord2, texCoord3, u, v);
					usedTexs[i] = true;
				}
				const RTMaterial *material = nodeInfos[intersectInfos[i].nodeID].material;
				materialCache.GetMaterialDiffuseColor(*material, rays[i], intersectInfos[i], u, v, rayOffset, diffuseColors[i], usedTexs[i], texColors[i]);
				//if (usedTexs[k] && (material->texFlags & Material::BUMP_MAP)) {
				if (material->flags & RTMaterial::HAS_BUMPMAPPING)
				{
					// Apply bump mapping
					// this has to be scaled properly and use a local frame to produce consistent
					// orientation of bump mapped normals
					// coordinate basis of a geometry frame at the intersection point
					Vector3 normal = normals[i];
					// cached vertices of the triangle
					Vector3 v1, v2, v3;
					// basis vector of the shading frame
					Vector3 tangent, bitangent;
					// get ray proxy
					const Ray &ray = rays[i];
					// get vertices of the triangle for computing a shading frame for the intersection
					geomAccelerator->GetVerticesAt(intersectInfos[i].index, v1, v2, v3);
					// position differentials
					Vector3 dP1 = v2 - v1, dP2 = v3 - v1;
					// texture differential
					Vector2 dUV1 = *texCoord2 - *texCoord1, dUV2 = *texCoord3 - *texCoord1;
					//Vector3 n = Normalize(CrossProd(dP1, dP2));
					float determinant = dUV1.x * dUV2.y - dUV1.y * dUV2.x;
					if (determinant == 0.0f) {
						// The user-specified parameterization is degenerate. Pick
						// arbitrary tangents that are perpendicular to the geometric normal
						normal.RightHandedBase(tangent, bitangent);
					} else {
						float invDet = 1.0f / determinant;
						// shading frame vectors
						tangent = (( dUV2.y * dP1 - dUV1.y * dP2) * invDet);
						bitangent = ((-dUV2.x * dP1 + dUV1.x * dP2) * invDet);
					}
					
					//Normal mapping (uses normal map, not height map)
					Vector3 sampledNormal = materialCache.SampleNormalMap(*material, u, v);
					normals[i] = GetNormalMapNormal(sampledNormal, normal.Normalize(), tangent.Normalize(), bitangent.Normalize());
				}
			}
		}
		else
		{
			Vector4 bgColor;
			materialCache.GetEnvironmentColor(rays[i], intersectInfos[i], rayOffset, bgColor);
			bounceColors[i] += bgColor;
		}
	}

	const Vector3 &eyeOrigin = cameras[cameraIndex]->origin;
	unsigned int lightIndex = -1;
	unsigned int lightIndexEnd = __min(lightInfo.size(), (unsigned int)maxLights);
	LightInfoEntry *lightEntry = nullptr;
	if (headlightInfo.size() > cameraIndex)
	{
		lightEntry = &headlightInfo[cameraIndex];
		lightIndex = -1;
	}
	else if (lightIndexEnd > 0)
	{
		lightEntry = &lightInfo[0];
		lightIndex = 0;
	}
	while (lightEntry)
	{
		unsigned int shadowIndex = 0;
		shadowRayStream.Clear();
		for (unsigned int i = 0; i < rayStream.GetNumberOfRays(); ++i)
		{
			if ((intersectInfos[i].nodeID != NODE_ID_NONE) && (intersectInfos[i].nodeID < nodeInfos.size()))
			{
				Vector3 lightDir = (lightEntry->light.type == Light::DIRECTIONAL) ?
					lightEntry->direction : (intersectInfos[i].intersection - lightEntry->position).Normalize();
				Vector3 lightPosition = (lightEntry->light.type == Light::DIRECTIONAL) ?
					intersectInfos[i].intersection - 2.0f * radius * lightEntry->direction : lightEntry->position;
				Ray shadowRay(lightPosition, lightDir);
				// compute light color.
				lightColors[i] = lightEntry->light.diffuse;
				if (lightEntry->iesLight)
				{
					Vector3 shadowRayDirection = lightEntry->inverseWorldMat.TransformNormal(shadowRay.direction).Normalize();
					float ver = 0.0f;
					float hor = 0.0f;
					Vector3 verV = Vector3(0.0f, shadowRayDirection.y, shadowRayDirection.z).Normalize();
					if (verV.Length() != 0.0f)
					{
						float arg= -verV.Normalize().z;
						if (arg > 1.0f)
							arg = 1.0f;
						if (arg < -1.0f)
							arg = -1.0f;
						ver = acosf(arg) / M_PI * 180.0f;
						if (shadowRayDirection.y < 0.0f)
							ver = -ver;
					}
					Vector3 horV = Vector3(shadowRayDirection.x, 0.0f, shadowRayDirection.z).Normalize();
					if (horV.Length() != 0.0f)
					{
						float arg= -horV.Normalize().z;
						if (arg > 1)
							arg = 1;
						if (arg < -1)
							arg = -1;
						hor = acosf(arg) / M_PI * 180;
						if (shadowRayDirection.x < 0)
							hor = -hor;
					}
					lightColors[i] *= lightEntry->iesLight->GetIntensity(hor, ver);
					lightColors[i].w = 1.0f;
				}
				bool inShadow = (diffuseColors[i].w == 1.0f) ? (normals[i].Dot(lightDir) > -1e-4) : false;
				// cast shadow ray, if shadow is on and is not primary ray
				if (castShadowRays && !inShadow && (lightIndex!=-1))
				{
					shadowRay.t = Distance(shadowRay.origin, intersectInfos[i].intersection) - rayOffset;
					shadowRayStream.PushRay(shadowRay, i, 0.0f);
					shadowColors[shadowIndex] = lightColors[i];
					++shadowIndex;
				}
			}
		}

		// cast shadow rays
		if (castShadowRays && shadowRayStream.GetNumberOfRays() > 0)
		{
			rayCaster.SetBottomCulling(false);
			rayCaster.SetTopCulling(true);
			rayCaster.CastRayBundleShadow(shadowRayStream, bvh, materialCache, shadowColors, rayOffset, workers[workerIndex]->renderBuffers.rayCasterBuffers);
		}

		shadowIndex = 0;
		for (unsigned int i = 0; i < rayStream.GetNumberOfRays(); ++i)
		{
			if ((intersectInfos[i].nodeID != NODE_ID_NONE) && (intersectInfos[i].nodeID < nodeInfos.size()))
			{
				Vector3 lightDir = (lightEntry->light.type == Light::DIRECTIONAL) ?
					lightEntry->direction : (intersectInfos[i].intersection - lightEntry->position).Normalize();
				Vector3 lightPosition = (lightEntry->light.type == Light::DIRECTIONAL) ?
					intersectInfos[i].intersection - 2.0f * radius * lightEntry->direction : lightEntry->position;
				bool inShadow = (diffuseColors[i].w == 1.0f) ? (normals[i].Dot(lightDir) > -1e-4) : false;
				if (!inShadow)
				{
					Ray shadowRay(lightPosition, lightDir);
					// add lighting caused by this light
					float diffuseAmount = shadowRay.direction.Dot(normals[i]);
					//compute phong
					Vector3 eyeToPoint = (intersectInfos[i].intersection - eyeOrigin).Normalize();
					Vector3 phongVector = (( 2.0f * diffuseAmount * normals[i]) - shadowRay.direction);
					float dotEyeNormal = std::max<float>(eyeToPoint.Dot(phongVector.Normalize()), 0.0f);
					const RTMaterial *material = nodeInfos[intersectInfos[i].nodeID].material;
					float specularPow = fastPow(dotEyeNormal, material->shininess);
					//float specularPow = powf(dotEyeNormal, material->shininess);
					if ((lightEntry->light.type == Light::SPOTLIGHT) && (lightEntry->iesLight))
						specularPow = fastPow(specularPow, lightEntry->light.exponent);
						//specularPow = powf(specularPow, lightEntry->light.exponent);
					if ((lightEntry->light.type == Light::SPOTLIGHT) && (!lightEntry->iesLight))
					{
						float spota = shadowRay.direction.Dot(lightEntry->direction);
						if (acosf(spota) > lightEntry->light.angle / 180 * M_PI)
						{
							spota = 0;
							specularPow = 0;
						}
						diffuseAmount *= powf(spota, lightEntry->light.exponent);
						specularPow = powf(specularPow, lightEntry->light.exponent);
					}
					if (lightEntry->light.type != Light::DIRECTIONAL)
					{
						float dist = (intersectInfos[i].intersection - shadowRay.origin).Length();
						diffuseAmount /= (lightEntry->light.attenuation.x + lightEntry->light.attenuation.y *
							dist + lightEntry->light.attenuation.z * dist * dist); //decrease intensity by distance
					}
					if (castShadowRays && (lightIndex != -1))
					{
                        lightColors[i] = (1-shadowIntensity)*lightColors[i]+shadowIntensity*shadowColors[shadowIndex++];
					}

					lightColors[i] *= lightColors[i].w;
					lightColors[i].w = 1.0f;
					bounceColors[i] += (material->specular * specularPow + fabs(diffuseAmount) * diffuseColors[i]) * lightColors[i];
					bounceColors[i].w = diffuseColors[i].w;
				}
				else
				{
					bounceColors[i].w = diffuseColors[i].w;
				}
			}
		}

		++lightIndex;
		if (lightIndex < lightIndexEnd)
		{
			lightEntry = &lightInfo[lightIndex];
		}
		else
		{
			lightEntry = nullptr;
		}
	}

	for (unsigned int i = 0; i < rayStream.GetNumberOfRays(); ++i)
	{
		if ((intersectInfos[i].nodeID != NODE_ID_NONE) && (intersectInfos[i].nodeID < nodeInfos.size()))
		{
			const RTMaterial *material = nodeInfos[intersectInfos[i].nodeID].material;
			if (usedTexs[i])
			{
				bounceColors[i].x += material->emission.x * material->emission.w * texColors[i].x;
				bounceColors[i].y += material->emission.y * material->emission.w * texColors[i].y;
				bounceColors[i].z += material->emission.z * material->emission.w * texColors[i].z;
			}
			else
			{
				bounceColors[i].x += material->emission.x * material->emission.w;
				bounceColors[i].y += material->emission.y * material->emission.w;
				bounceColors[i].z += material->emission.z * material->emission.w;
			}
			bounceColors[i].w = diffuseColors[i].w;
		}
	}
}

void WhittedRenderer::ReflectedAndRefractedRays(const RayStream & inputRayStream, RayStream & outputRayStream, Vector4 colors[], Vector4 bounceColors[], const int depth)
{
	auto & nodeInfos = bvh.GetNodeInfos();

	const Ray * rays = inputRayStream.GetRays();
	const RayIntersectionInfo * intersectInfos = inputRayStream.GetRayIntersectionInfos();
	const Vector4 * weights = inputRayStream.GetWeights();
	const int * pixelIndices = inputRayStream.GetPixelIndices();

	unsigned int inputRayStreamRaysCount = inputRayStream.GetNumberOfRays();

	for (unsigned int i = 0; i < inputRayStreamRaysCount; ++i)
	{
		if (intersectInfos[i].nodeID != NODE_ID_NONE)
		{
			Vector3 normal = intersectInfos[i].normal;
			float cosPhi1 = normal.Dot(rays[i].direction);
			if (cosPhi1 > 0.0f)
				normal = -normal;

			const RTMaterial * material = nodeInfos[intersectInfos[i].nodeID].material;
			Vector4 weightReflected, weightRefracted;

			processReflectRefractRayGetWeights(rays[i].direction, intersectInfos[i].intersection, normal, material, depth, weights[i], pixelIndices[i], cosPhi1, outputRayStream, bounceColors[i], weightReflected, weightRefracted);
		}

		colors[pixelIndices[i]] += weights[i] * bounceColors[i];
	}
}
//*/
void WhittedRenderer::CastAndShade(RayStream & inputRayStream, Vector4 colors[], unsigned int cameraIndex, const int workerIndex)
{
	Vector4 *bounceColors = workers[workerIndex]->renderBuffers.casBuffers.bounceColors;
	Vector4 *diffuseColors = workers[workerIndex]->renderBuffers.casBuffers.diffuseColors;
	Vector4 *texColors = workers[workerIndex]->renderBuffers.casBuffers.texColors;
	bool *usedTexs = workers[workerIndex]->renderBuffers.casBuffers.usedTexs;

	RayStream * irays = &inputRayStream;
	RayStream * orays = workers[workerIndex]->outputRayStream;
	orays->Clear();

	int depth = 0;
	do {
		do {
			// cast rays
			rayCaster.SetTopCulling(true);
			rayCaster.SetBottomCulling(true);
			rayCaster.CastRayBundle(*irays, bvh, workers[workerIndex]->renderBuffers.rayCasterBuffers);
			// shade
			Shade(*irays, bounceColors, diffuseColors, texColors, usedTexs, cameraIndex, workerIndex);
			// reflected and refracted rays
			ReflectedAndRefractedRays(*irays, *orays, colors, bounceColors, depth);
		} while (irays->RefillRays());
		// swap
		std::swap(irays, orays);
		// reset output rays
		orays->Clear();
	} while (depth++ < maxRecursionDepth && irays->GetNumberOfRays() > 0);
}
