/*
    QGL Renderer
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#define GL_GLEXT_PROTOTYPES
#include "renderer.h"

#include "ccm_qglengine_core.h"
#include "glmesh.h"
#include "glshaders.h"
#include "gltexture_manager.h"
#include "qglview.h"

#include <QOpenGLExtraFunctions>
#include <picodeinfo.h>
#include <piiostream.h>
#include <piqt.h>
#include <qad_types.h>


using namespace QGLEngineShaders;


Renderer::Renderer(QGLView * view_)
	: RendererBase(view_)
	, fbo_ds(view_,
			 QVector<GLenum>() << GL_RGBA16F << GL_RGBA32F << GL_RGBA16F << GL_RGBA16F << GL_RGBA16F << GL_RGBA16F << GL_RGBA32F
							   << GL_RGBA16F)
	, fbo_out(view_, obrBuffersCount, false, GL_RGBA16F)
	, rend_mat(this)
	, rend_service(this)
	, rend_selection(this)
	, tone_proc(this)
	, tex_env(view_, 512) {
	mat_norm_to_tex_coord.scale(0.5, 0.5);
	mat_norm_to_tex_coord.translate(1, 1);
	mat_proj_90 = glMatrixPerspective(90., 1., 0.1);
	for (int i = 0; i < 6; ++i) {
		QMatrix4x4 fvm;
		switch (i) {
		case 0:
			fvm.rotate(-90., QVector3D(0, 1, 0));
			fvm.rotate(180., QVector3D(0, 0, 1));
			break;
		case 1:
			fvm.rotate(90., QVector3D(0, 1, 0));
			fvm.rotate(180., QVector3D(0, 0, 1));
			break;
		case 2: fvm.rotate(-90., QVector3D(1, 0, 0)); break;
		case 3: fvm.rotate(90., QVector3D(1, 0, 0)); break;
		case 4:
			fvm.rotate(180., QVector3D(0, 0, 1));
			fvm.rotate(180., QVector3D(0, 1, 0));
			break;
		case 5: fvm.rotate(180., QVector3D(0, 0, 1)); break;
		}
		mat_faces[i] = fvm;
	}

	quad                 = Primitive::plane(2., 2.);
	cam_light            = new Light();
	cam_light->intensity = 0.75;
	cam_light->setName("Camera_Light");
	cam_light->setCastShadows(false);

	shader_files[srSelectionFill]           = "selection.glsl";
	shader_files[srSelectionHalo]           = "selection_halo.glsl";
	shader_files[srSelectionApply]          = "selection_apply.glsl";
	shader_files[srSelectionFrame]          = "selection_frame.glsl";

	shader_files[srServiceFill]             = "service_fill.glsl";
	shader_files[srServiceFrame]            = "service_frame.glsl";
	shader_files[srServiceLine]             = "service_line.glsl";

	shader_files[srGeometrySolidPass]       = "ds_geom.glsl";
	shader_files[srGeometryTransparentPass] = "ds_geom.glsl";
	shader_files[srLightOmniPass]           = "ds_light.glsl";
	shader_files[srLightSpotPass]           = "ds_light.glsl";
	shader_files[srFinalPass]               = "ds_final.glsl";
	shader_files[srTonemapPass]             = "ds_tonemap.glsl";

	shader_files[srShadowConePass]          = "shadow.glsl";
	shader_files[srShadowOmniPass]          = "shadow.glsl";

	shader_defines[srGeometrySolidPass] << "SOLID";
	shader_defines[srLightSpotPass] << "SPOT";
	shader_defines[srShadowOmniPass] << "OMNI";
	shader_defines[srLightSpotPass] << "SHADOWS";
	shader_defines[srLightOmniPass] << "SHADOWS";

	PICodeInfo::EnumInfo * obre = PICodeInfo::enumsInfo->value("Renderer::DeferredBufferRole");
	if (obre) {
		for (auto e: obre->members) {
			obr_defines += "#define " + PI2QString(e.name) + " " + QString::number(e.value) + "\n";
		}
	}

	edit_mode = need_init_shaders = true;
	camera_light_mode             = QGLView::clmAuto;
}


Renderer::~Renderer() {
	delete quad;
	delete cam_light;
	qDeleteAll(shaders.values());
	if (shader_fxaa) delete shader_fxaa;
}


void Renderer::init(int width, int height) {
	fbo_ds.reinit();
	fbo_out.reinit();
	quad->reinit();
	buffer_materials.reinit();
	buffer_lights.reinit();
	buffer_lights_pos.reinit();
	textures_maps.reinit();
	textures_empty.reinit();
	shadow_maps_cone.reinit();
	shadow_maps_omni.reinit();
	depth_maps_cone.reinit();
	depth_maps_omni.reinit();
	resize(width, height);
	rend_mat.init(width, height);
	rend_service.init(width, height);
	rend_selection.init(width, height);
	tone_proc.init();
	initQuad(quad);
	initTextureArrays();
	initCoeffTextures();
	markReloadTextures();
	tex_env.init();
	if (is_grabbing) fbo_out.enablePixelBuffer();
	need_init_shaders = true;
}


void Renderer::resize(int width, int height) {
	rend_mat.resize(width, height);
	rend_service.resize(width, height);
	rend_selection.resize(width, height);
	fbo_ds.resize(width, height);
	fbo_out.resize(width, height);
	tone_proc.resize();
}


void Renderer::reloadShaders() {
	__reinit_debug = true;
	QMapIterator<ShaderRole, QString> it(shader_files);
	qDeleteAll(shaders.values());
	shaders.clear();
	if (shader_fxaa) delete shader_fxaa;
	shader_fxaa = nullptr;
	if (tone_proc.shader_sum) delete tone_proc.shader_sum;
	tone_proc.shader_sum = nullptr;
	QString dir          = ":/shaders/";
	while (it.hasNext()) {
		it.next();
		loadShadersMulti(shaders[it.key()], dir + it.value(), true, shader_defines.value(it.key()), obr_defines);
	}
	loadShadersMulti(tone_proc.shader_sum, dir + "sum.glsl", false);
	QStringList fxaa_defs;
	fxaa_defs << "FXAA_PC 1"
			  << "FXAA_GLSL_130 1"
			  << "FXAA_QUALITY__PRESET 15";
	loadShaders(shader_fxaa, QStringList() << (dir + "fxaa.vert") << (dir + "fxaa.frag"), true, fxaa_defs);
	for (auto * e: fb_effects) {
		e->reloadShaders();
		e->is_loaded = true;
	}
	need_init_shaders = true;
	view->scene()->setLightsChanged();
	view->scene()->setTreeStructChanged();
	view->scene()->setMaterialsChanged();
}


bool Renderer::bindShader(Renderer::ShaderRole role, QOpenGLShaderProgram ** ret) {
	QOpenGLShaderProgram * prog = shaders.value(role);
	if (ret) *ret = prog;
	if (!prog) return false;
	if (!prog->isLinked()) return false;
	prog->bind();
	return true;
}


bool Renderer::bindShader(QOpenGLShaderProgram * sp) {
	if (!sp) return true;
	if (!sp->isLinked()) return true;
	if (!sp->bind()) return false;
	return true;
}


void Renderer::initShaders() {
	if (!need_init_shaders) return;
	need_init_shaders           = false;
	QOpenGLShaderProgram * prog = 0;
	for (ShaderRole role: {srLightOmniPass, srLightSpotPass}) {
		if (!bindShader(role, &prog)) continue;
		initUniformBuffer(prog, &buffer_materials, bpMaterials, "QGLMaterialData");
		initUniformBuffer(prog, &buffer_lights, bpLightParameters, "QGLLightParameterData");
		initUniformBuffer(prog, &buffer_lights_pos, bpLightPositions, "QGLLightPositionData");
		for (int i = 0; i <= 5; ++i)
			prog->setUniformValue(QString("tex_%1").arg(i).toLatin1().constData(), i);
		prog->setUniformValue("tex_coeff_brdf", trCoeffBRDF);
		prog->setUniformValue("tex_env", trEnvironment);
		setUniformMaps(prog);
	}
	if (bindShader(srFinalPass, &prog)) {
		prog->setUniformValue("tex_g1", 0);
		prog->setUniformValue("tex_s_0", 1);
		prog->setUniformValue("tex_s_1", 2);
		prog->setUniformValue("tex_t_0", 3);
		prog->setUniformValue("tex_t_1", 4);
	}
	for (ShaderRole role: {srGeometrySolidPass, srGeometryTransparentPass, srShadowConePass, srShadowOmniPass}) {
		if (!bindShader(role, &prog)) continue;
		initUniformBuffer(prog, &buffer_materials, bpMaterials, "QGLMaterialData");
		setUniformMaps(prog);
	}
	if (bindShader(srTonemapPass, &prog)) {
		prog->setUniformValue("tex_0", 0);
		prog->setUniformValue("tex_sum", 1);
	}
}


void Renderer::releaseShader() {
	view->glUseProgram(0);
}


QVector<int> Renderer::getFreePlanes(int count) {
	prev_write_plane = cur_write_plane;
	QVector<int> ret;
	bool output_done      = false;
	const int total_count = 4;
	for (int i = 0; i < total_count; ++i) {
		int plane = obrGeneral0 + i;
		if (prev_write_plane == plane) continue;
		if (!output_done) {
			fbo_out.setWriteBuffer(plane);
			cur_write_plane = plane;
			output_done     = true;
			continue;
		}
		ret << plane;
		if (ret.size() == count) break;
	}
	return ret;
}


void Renderer::fillObjectsBuffer(const ObjectBaseList & ol, RenderPass pass) {
	cur_objects_.resize(ol.size());
	for (int i = 0; i < ol.size(); ++i) {
		Object & so(cur_objects_[i]);
		ObjectBase * o = ol[i];
		if (o->material()) {
			so.material = o->material()->_index;
			so.color    = QVector4D(1, 1, 1, 1);
		} else {
			so.material = 0;
			so.color    = QColor2QVector(o->color());
		}
		so.object_id            = o->id();
		so.flags                = 0;
		so.f_accept_light       = o->isAcceptLight();
		so.f_accept_fog         = o->isAcceptFog();
		so.f_accept_cast_shadow = o->isCastShadows();
		so.f_accept_rec_shadow  = o->isReceiveShadows();
		o->worldTransform().transposed().copyDataTo(so.modelmatrix);
		o->textureGLMatrix().copyDataTo(so.texturematrix);
		// qDebug() << "load obj" << o->name() << o->textureMatrix() << tmat;
	}
	// qDebug() << "fillObjectsBuffer" << ol.size();
}


void Renderer::renderObjects(Scene & scene, RenderPass pass) {
	QOpenGLExtraFunctions * f = view;
	QMapIterator<Mesh *, ObjectBaseList> it(scene.geometries_used[pass]);
	bool emit_pos_change = false;
	while (it.hasNext()) {
		it.next();
		Mesh * mesh = it.key();
		if (mesh->isObjectsChanged(pass)) {
			mesh->setObjectsChanged(pass, false);
			emit_pos_change = true;
			fillObjectsBuffer(it.value(), pass);
			// qDebug() << "loadObjects" << pass << cur_objects_.size();
			mesh->loadObjects(f, cur_objects_, pass);
		}
		if (mesh->isSelectionChanged(pass) && edit_mode) {
			mesh->setSelectionChanged(pass, false);
			fillSelectionsBuffer(rend_selection.cur_selections_, it.value());
			// qDebug() << "fillSelectionsBuffer" << pass << rend_selection.cur_selections_.size();
			mesh->loadSelections(f, rend_selection.cur_selections_, pass);
		}
		// qDebug() << "draw" << pass << it.value().size();
		mesh->draw(f, it.value().size(), pass);
	}
	if (emit_pos_change) emit view->objectsPositionChanged();
}


void Renderer::renderLight(int first_wr_buff, bool clear_only) {
	QOpenGLShaderProgram * prog = 0;
	Camera * cam                = view->camera();
	view->glActiveTexture(GL_TEXTURE0 + trCoeffBRDF);
	view->glBindTexture(GL_TEXTURE_2D, tex_coeff[0]);
	// tex_env.bind((int)Renderer::dbrBuffersCount + 1);
	typedef QPair<Renderer::ShaderRole, Light::Type> PassPair;
	QVector<PassPair> passes;
	passes << PassPair(srLightOmniPass, Light::Omni) << PassPair(srLightSpotPass, Light::Cone);
	QColor back = view->fogColor();
	back.setAlpha(0);
	foreach(PassPair pass, passes) {
		if (bindShader(pass.first, &prog)) {
			/*if (pass.second == Light::Cone) {
				auto & cone_ll(cur_lights[pass.second]);
				for (int i = 0; i < cone_ll.size(); ++i) {
					Light * l = cone_ll[i];
					// l->shadow_map.bindDepthTexture(mtShadowCone + i);
					l->shadow_map.bindColorTexture(0, mtShadowCone + i);
				}
			}*/
			fbo_out.setWriteBuffer(first_wr_buff + pass.second);
			glClearFramebuffer(back, false);
			if (clear_only) continue;
			setUniformCamera(prog, cam);
			setUniformViewCorners(prog, cam);
			prog->setUniformValue("lights_start", lights_start[pass.second]);
			prog->setUniformValue("lights_count", (int)cur_lights[pass.second].size());
			prog->setUniformValue("fog_color", view->fogColor());
			prog->setUniformValue("fog_decay", qMax(view->fogDecay(), 0.001f));
			prog->setUniformValue("fog_density", view->fogDensity());
			prog->setUniformValue("view_mat", cam->viewMatrix().inverted().toGenericMatrix<3, 3>());
			if (shadows_enabled) {
				prog->setUniformValue("shadow_size", view->shadow_map_size);
				prog->setUniformValue("noise_size", noise_size);
				prog->setUniformValue("soft_shadows_enabled", view->soft_shadows);
				prog->setUniformValue("soft_shadows_samples", view->soft_shadows_samples);
				prog->setUniformValue("soft_shadows_quality", view->soft_shadows_quality);
				prog->setUniformValue("tex_shadows_cone", (int)tarShadowsCone);
				prog->setUniformValue("tex_shadows_omni", (int)tarShadowsOmni);
				prog->setUniformValue("tex_depths_cone", (int)tarDepthsCone);
				prog->setUniformValue("tex_depths_omni", (int)tarDepthsOmni);
				GLenum filter = view->softShadows() ? GL_NEAREST : GL_LINEAR;
				shadow_maps_cone.bind(view, tarShadowsCone);
				view->glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, filter);
				view->glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, filter);
				shadow_maps_omni.bind(view, tarShadowsOmni);
				view->glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, filter);
				view->glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, filter);
				depth_maps_cone.bind(view, tarDepthsCone);
				depth_maps_omni.bind(view, tarDepthsOmni);
			}
			renderQuad(prog, quad, cam);
		}
	}
}


