/*
    QGLView
    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/>.
*/

#include "renderer_deferred_shading.h"

#include <QBoxLayout>


RendererDeferredShading::RendererDeferredShading(QGLView * view_)
	: GLRendererBase(view_)
	, fbo_g(5, true, GL_RGBA16F)
	, fbo_out(3, false, GL_RGBA16F)
	, fbo_hsmall(1, false, GL_RGB16F) {
	shaders << ShaderPair("FXAA", &shader_fxaa) << ShaderPair("dsl_pass_0", &shader_ds_0) << ShaderPair("dsl_pass_1", &shader_ds_1)
			<< ShaderPair("hdr", &shader_hdr) << ShaderPair("downscale", &shader_small) << ShaderPair("bloom_pass_0", &shader_bloom_0)
			<< ShaderPair("bloom_pass_1", &shader_bloom_1) << ShaderPair("fbo_add", &shader_fbo_add)
			<< ShaderPair("motion_blur", &shader_motion_blur) << ShaderPair("shadow", &shader_shadow) << ShaderPair("ssr", &shader_ssr)
			<< ShaderPair("ssr_blur", &shader_ssr_blur) << ShaderPair("ssr_merge", &shader_ssr_merge)
			<< ShaderPair("ssao_blur", &shader_ssao_blur) << ShaderPair("ssao_merge", &shader_ssao_merge) << ShaderPair("dof", &shader_dof);
	for (int i = 0; i < shaders.size(); ++i)
		*(shaders[i].second) = nullptr;
	lights_per_pass = 8;
	tnoise          = 0;
	exposure_       = 1.;
	df              = new QWidget();
	df->setLayout(new QBoxLayout(QBoxLayout::TopToBottom));
	label_exp      = new QLabel();
	label_exp_step = new QLabel();
	df->layout()->addWidget(label_exp);
	df->layout()->addWidget(label_exp_step);
	QPalette pal(df->palette());
	pal.setBrush(QPalette::Window, QColor(255, 255, 255, 192));
	df->setPalette(pal);
	if (view_)
		; // view_->addObject(df);
}


RendererDeferredShading::~RendererDeferredShading() {
	for (int i = 0; i < shaders.size(); ++i) {
		QOpenGLShaderProgram * p(*(shaders[i].second));
		if (p) delete p;
	}
	delete df;
}


void RendererDeferredShading::renderScene() {
	// qDebug() << lights_per_pass;
	QMatrix4x4 mproj      = rp.proj_matrix;
	QMatrix4x4 mproji     = rp.proj_matrix_i;
	QMatrix4x4 mview      = rp.view_matrix;
	QMatrix4x4 mviewi     = rp.view_matrix_i;
	QMatrix4x4 mviewproji = (mproj * mview).inverted();
	QMatrix4x4 moffset    = view.camera()->offsetMatrix();
	QMatrix4x4 moffseti   = moffset.inverted();
	rp.prev_proj_matrix   = prev_proj;
	rp.prev_view_matrix   = prev_view;
	QMatrix4x4 vc_proji;
	vc_proji.perspective(90., 1., view.camera()->depthStart(), view.camera()->depthEnd());
	vc_proji       = vc_proji.inverted();
	corner_dirs[0] = (mproji * QVector4D(-1, -1, 0, 1));
	corner_dirs[1] = (mproji * QVector4D(1, -1, 0, 1));
	corner_dirs[2] = (mproji * QVector4D(-1, 1, 0, 1));
	corner_dirs[3] = (mproji * QVector4D(1, 1, 0, 1));
	// qDebug() << corner_dirs[0] << corner_dirs[1] << corner_dirs[2] << corner_dirs[3];
	fbo_g.bind();
	int buffs[] = {0, 1, 2, 3, 4};
	fbo_g.setWriteBuffers(buffs, 5);
	if (white_image_id == 0) {
		glActiveTexture(GL_TEXTURE0 + 6);
		white_image_id = view.textureManager()->loadTexture(white_image, false);
		glBindTexture(GL_TEXTURE_2D, white_image_id);
		glActiveTexture(GL_TEXTURE0);
	}
	if (violent_image_id == 0) {
		glActiveTexture(GL_TEXTURE0 + 7);
		violent_image_id = view.textureManager()->loadTexture(violent_image, false);
		glBindTexture(GL_TEXTURE_2D, violent_image_id);
		glActiveTexture(GL_TEXTURE0);
	}
	glEnableDepth();
	glClearFramebuffer(QColor(0, 0, 0, 0));
	glDisable(GL_RESCALE_NORMAL);
	shader_ds_0->bind();
	rp.setUniform(shader_ds_0);
	shader_ds_0->setUniformValue("z_far", view.depthEnd());
	shader_ds_0->setUniformValue("z_near", view.depthStart());
	shader_ds_0->setUniformValue("t0", 0);
	shader_ds_0->setUniformValue("t1", 1);
	shader_ds_0->setUniformValue("t2", 2);
	shader_ds_0->setUniformValue("t3", 3);
	shader_ds_0->setUniformValue("t4", 4);
	shader_ds_0->setUniformValue("dt", QVector2D(1.f / view.width(), 1.f / view.height()));
	// qDebug() << rp.view_matrix << prev_view;
	// shader_ds_0->setUniformValue("qgl_ModelViewMatrix", rp.view_matrix);
	renderObjects(GLObjectBase::Solid, 0, shader_ds_0, true, false, false);
	// glReleaseShaders();
	fbo_g.release();

	if (view.isFeatureEnabled(QGLView::qglShadowsEnabled)) {
		shader_shadow->bind();
		int sms = view.feature(QGLView::qglShadowsMapSize).toInt();
		glDisable(GL_TEXTURE_1D);
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_TEXTURE_CUBE_MAP);
		glDisable(GL_MULTISAMPLE);
		glDisable(GL_LIGHTING);
		glDisable(GL_BLEND);
		glDisable(GL_ALPHA_TEST);
		glDisable(GL_RESCALE_NORMAL);
		glDisableClientState(GL_NORMAL_ARRAY);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glDisableClientState(GL_COLOR_ARRAY);
		// qDebug() << "render shadows";
		for (int i = 0; i < view.lightsCount(); ++i) {
			Light * l = view.light(i);
			if (l->light_type == Light::Omni) continue;
			l->shadow_map.resize(sms, sms);
			l->shadow_map.bind();
			l->shadow_map.setWriteBuffer(0);
			glClearFramebuffer();
			// glClear(GL_DEPTH_BUFFER_BIT);
			renderShadow(l, shader_shadow, moffseti * mviewi);
			l->shadow_map.release();
		}
	}
	//	glUseProgram(0);
	////	fbo_g.bindColorTextures();
	//	glBindTexture(GL_TEXTURE_2D, fbo_g.colorTexture(0));
	//	glActiveTexture(GL_TEXTURE0);
	//	glDrawQuad();
	//	return;

	glResetAllTransforms();
	glSetLightEnabled(false);
	glEnable(GL_TEXTURE_2D);
	glDisable(GL_BLEND);
	// glBlendFunc(GL_ONE, GL_ONE);
	glDisableDepth();
	rp.prepare();
	// qDebug() << rp.view_matrix;
	shader_ds_1->bind();
	shader_ds_1->setUniformValue("z_far", view.depthEnd());
	shader_ds_1->setUniformValue("z_near", view.depthStart());
	shader_ds_1->setUniformValue("t0", 0);
	shader_ds_1->setUniformValue("t1", 1);
	shader_ds_1->setUniformValue("t2", 2);
	shader_ds_1->setUniformValue("t3", 3);
	shader_ds_1->setUniformValue("t4", 4);
	shader_ds_1->setUniformValue("td", 5);
	shader_ds_1->setUniformValue("back_color", view.backColor());
	shader_ds_1->setUniformValue("mat_proji", mproji);
	shader_ds_1->setUniformValue("mat_view", mview);
	shader_ds_1->setUniformValue("mat_viewi", mviewi);
	shader_ds_1->setUniformValue("mat_viewproji", mviewproji);
	shader_ds_1->setUniformValue("shadow_on", view.isFeatureEnabled(QGLView::qglShadowsEnabled) ? 1 : 0);
	shader_ds_1->setUniformValue("dt", QVector2D(1.f / view.width(), 1.f / view.height()));
	rp.setUniform(shader_ds_1);
	fbo_g.bindColorTextures();
	fbo_g.bindDepthTexture(5);
	fbo_out.bind();
	fbo_out.setWriteBuffer(0);
	glClearFramebuffer(Qt::black, false);
	// QVector<QVector4D> lpos;
	// qDebug() << view_matrix;
	shader_ds_1->setUniformValue("t_pp", 6);
	int passes = (view.lightsCount() - 1) / lights_per_pass + 1;
	if (passes < 1) passes = 1;
	// qDebug() << "render in" << passes << "passes (" << lights_per_pass << ")";
	int wi, ri;
	for (int l = 0; l < passes; ++l) {
		wi = 1 - l % 2;
		ri = l % 2;
		// qDebug() << "  pass" << l << "read from" << ri << "write to" << wi;
		glActiveTexture(GL_TEXTURE0 + 6);
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(ri));
		fbo_out.setWriteBuffer(wi);
		setupDSLights(l, mview * moffset);
		glDrawQuad(shader_ds_1, corner_dirs);
		// break;
	}
	// fbo_out.release();
	wi = 1 - passes % 2;
	ri = passes % 2;
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(ri));

	if (view.isFeatureEnabled(QGLView::qglSSAOEnabled)) {
		fbo_out.setWriteBuffer(2);
		fbo_out.setReadBuffer(ri);
		glBlitFramebuffer(0,
		                  0,
		                  fbo_out.width(),
		                  fbo_out.height(),
		                  0,
		                  0,
		                  fbo_out.width(),
		                  fbo_out.height(),
		                  GL_COLOR_BUFFER_BIT,
		                  GL_NEAREST);
		glActiveTexture(GL_TEXTURE0 + 1);
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(2));
		glActiveTexture(GL_TEXTURE0 + 2);
		glBindTexture(GL_TEXTURE_2D, fbo_g.colorTexture(1));
		glActiveTexture(GL_TEXTURE0);
		// glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(ri));
		// glActiveTextureChannel(1);
		// glBindTexture(GL_TEXTURE_2D, fbo_g.colorTexture(0));
		int lri = ri, lwi = wi; //, lms = ri;
		shader_ssao_blur->bind();
		shader_ssao_blur->setUniformValue("qgl_ModelViewProjectionMatrix", QMatrix4x4());
		shader_ssao_blur->setUniformValue("dt", QVector2D(1.f / fbo_out.width(), 1.f / fbo_out.height()));
		shader_ssao_blur->setUniformValue("t0", 0);
		shader_ssao_blur->setUniformValue("ts", 1);
		shader_ssao_blur->setUniformValue("tg1", 2);
		int passes = view.feature(QGLView::qglSSAORadius).toInt();
		int crad   = 1;
		for (int p = 0; p < passes; ++p) {
			glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(lri));
			fbo_out.setWriteBuffer(lwi);
			shader_ssao_blur->setUniformValue("radius", GLfloat(crad));
			glDrawQuad(shader_ssao_blur);
			piSwap<int>(lwi, lri);
			crad *= 2;
		}
		// qDebug() << wi << ri << lms;
		/*wi = lri;
		ri = 1 - lms;*/
		glEnable(GL_TEXTURE_1D);
		if (tnoise == 0) {
			glGenTextures(1, &tnoise);
			glBindTexture(GL_TEXTURE_1D, tnoise);
			QByteArray ba;
			for (int i = 0; i < 32 * 3; ++i)
				ba.push_back(char(random() % 256));
			// qDebug() << ba;
			glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB8, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, ba.constData());
			glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		}
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(lri));
		glActiveTexture(GL_TEXTURE0 + 1);
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(2));
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glActiveTexture(GL_TEXTURE0 + 2);
		glBindTexture(GL_TEXTURE_2D, fbo_g.colorTexture(1));
		glActiveTexture(GL_TEXTURE0 + 3);
		glBindTexture(GL_TEXTURE_1D, tnoise);
		shader_ssao_merge->bind();
		shader_ssao_merge->setUniformValue("qgl_ModelViewProjectionMatrix", QMatrix4x4());
		shader_ssao_merge->setUniformValue("z_far", view.depthEnd());
		shader_ssao_merge->setUniformValue("z_near", view.depthStart());
		shader_ssao_merge->setUniformValue("mat_proj", mproj);
		shader_ssao_merge->setUniformValue("n0", 3);
		shader_ssao_merge->setUniformValue("t0", 0);
		shader_ssao_merge->setUniformValue("ts", 1);
		shader_ssao_merge->setUniformValue("tg1", 2);
		fbo_out.setWriteBuffer(lwi);
		glDrawQuad(shader_ssao_merge, corner_dirs);
		glDisable(GL_TEXTURE_1D);
		wi = lri;
		ri = lwi;
		// piSwap<int>(wi, ri);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(ri));
		// piSwap<int>(wi, ri);
	}

	if (view.isFeatureEnabled(QGLView::qglReflectionsEnabled)) {
		fbo_out.setWriteBuffer(2);
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(ri));
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //_MIPMAP_LINEAR);
		// glGenerateMipmap(GL_TEXTURE_2D);
		glActiveTexture(GL_TEXTURE0 + 1);
		glBindTexture(GL_TEXTURE_2D, fbo_g.colorTexture(0));
		glActiveTexture(GL_TEXTURE0 + 2);
		glBindTexture(GL_TEXTURE_2D, fbo_g.colorTexture(1));
		fbo_g.bindDepthTexture(7);
		shader_ssr->bind();
		shader_ssr->setUniformValue("qgl_ModelViewProjectionMatrix", QMatrix4x4());
		shader_ssr->setUniformValue("z_far", view.depthEnd());
		shader_ssr->setUniformValue("z_near", view.depthStart());
		shader_ssr->setUniformValue("mat_proj", mproj);
		shader_ssr->setUniformValue("t0", 1);
		shader_ssr->setUniformValue("t1", 2);
		shader_ssr->setUniformValue("ts", 0);
		shader_ssr->setUniformValue("td", 7);
		glDrawQuad(shader_ssr, corner_dirs);

		glActiveTexture(GL_TEXTURE0);
		int lri = 2, lwi = wi, lms = ri;
		if (view.isFeatureEnabled(QGLView::qglReflectionsBlur)) {
			shader_ssr_blur->bind();
			shader_ssr_blur->setUniformValue("qgl_ModelViewProjectionMatrix", QMatrix4x4());
			shader_ssr_blur->setUniformValue("dt", QVector2D(1.f / fbo_out.width(), 1.f / fbo_out.height()));
			shader_ssr_blur->setUniformValue("t0", 0);
			int passes = 5;
			int crad   = 1;
			for (int p = 0; p < passes; ++p) {
				glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(lri));
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //_MIPMAP_LINEAR);
				fbo_out.setWriteBuffer(lwi);
				shader_ssr_blur->setUniformValue("radius", GLfloat(crad));
				glDrawQuad(shader_ssr_blur);
				piSwap<int>(lwi, lri);
				crad *= 2;
			}
		}
		// qDebug() << wi << ri << lms;
		wi = lri;
		ri = 1 - lms;

		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(wi));
		glActiveTexture(GL_TEXTURE0 + 1);
		glBindTexture(GL_TEXTURE_2D, fbo_g.colorTexture(1));
		glActiveTexture(GL_TEXTURE0 + 2);
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(lms));
		shader_ssr_merge->bind();
		shader_ssr_merge->setUniformValue("qgl_ModelViewProjectionMatrix", QMatrix4x4());
		shader_ssr_merge->setUniformValue("t0", 0);
		shader_ssr_merge->setUniformValue("tg1", 1);
		shader_ssr_merge->setUniformValue("ts", 2);
		fbo_out.setWriteBuffer(ri);
		glDrawQuad(shader_ssr_merge);
		wi = ri;
		ri = 1 - ri;
		// piSwap<int>(wi, ri);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(wi));
		piSwap<int>(wi, ri);
	}

	if (view.isFeatureEnabled(QGLView::qglDepthOfFieldEnabled)) {
		if (view.isFeatureEnabled(QGLView::qglDepthOfFieldAutoFocusEnabled)) {
			GLfloat cw;
			// glReadBuffer();
			fbo_g.bind();
			glReadPixels(fbo_out.width() / 2, fbo_out.height() / 2, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &cw);
			fbo_out.bind();
			const float _pe = 2.4e-7f;
			float cz        = cw + cw - 1;
			cz              = ((_pe - 2.f) * view.depthStart()) / (cz + _pe - 1.f); // infinite depth
			float z         = view.feature(QGLView::qglDepthOfFieldFocus).toFloat(),
				  s         = view.feature(QGLView::qglDepthOfFieldAutoFocusSpeed).toFloat();
			z               = z * (1.f - s) + cz * s;
			view.setFeature(QGLView::qglDepthOfFieldFocus, z);
		}
		shader_dof->bind();
		shader_dof->setUniformValue("qgl_ModelViewProjectionMatrix", QMatrix4x4());
		shader_dof->setUniformValue("z_far", view.depthEnd());
		shader_dof->setUniformValue("z_near", view.depthStart());
		shader_dof->setUniformValue("focus", view.feature(QGLView::qglDepthOfFieldFocus).toFloat());
		shader_dof->setUniformValue("diaphragm", view.feature(QGLView::qglDepthOfFieldDiaphragm).toFloat());
		shader_dof->setUniformValue("t0", 0);
		shader_dof->setUniformValue("td", 7);
		fbo_g.bindDepthTexture(7);
		glActiveTexture(GL_TEXTURE0);

		int passes = 3;
		float crad = 1.;
		for (int p = 0; p < passes; ++p) {
			shader_dof->setUniformValue("radius", crad);
			fbo_out.setWriteBuffer(wi);
			glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(ri));
			shader_dof->setUniformValue("dt", QVector2D(1.f / fbo_out.width(), 0.f));
			glDrawQuad(shader_dof);
			piSwap<int>(wi, ri);
			fbo_out.setWriteBuffer(wi);
			glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(ri));
			shader_dof->setUniformValue("dt", QVector2D(0.f, 1.f / fbo_out.height()));
			glDrawQuad(shader_dof);
			piSwap<int>(wi, ri);
			crad *= 2.f;
		}

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(ri));
	}

	if (view.isFeatureEnabled(QGLView::qglEyeAccomodationEnabled)) {
		fbo_hsmall.bind();
		fbo_hsmall.setWriteBuffer(0);
		shader_small->bind();
		shader_small->setUniformValue("qgl_ModelViewProjectionMatrix", QMatrix4x4());
		shader_small->setUniformValue("t0", 0);
		glDrawQuad(shader_small);
		hcontent.resize(fbo_hsmall.width() * fbo_hsmall.height());
		glReadPixels(0, 0, fbo_hsmall.width(), fbo_hsmall.height(), GL_RGB, GL_FLOAT, hcontent.data());
		GLfloat max[3] = {0., 0., 0.}; // min[3] = {10000.,10000.,10000.},;
		for (int i = 0; i < hcontent.size(); ++i) {
			// if (min[0] > hcontent[i].x) min[0] = hcontent[i].x;
			if (max[0] < hcontent[i].x) max[0] = hcontent[i].x;
			// if (min[1] > hcontent[i].y) min[1] = hcontent[i].y;
			if (max[1] < hcontent[i].y) max[1] = hcontent[i].y;
			// if (min[2] > hcontent[i].z) min[2] = hcontent[i].z;
			if (max[2] < hcontent[i].z) max[2] = hcontent[i].z;
		}
		GLfloat mluma = (0.299f * max[0]) + (0.587f * max[1]) + (0.114f * max[2]);
		float nexp = mluma / 16.f, dexp = nexp - exposure_,
			  mestep = exposure_ * view.feature(QGLView::qglEyeAccomodationMaxSpeed).toFloat();
		dexp /= view.feature(QGLView::qglEyeAccomodationTime).toFloat();
		if (dexp > 0.f && dexp > mestep / 4) dexp = mestep / 4;
		if (dexp < 0.f && dexp < -mestep) dexp = -mestep;
		exposure_ += dexp;
		label_exp->setText(QString("exposure: %1").arg(exposure_));
		label_exp_step->setText(QString("d_exposure: %1").arg(dexp));
		// qDebug() << min[0] << max[0] << min[1] << max[1] << min[2] << max[2];
		fbo_hsmall.release();

		// glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(ri));
		fbo_out.bind();
		fbo_out.setWriteBuffer(wi);
		shader_hdr->bind();
		shader_hdr->setUniformValue("qgl_ModelViewProjectionMatrix", QMatrix4x4());
		shader_hdr->setUniformValue("t0", 0);
		shader_hdr->setUniformValue("exposure", GLfloat(1.f / exposure_));
		glDrawQuad(shader_hdr);
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(wi));
		piSwap<int>(wi, ri);
	}

	if (view.isFeatureEnabled(QGLView::qglMotionBlurEnabled)) {
		fbo_out.setWriteBuffer(wi);
		glActiveTexture(GL_TEXTURE0 + 1);
		glBindTexture(GL_TEXTURE_2D, fbo_g.colorTexture(4));
		shader_motion_blur->bind();
		shader_motion_blur->setUniformValue("qgl_ModelViewProjectionMatrix", QMatrix4x4());
		shader_motion_blur->setUniformValue("dt", QVector2D(1.f / fbo_out.width(), 1.f / fbo_out.height()));
		shader_motion_blur->setUniformValue("t0", 0);
		shader_motion_blur->setUniformValue("ts", 1);
		shader_motion_blur->setUniformValue("factor", view.feature(QGLView::qglMotionBlurFactor).toFloat());
		shader_motion_blur->setUniformValue("steps", view.feature(QGLView::qglMotionBlurSteps).toInt());
		glDrawQuad(shader_motion_blur);
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(wi));
		piSwap<int>(wi, ri);
	}

	if (view.isFeatureEnabled(QGLView::qglBloomEnabled)) {
		fbo_out.setWriteBuffer(2);
		fbo_out.setReadBuffer(ri);
		glBlitFramebuffer(0,
		                  0,
		                  fbo_out.width(),
		                  fbo_out.height(),
		                  0,
		                  0,
		                  fbo_out.width(),
		                  fbo_out.height(),
		                  GL_COLOR_BUFFER_BIT,
		                  GL_NEAREST);
		// QTime tm;
		// tm.restart();
		fbo_out.setWriteBuffer(wi);
		shader_bloom_0->bind();
		shader_bloom_0->setUniformValue("qgl_ModelViewProjectionMatrix", QMatrix4x4());
		shader_bloom_0->setUniformValue("factor", view.feature(QGLView::qglBloomFactor).toFloat());
		shader_bloom_0->setUniformValue("threshold", view.feature(QGLView::qglBloomThreshold).toFloat());
		shader_bloom_0->setUniformValue("t0", 0);
		glDrawQuad(shader_bloom_0);
		glActiveTexture(GL_TEXTURE0);
		piSwap<int>(wi, ri);

		shader_bloom_1->bind();
		shader_bloom_1->setUniformValue("qgl_ModelViewProjectionMatrix", QMatrix4x4());
		shader_bloom_1->setUniformValue("dt", QVector2D(1.f / fbo_out.width(), 1.f / fbo_out.height()));
		shader_bloom_1->setUniformValue("t0", 0);
		int radius = view.feature(QGLView::qglBloomRadius).toInt();
		int passes = qMax<int>(int(ceil(log2(radius))), 1);
		int crad   = 1;
		for (int p = 0; p < passes; ++p) {
			glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(ri));
			fbo_out.setWriteBuffer(wi);
			if (p == passes - 1) crad = piMax(1, radius - crad);
			shader_bloom_1->setUniformValue("radius", crad);
			glDrawQuad(shader_bloom_1);
			piSwap<int>(wi, ri);
			crad *= 2;
		}
		// qDebug() << tm.elapsed();
		fbo_out.setWriteBuffer(wi);
		//		glActiveTextureChannel(0);
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(ri));
		glActiveTexture(GL_TEXTURE0 + 1);
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(2));
		shader_fbo_add->bind();
		shader_fbo_add->setUniformValue("qgl_ModelViewProjectionMatrix", QMatrix4x4());
		shader_fbo_add->setUniformValue("t0", 0);
		shader_fbo_add->setUniformValue("t1", 1);
		glDrawQuad(shader_fbo_add);
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, fbo_out.colorTexture(wi));
		piSwap<int>(wi, ri);
	}

	glUseProgram(0);
	fbo_out.release();

	if (view.isFeatureEnabled(QGLView::qglFXAA)) {
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		shader_fxaa->bind();
		shader_fxaa->setUniformValue("dt", QVector2D(1.f / view.width(), 1.f / view.height()));
	} else {
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	}
	glDrawQuad();
	if (view.isFeatureEnabled(QGLView::qglFXAA)) shader_fxaa->release();
	prev_proj = mproj;
	prev_view = mview;
}