void Renderer::renderConeShadows() {
	QOpenGLExtraFunctions * f   = view;
	QOpenGLShaderProgram * prog = 0;
	if (bindShader(srShadowConePass, &prog)) {
		glEnableDepth();
		textures_empty.bind(f, tarEmpty);
		textures_maps.bind(f, tarMaps);
		auto cone_ll = cur_lights.value(Light::Cone);
		if (shadow_maps_cone.resize(f, view->shadow_map_size, cone_ll.size())) {
			f->glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
			f->glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
			f->glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
			f->glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_FUNC, GL_LESS);
		}
		depth_maps_cone.resize(f, view->shadow_map_size, cone_ll.size());
		for (int i = 0; i < cone_ll.size(); ++i) {
			Light * l     = cone_ll[i];
			QMatrix4x4 pm = glMatrixPerspective(l->angle_end, 1., 0.1), om, vm;
			om.translate(-l->worldAim());
			vm.translate(0., 0., -l->distance());
			// vm.rotate(-roll_, 0., 0., 1.);
			QMatrix4x4 pmat = l->worldTransform();
			pmat(0, 3) = pmat(1, 3) = pmat(2, 3) = 0.;
			vm *= pmat.inverted();
			auto vpm = pm * (vm * om);
			if (l->isCastShadows()) {
				prog->setUniformValue("qgl_ViewProjMatrix", vpm);
				renderConeShadow(i, l);
			}
			l->shadow_matrix = mat_norm_to_tex_coord * vpm * mat_camera_fullview_inv;
		}
	}
}