void RendererDeferredShading::init(int width, int height) {
	initializeOpenGLFunctions();
	resize(width, height);
}


void RendererDeferredShading::resize(int width, int height) {
	fbo_g.resize(width, height);
	fbo_out.resize(width, height);
	fbo_hsmall.resize(width / 16, height / 16);
	//	view.setSceneRect(QRect(0, 0, width, height));
	// df->move(-width / 2, -height / 2);
}


void RendererDeferredShading::reloadShaders() {
	for (int i = 0; i < shaders.size(); ++i) {
		QOpenGLShaderProgram * p(*(shaders[i].second));
		if (!p) p = new QOpenGLShaderProgram(view.context());
		loadShaders(p, shaders[i].first, "://shaders");
		*(shaders[i].second) = p;
	}
}


void RendererDeferredShading::setupShadersTextures(GLObjectBase & object, GLRendererBase::RenderingParameters & rp) {
	glActiveTexture(GL_TEXTURE0 + 6);
	glBindTexture(GL_TEXTURE_2D, white_image_id);
	glActiveTexture(GL_TEXTURE0 + 7);
	glBindTexture(GL_TEXTURE_2D, violent_image_id);
}


void RendererDeferredShading::setupDSLights(int pass, const QMatrix4x4 & view_matrix) {
	int light_start, light_end, lmax, shadow_start = 7;
	light_start         = pass * lights_per_pass;
	light_end           = qMin<int>((pass + 1) * lights_per_pass, view.lights().size());
	lmax                = light_start + lights_per_pass;
	amb_light.intensity = (pass == 0 ? 1. : 0.);
	amb_light.setColor(pass == 0 ? view.ambientColor() : Qt::black);
	amb_light.setName("ambient");
	setUniformLight(shader_ds_1, &amb_light, "qgl_AmbientLight");
	amb_light.intensity = 0.;
	QVector<Light *> lv;
	for (int i = light_start; i < light_end; ++i) {
		lv << view.lights()[i];
		glActiveTexture(GL_TEXTURE0 + shadow_start + i - light_start);
		glBindTexture(GL_TEXTURE_2D, lv.back()->shadow_map.depthTexture());
		if (view.isFeatureEnabled(QGLView::qglShadowsSoftEnabled)) {
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		} else {
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		}
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
	}
	amb_light.setName("null");
	for (int i = light_end; i < lmax; ++i)
		lv << &amb_light;
	// QStringList lnl; foreach (Light * l, lv) lnl << l->name();
	// qDebug() << "  lights" << light_start << "->" << light_end << ", inactive" << (lmax - light_end) << lnl;
	setUniformLights(shader_ds_1, lv, view_matrix, shadow_start);
}


void RendererDeferredShading::setupAmbientLight(const QColor & a, bool first_pass) {}