void Renderer::renderOmniShadows() {
	QOpenGLExtraFunctions * f   = view;
	QOpenGLShaderProgram * prog = 0;
	if (bindShader(srShadowOmniPass, &prog)) {
		glEnableDepth();
		textures_empty.bind(f, tarEmpty);
		textures_maps.bind(f, tarMaps);
		auto omni_ll = cur_lights.value(Light::Omni);
		if (shadow_maps_omni.resize(f, view->shadow_map_size, omni_ll.size())) {
			f->glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
			f->glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_COMPARE_FUNC, GL_LESS);
		}
		depth_maps_omni.resize(f, view->shadow_map_size, omni_ll.size());
		for (int i = 0; i < omni_ll.size(); ++i) {
			Light * l = omni_ll[i];
			QMatrix4x4 om;
			om.translate(-l->worldPos());
			// vm.translate(0., 0., -l->distance());
			// vm.rotate(-roll_, 0., 0., 1.);
			if (l->isCastShadows()) {
				renderOmniShadow(i, l, prog, om);
			}
			// l->shadow_matrix = mat_norm_to_tex_coord * vpm * mat_camera_fullview_inv;
		}
	}
}


void Renderer::renderConeShadow(int index, Light * light) {
	Scene & scene(*(view->scene()));
	bool force_resize = false;
	if (!light->shadow_map.isInit()) {
		light->shadow_map.reinit(view);
		force_resize = true;
	}
	light->shadow_map.resize(view->shadow_map_size, force_resize);
	light->shadow_map.bind();

	view->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadow_maps_cone.ID(), 0, index);
	view->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, depth_maps_cone.ID(), 0, index);
	glDrawBuffer(GL_COLOR_ATTACHMENT0);
	glClearFramebuffer();
	renderObjects(scene, rpSolid);
	light->shadow_map.release();
}


void Renderer::renderOmniShadow(int index, Light * light, QOpenGLShaderProgram * prog, QMatrix4x4 om) {
	Scene & scene(*(view->scene()));
	bool force_resize = false;
	if (!light->shadow_map.isInit()) {
		light->shadow_map.reinit(view);
		force_resize = true;
	}
	light->shadow_map.resize(view->shadow_map_size, force_resize);
	light->shadow_map.bind();

	for (int i = 0; i < 6; ++i) {
		QMatrix4x4 vm = mat_proj_90 * mat_faces[i] * om;
		prog->setUniformValue("qgl_ViewProjMatrix", vm);
		view->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadow_maps_omni.ID(), 0, index * 6 + i);
		view->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, depth_maps_omni.ID(), 0, index * 6 + i);
		// static GLenum faces[6] = {GL_COLOR_ATTACHMENT0,
		//						  GL_COLOR_ATTACHMENT1,
		//						  GL_COLOR_ATTACHMENT2,
		//						  GL_COLOR_ATTACHMENT3,
		//						  GL_COLOR_ATTACHMENT4,
		//						  GL_COLOR_ATTACHMENT5};
		// view->glDrawBuffers(6, faces);
		glDrawBuffer(GL_COLOR_ATTACHMENT0);
		glClearFramebuffer();
		renderObjects(scene, rpSolid);
	}
	light->shadow_map.release();
}


void Renderer::renderScene() {
	timings.clear();
	Measurer phase(&timings);

	phase.begin("init");
	initShaders();
	tex_env.load();
	QOpenGLExtraFunctions * f = view;
	Scene & scene(*(view->scene()));
	Camera * cam                = view->camera();
	QOpenGLShaderProgram * prog = 0;
	bool scene_changed          = scene.prepare();
	cur_lights                  = scene.lights_used;
	mat_camera_fullview_inv     = cam->fullViewMatrix().inverted();
	scene.destroyUnused(f);
	phase.end();

	/// reload materials on change
	phase.begin("scene reload");
	if (scene_changed || scene.need_reload_materials) {
		rend_selection.generateObjectsID(scene);
		reloadMaterials(scene);
		if (edit_mode) recreateMaterialThumbnails();
		emit view->materialsChanged();
	}
	phase.end();

	/// material thumbnails
	phase.begin("materials");
	if (edit_mode && !scene_changed) {
		rend_mat.procQueue();
	}
	phase.end();

	if (shadows_enabled) {
		/// cone shadows and shadow matrix
		phase.begin("shadows cone");
		renderConeShadows();
		phase.end();

		/// omni shadows and shadow matrix
		phase.begin("shadows omni");
		renderOmniShadows();
		phase.end();
	}

	/// lights
	phase.begin("lights prepare");
	bool use_camlight = (camera_light_mode == QGLView::clmOn);
	if ((camera_light_mode == QGLView::clmAuto) && cur_lights.isEmpty()) {
		use_camlight = true;
	}
	if (use_camlight) {
		cur_lights[Light::Omni] << cam_light;
		cam_light->setPos(cam->pos());
	}
	if (scene.lights_changed) {
		scene.lights_changed = false;
		reloadLightsParameters(cur_lights);
	}
	reloadLightsPositions(cam);
	phase.end();

	/// selection
	phase.begin("selection");
	if (edit_mode) {
		rend_selection.renderSelection(scene);
	}
	phase.end();


	/// solid geometry pass
	phase.begin("geometry solid");
	fbo_ds.bind();
	glEnableDepth();
	glClearFramebuffer();
	if (bindShader(srGeometrySolidPass, &prog)) {
		setUniformCamera(prog, cam);
		textures_empty.bind(f, tarEmpty);
		textures_maps.bind(f, tarMaps);
		prog->setUniformValue("out_index_normal", dbrNormalZSolid);
		prog->setUniformValue("out_index_metal", dbrMetalRoughReflectFlagsSolid);
		glPolygonMode(GL_FRONT_AND_BACK, view->renderMode());
		renderObjects(scene, rpSolid);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
	// fbo_ds.blit(dbrNormalZ, fbo_ds.id(), dbrNormalZSolid, fbo_ds.rect(), fbo_ds.rect());
	// fbo_ds.blit(dbrMetalRoughReflectFlags, fbo_ds.id(), dbrMetalRoughReflectFlagsSolid, fbo_ds.rect(), fbo_ds.rect());
	fbo_ds.release();
	phase.end();

	/// lighting passes
	phase.begin("... light");
	fbo_out.bind();
	fbo_ds.bindColorTextures({dbrDiffuse, dbrNormalZSolid, dbrMetalRoughReflectFlagsSolid, dbrEmission, dbrSpeedBitangXY, dbrGeoNormal});
	renderLight(obrSolidOmni, scene.geometries_used[rpSolid].isEmpty());
	phase.end();

	/// transparent geometry pass
	phase.begin("geometry trans");
	fbo_ds.bind();
	glEnableDepth();
	fbo_ds.setWriteBuffers({0, 1, 2, 3, 4});
	glClearFramebuffer(Qt::black, false);
	fbo_ds.setWriteBuffers();
	if (bindShader(srGeometryTransparentPass, &prog)) {
		setUniformCamera(prog, cam);
		textures_empty.bind(f, tarEmpty);
		textures_maps.bind(f, tarMaps);
		prog->setUniformValue("out_index_normal", dbrNormalZ);
		prog->setUniformValue("out_index_metal", dbrMetalRoughReflectFlags);
		glPolygonMode(GL_FRONT_AND_BACK, view->renderMode());
		renderObjects(scene, rpTransparent);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
	fbo_ds.release();
	phase.end();

	/// lighting passes
	phase.begin("... light");
	fbo_out.bind();
	fbo_ds.bindColorTextures({dbrDiffuse, dbrNormalZ, dbrMetalRoughReflectFlags, dbrEmission, dbrSpeedBitangXY, dbrGeoNormal});
	renderLight(obrTransparentOmni, scene.geometries_used[rpTransparent].isEmpty());
	phase.end();

	/// blending layers
	phase.begin("blending");
	if (bindShader(srFinalPass, &prog)) {
		fbo_out.bindColorTexture(obrSolidOmni, 1);
		fbo_out.bindColorTexture(obrSolidSpot, 2);
		fbo_out.bindColorTexture(obrTransparentOmni, 3);
		fbo_out.bindColorTexture(obrTransparentSpot, 4);
		fbo_out.setWriteBuffer(obrLighting);
		renderQuad(prog, quad);
	}
	phase.end();

	fbo_out.bind();
	cur_write_plane = obrLighting;

	/// tonemapping
	phase.begin("tonemap");
	tone_proc.process();
	auto free = getFreePlanes(0);
	if (bindShader(srTonemapPass, &prog)) {
		prog->setUniformValue("gamma", gamma_);
		prog->setUniformValue("frame_max", tone_proc.frameMax());
		// qDebug() << tone_proc.frameMax();
		fbo_out.bindColorTexture(prev_write_plane, 0);
		renderQuad(prog, quad);
	} else {
		fbo_out.blit(prev_write_plane, fbo_out.id(), cur_write_plane, fbo_out.rect(), fbo_out.rect());
	}
	phase.end();

	/// FXAA
	phase.begin("fxaa");
	if (view->FXAA_) {
		prog = shader_fxaa;
		if (bindShader(prog)) {
			auto free = getFreePlanes(0);
			setUniformCamera(prog, 0, true, fbo_out.size());
			fbo_out.bindColorTexture(prev_write_plane);
			renderQuad(prog, quad, 0, false);
		}
	}
	phase.end();

	/// custom effects
	for (auto * e: fb_effects) {
		if (!e->isEnabled()) continue;
		phase.begin("fb effect " + e->name());
		e->reloadShadersInternal();
		e->drawInternal();
		phase.end();
	}

	fbo_out.release();

	/// apply hovers and selection frame
	phase.begin("service");
	if (edit_mode) {
		rend_selection.drawSelection(fbo_out, cur_write_plane);
		rend_service.renderService();
	} else {
		fbo_out.blit(cur_write_plane, 0, 0, fbo_out.rect(), QRect(QPoint(), view->pixelSize()));
	}
	phase.end();

	/// grab framebuffer
	phase.begin("grab");
	if (is_grabbing) {
		fbo_out.queryImage(0);
		last_img = fbo_out.getImage().mirrored();
		// qDebug() << last_img.size();
	}
	phase.end();

	/*
		static QOpenGLShaderProgram * tprog = nullptr;
		if (tprog && __reinit_debug) {
			delete tprog;
			tprog = nullptr;
		}
		if (!tprog) {
			loadShadersMulti(tprog, "./shaders/debug.glsl");
			tprog->bind();
			tprog->setUniformValue("tex_cone", (int)tarShadowsCone);
			tprog->setUniformValue("tex_omni", (int)tarShadowsOmni);
			tprog->setUniformValue("dep_cone", (int)tarDepthsCone);
			tprog->setUniformValue("dep_omni", (int)tarDepthsOmni);
			QMatrix4x4 mat;
			double sz = 0.33;
			mat.translate(sz - 1, sz - 1);
			mat.scale(sz, sz);
			tprog->setUniformValue("qgl_ViewProjMatrix", mat);
		}
		__reinit_debug = false;
		tprog->bind();
		shadow_maps_cone.bind(f, tarShadowsCone);
		shadow_maps_omni.bind(f, tarShadowsOmni);
		depth_maps_cone.bind(f, tarDepthsCone);
		depth_maps_omni.bind(f, tarDepthsOmni);
		quad->draw(f, 1);
		*/
}


void Renderer::setCameraLightMode(int m) {
	camera_light_mode = m;
	view->scene()->setLightsChanged();
}

void Renderer::setGrabImage(bool on) {
	is_grabbing = on;
	// fbo_out.enablePixelBuffer();
}


void Renderer::addFramebufferEffect(FramebufferEffectBase * e) {
	e->r = this;
	fb_effects << e;
}

void Renderer::setSadowsEnabled(bool on) {
	shader_defines[srLightSpotPass].removeAll("SHADOWS");
	shader_defines[srLightOmniPass].removeAll("SHADOWS");
	if (on) {
		shader_defines[srLightSpotPass] << "SHADOWS";
		shader_defines[srLightOmniPass] << "SHADOWS";
	}
	shadows_enabled = on;
	reloadShaders();
}
