/*
								+----------------------------------+
								|                                  |
								|  ***  GPU image processing  ***  |
								|                                  |
								|   Copyright © -tHE SWINe- 2008   |
								|                                  |
								|          GPUImgProc.cpp          |
								|                                  |
								+----------------------------------+
*/

/**
 *	@file dev/GPUImgProc.cpp
 *	@date 2008
 *	@author -tHE SWINe-
 *	@brief GPU image processing
 *
 *	@date 2008-08-03
 *
 *	renamed from ImageProc.cpp to GPUImgProc.cpp for the sake of consistency
 *
 *	@date 2008-08-08
 *
 *	added \#ifdef for windows 64, added \#define for GL_GLEXT_LEGACY (for linux builds)
 *
 *	@date 2008-08-09
 *
 *	added void CGLImageProc::DisableProgrammableShading(),
 *	CGLImageProc::DisableFixedVertexProcessing(), and
 *	CGLImageProc::DisableFixedFragmentProcessing(). those can be used to disable unwanted
 *	operations on outputs of vertex / fragment programs
 *
 *	added IdentityMatrices() and FullscreenQuad(). those are pretty common
 *	functions in GPU image processing.
 *
 *	added GPUImgProc.inl
 *
 *	@date 2009-05-23
 *
 *	removed all instances of std::vector::reserve and replaced them by stl_ut::Reserve_*
 *
 *	changed format of message, printed on FBO allocation (GPU_IMGPROC_REPORT_ALLOCATING_FBO)
 *
 *	@date 2009-10-20
 *
 *	fixed some warnings when compiling under VC 2005, implemented "Security
 *	Enhancements in the CRT" for VC 2008. compare against MyProjects_2009-10-19_
 *
 */

#include "../NewFix.h"

#include "../CallStack.h"
#include "../OpenGL20.h"
#include <GL/glut.h>
#include <vector>
#include <algorithm>
#include <math.h>
#include <stdio.h>
#include "../OpenGLState.h"
#include "../Texture.h"
#include "../TextureUtil.h"
#include "../RenderBuffer2.h"
#include "../Shader2.h"
#include "../ShaderSys.h"
#include "../Vector.h"
#include "../MinMax.h"
#include "../Hash.h"
#include "GPUImgProc.h"
#include "../StlUtils.h"

/*
 *								=== CGLImageProc ===
 */

/*
 *	class CGLImageProc::CFindBuffer
 *		- predicate for finding TLockableFBO by framebuffer pointer
 */
class CGLImageProc::CFindBuffer {
protected:
	const CGLFrameBuffer_FBO *m_p_buffer;

public:
	inline CFindBuffer(const CGLFrameBuffer_FBO *p_buffer)
		:m_p_buffer(p_buffer)
	{}

	inline bool operator ()(const TLockableFBO &r_t_lockable) const
	{
		return r_t_lockable.p_fbo == m_p_buffer;
	}
};

#if 0

/*
 *	class CGLImageProc::CFindShaderBySource
 *		- predicate for finding TShaderInstance by source filename
 */
class CGLImageProc::CFindShaderBySource {
protected:
	const std::string &m_r_s_source;

public:
	inline CFindShaderBySource(const std::string &r_s_source)
		:m_r_s_source(r_s_source)
	{}

	inline bool operator ()(const TShaderInstance &r_t_instance) const
	{
		return r_t_instance.s_source == m_r_s_source;
	}
};

#endif // 0 // not used right now

/*
 *	class CGLImageProc::CFindShaderById
 *		- predicate for finding TShaderInstance by id
 */
class CGLImageProc::CFindShaderById {
protected:
	const std::string &m_r_s_id;

public:
	inline CFindShaderById(const std::string &r_s_id)
		:m_r_s_id(r_s_id)
	{}

	inline bool operator ()(const TShaderInstance &r_t_instance) const
	{
		return !m_r_s_id.compare(r_t_instance.s_id);
	}
};

int CGLImageProc::m_n_instance_num = 0;
std::vector<CGLImageProc::TLockableFBO> CGLImageProc::m_frame_buffer_pool;
std::vector<CGLImageProc::TShaderInstance> CGLImageProc::m_shader_pool;

/*
 *	CGLImageProc::CGLImageProc()
 *		- default constructor
 */
CGLImageProc::CGLImageProc()
{
	if(!m_n_instance_num) {
		// initialize objects
	}
	++ m_n_instance_num;
}

/*
 *	CGLImageProc::~CGLImageProc()
 *		- destructor
 *		- takes care of cleanup once instance counter reaches zero
 */
CGLImageProc::~CGLImageProc()
{
	if(!(-- m_n_instance_num)) {
		std::for_each(m_frame_buffer_pool.begin(),
			m_frame_buffer_pool.end(), DeleteRenderBuffer);
		m_frame_buffer_pool.clear();
		// delete fbo's

		std::for_each(m_shader_pool.begin(), m_shader_pool.end(), DeleteShader);
		m_shader_pool.clear();
		// delete shaders
	}
}

/*
 *	static CGLFrameBuffer_FBO *CGLImageProc::p_GetRenderBuffer(int n_width, int n_height,
 *		int n_color_buffer_num, bool b_color_texture_target, GLenum n_color_internal_format,
 *		bool b_depth_buffer, bool b_depth_texture_target, GLenum n_depth_internal_format,
 *		bool b_stencil_buffer, bool b_stencil_texture_target, GLenum n_stencil_internal_format,
 *		bool b_lock = false)
 *
 */
CGLFrameBuffer_FBO *CGLImageProc::p_GetRenderBuffer(int n_width, int n_height,
	int n_color_buffer_num, bool b_color_texture_target, GLenum n_color_internal_format,
	bool b_depth_buffer, bool b_depth_texture_target, GLenum n_depth_internal_format,
	bool b_stencil_buffer, bool b_stencil_texture_target, GLenum n_stencil_internal_format,
	bool b_lock)
{
	if(!m_n_instance_num)
		return 0;
	// there must be instance of CGLImageProc somewhere

	for(int i = 0, n = m_frame_buffer_pool.size(); i < n; ++ i) {
		if(m_frame_buffer_pool[i].b_locked)
			continue; // this one is locked
		CGLFrameBuffer_FBO *p_rb = m_frame_buffer_pool[i].p_fbo;
		if(p_rb->n_Width() != n_width || p_rb->n_Height() != n_height ||
		   p_rb->n_Draw_Buffer_Num() != n_color_buffer_num ||
		   p_rb->b_Depth_Buffer() != b_depth_buffer ||
		   p_rb->b_Stencil_Buffer() != b_stencil_buffer)
			continue;
		if(n_color_buffer_num) {
			if(p_rb->b_Color_TextureTarget() != b_color_texture_target ||
			   (!b_color_texture_target && p_rb->n_Color_Format() != n_color_internal_format))
				continue;
			// color buffer props are significant
		}
		if(b_depth_buffer) {
			if(p_rb->b_Depth_TextureTarget() != b_depth_texture_target ||
			   (!b_depth_texture_target && p_rb->n_Depth_Format() != n_depth_internal_format))
				continue;
			// depth buffer props are significant
		}
		if(b_stencil_buffer) {
			if(p_rb->b_Stencil_TextureTarget() != b_stencil_texture_target ||
			   (!b_stencil_texture_target && p_rb->n_Stencil_Format() !=
			   n_stencil_internal_format))
				continue;
			// stancil buffer props are significant
		}
		if(b_lock)
			m_frame_buffer_pool[i].b_locked = true; // raise lock
		return p_rb;
	}
	// try to find existing render buffer

	if(!stl_ut::Reserve_1More(m_frame_buffer_pool))
		return 0;
	// make sure there's enough space in the pool

	int n_significant = 0;
	if(n_color_buffer_num && !b_color_texture_target)
		n_significant += n_color_buffer_num;
	if(b_depth_buffer && !b_depth_texture_target)
		++ n_significant;
	if(b_stencil_buffer && !b_stencil_buffer)
		++ n_significant;
	// calculate significancy (number of buffers which actually have to be allocated)

#ifdef GPU_IMGPROC_REPORT_ALLOCATING_FBO
	{
		printf("allocating new ");
		if(n_significant)
			printf("%d-significant", n_significant);
		else
			printf("insignificant");
		printf(" FBO (%d x %d", n_width, n_height);
		if(n_color_buffer_num || b_depth_buffer || b_stencil_buffer)
			printf(" x ");
		if(n_color_buffer_num > 0)
			printf("%d%s", n_color_buffer_num, (b_color_texture_target)? "ct" : "c");
		printf("%s%s)\n", (b_depth_buffer)? ((b_depth_texture_target)? ", dt" : ", d" ) : "",
			(b_stencil_buffer)? ((b_stencil_texture_target)? ", st" : ", s" ) : "");
		// print FBO requirements description ...
	}
#endif // GPU_IMGPROC_REPORT_ALLOCATING_FBO

	CGLFrameBuffer_FBO *p_rb;
	if(!(p_rb = new(std::nothrow) CGLFrameBuffer_FBO(n_width, n_height,
	   n_color_buffer_num, b_color_texture_target, n_color_internal_format,
	   b_depth_buffer, b_depth_texture_target, n_depth_internal_format,
	   b_stencil_buffer, b_stencil_texture_target, n_stencil_internal_format)))
		return 0;
	if(!p_rb->b_Status()) {
		delete p_rb;
		return 0;
	}
	m_frame_buffer_pool.push_back(TLockableFBO(p_rb, b_lock, n_significant));
	// create new render buffer

	return p_rb;
}

/*
 *	static void CGLImageProc::UnlockRenderBuffer(CGLFrameBuffer_FBO *p_buffer)
 *		- returns framebuffer which was requested locked
 *		- silently ignores framebuffers which do not exist in the pool
 */
void CGLImageProc::UnlockRenderBuffer(CGLFrameBuffer_FBO *p_buffer)
{
	if(!m_n_instance_num)
		return;
	// there must be instance of CGLImageProc somewhere

	std::vector<TLockableFBO>::iterator p_buffer_it =
		std::find_if(m_frame_buffer_pool.begin(), m_frame_buffer_pool.end(),
		CFindBuffer(p_buffer));
	if(p_buffer_it == m_frame_buffer_pool.end())
		return;
	(*p_buffer_it).b_locked = false;
}

/*
 *	static CGLShaderObject *CGLImageProc::p_CompileLinkReport(CGLState *p_state,
 *		const TShaderInfo *p_vs, const TShaderInfo *p_gs, const TShaderInfo *p_fs,
 *		bool b_exclusive)
 *		- compiles and links shaders p_vs, p_gs and p_fs (one or two of them can be 0)
 *		  and returns new CGLShaderObject
 *		- setting b_exclusive to true disables this shader from being stored in the pool,
 *		  caller is responsible for deleting shader once it's no longer needed
 *		- reports any errors / warnings to stderr
 *		- if shader couldn't be compiled returns 0
 */
CGLShaderObject *CGLImageProc::p_CompileLinkReport(CGLState *p_state,
	const TShaderInfo *p_vs, const TShaderInfo *p_gs,
	const TShaderInfo *p_fs, bool b_exclusive)
{
	if(!m_n_instance_num)
		return 0;
	// there must be instance of CGLImageProc somewhere

	std::string s_id_all;
	if(!Identify_ShaderSources(s_id_all, p_vs, p_gs, p_fs))
		return 0;
	// generate shader id (in case it is exclusive, it is for logging only - performance hint)

	if(!b_exclusive) {
		std::vector<TShaderInstance>::iterator p_shader_it =
			std::find_if(m_shader_pool.begin(), m_shader_pool.end(), CFindShaderById(s_id_all));
		if(p_shader_it != m_shader_pool.end()) {
			++ (*p_shader_it).n_reference_num;
			return (*p_shader_it).p_shader;
		}
		// try looking for shader in the pool

		if(!stl_ut::Reserve_1More(m_shader_pool))
			return 0;
		// make sure there is space in the pool for the shader
	}
	// try looking for shader in the pool

#ifdef GPU_IMGPROC_REPORT_COMPILING_SHADER
	printf("compiling \'%s\'\n", s_id_all.c_str());
#endif // GPU_IMGPROC_REPORT_COMPILING_SHADER
	// let's see what we can do

	CGLShaderObject *p_shader_object;
	if(!(p_shader_object = new(std::nothrow) CGLShaderObject(p_vs, p_gs, p_fs))) {
		//fprintf(stderr, "error: not enough memory\n");
		return 0;
	}

	char *p_s_compile = 0, *p_s_link = 0;
	bool b_result = p_shader_object->Compile_Link(p_state, p_s_compile, p_s_link);
	if(p_s_compile) {
#ifdef GPU_IMGPROC_REPORT_COMPILER_MESSAGES
		fprintf(stderr, "=== %s/%s compiler report ===\n%s\n",
			(p_vs)? p_vs->p_s_name : "(null)", (p_fs)? p_fs->p_s_name : "(null)", p_s_compile);
#endif // GPU_IMGPROC_REPORT_COMPILER_MESSAGES
		delete[] p_s_compile;
	}
	if(p_s_link) {
#ifdef GPU_IMGPROC_REPORT_COMPILER_MESSAGES
		fprintf(stderr, "=== %s/%s linker report ===\n%s\n",
			(p_vs)? p_vs->p_s_name : "(null)", (p_fs)? p_fs->p_s_name : "(null)", p_s_link);
#endif // GPU_IMGPROC_REPORT_COMPILER_MESSAGES
		delete[] p_s_link;
	}

	p_state->DisableVertexProgram();
	p_state->DisableFragmentProgram();
	p_state->BindProgramObject(0);

	if(!b_result) {
		delete p_shader_object;
		return 0;
	}

	if(!b_exclusive) {
		TShaderInstance t_inst(p_shader_object);
		t_inst.s_id.swap(s_id_all); // dammages s_id_all but is cheaper than operator =
		m_shader_pool.push_back(t_inst);
	}
	// in case shader isn't exclusive, put it to the pool

	return p_shader_object;
}

/*
 *	static void CGLImageProc::ReturnShaderObject(CGLShaderObject *p_shader)
 *		- decrements reference counter for shader p_shader,
 *		  once it reaches zero shader is deleted
 *		- note this has no effect if p_shader is not found in the pool
 */
void CGLImageProc::ReturnShaderObject(CGLShaderObject *p_shader)
{
	if(!m_n_instance_num)
		return;
	// there must be instance of CGLImageProc somewhere

	std::vector<TShaderInstance>::iterator p_shader_it =
		std::find(m_shader_pool.begin(), m_shader_pool.end(), p_shader);
	if(p_shader_it == m_shader_pool.end()) {
		delete p_shader; // not in the pool // fixme - is this nice behavior?
		return;
	}
	if(!(-- (*p_shader_it).n_reference_num)) {
		delete (*p_shader_it).p_shader;
		m_shader_pool.erase(p_shader_it);
	}
}

/*
 *	static void Hash_TNamedResource(CStreamHash<TMD5> &r_hash,
 *		const TShaderInfo::TNamedResource &r_resource)
 *		- utility function for CGLImageProc::Identify_ShaderSource(),
 *		  it is non-member static because Hash.h doesn't need to be included
 *		  in ImageProc.h that way
 *		- sends r_resource data to r_hash
 */
static void Hash_TNamedResource(CStreamHash<TMD5> &r_hash,
	const TShaderInfo::TNamedResource &r_resource)
{
	r_hash.Process_Data(r_resource.p_s_Name(), strlen(r_resource.p_s_Name()) * sizeof(char));
	r_hash.Process_Data(&r_resource.n_resource_index, sizeof(int));
}

/*
 *	static void Hash_TParamInfo(CStreamHash<TMD5> &r_hash,
 *		const TShaderInfo::TParamInfo &r_info)
 *		- utility function for CGLImageProc::Identify_ShaderSource(),
 *		  it is non-member static because Hash.h doesn't need to be included
 *		  in ImageProc.h that way
 *		- sends r_info data to r_hash
 */
static void Hash_TParamInfo(CStreamHash<TMD5> &r_hash,
	const TShaderInfo::TParamInfo &r_info)
{
	r_hash.Process_Data(r_info.p_s_Name(), strlen(r_info.p_s_Name()) * sizeof(char));
	r_hash.Process_Data(&r_info.n_resource_index, sizeof(int));
	r_hash.Process_Data(&r_info.b_environment, sizeof(bool));
}

/*
 *	static bool CGLImageProc::Identify_ShaderSources(std::string &r_s_id_all,
 *		const TShaderInfo *p_vs, const TShaderInfo *p_gs,
 *		const TShaderInfo *p_fs)
 *		- creates and concatenates id's of p_vs, p_gs ans p_fs (some of them can be 0)
 *		- r_s_source is output of the function, id is stored there
 *		- returns true on success, false on failure
 */
bool CGLImageProc::Identify_ShaderSources(std::string &r_s_id_all,
	const TShaderInfo *p_vs, const TShaderInfo *p_gs,
	const TShaderInfo *p_fs)
{
	std::string p_id[3];
	if(p_vs) {
		if(!Identify_ShaderSource(p_id[0], p_vs)) {
			//fprintf(stderr, "error: not enough memory\n");
			return false;
		}
	}
	if(p_gs) {
		if(!Identify_ShaderSource(p_id[1], p_gs)) {
			//fprintf(stderr, "error: not enough memory\n");
			return false;
		}
	}
	if(p_fs) {
		if(!Identify_ShaderSource(p_id[2], p_fs)) {
			//fprintf(stderr, "error: not enough memory\n");
			return false;
		}
	}
	// generate id's

	int n_length = 2;
	for(int i = 0; i < 3; ++ i)
		n_length += p_id[i].length();
	// calculate length

	r_s_id_all.erase();
	if(!stl_ut::Reserve_N(r_s_id_all, n_length))
		return false;
	for(int i = 0; i < 3; ++ i) {
		if(i && !p_id[i - 1].empty())
			r_s_id_all += ' ';
		r_s_id_all += p_id[i];
	}
	// alloc string and concat

	return true;
}

/*
 *	static bool CGLImageProc::Identify_ShaderSource(std::string &r_s_id,
 *		const TShaderInfo *p_shader)
 *		- creates string with unique id that can be used to identify shader p_shader
 *		- r_s_id is output of the function, id is stored there
 *		- returns true on success, false on failure
 */
bool CGLImageProc::Identify_ShaderSource(std::string &r_s_id,
	const TShaderInfo *p_shader)
{
	int n_length = 2 + strlen(p_shader->p_s_name) + 33;
	// calculate string length (2 for processor and separator,
	// for name and 33 for separator and md5)

	r_s_id.erase();
	if(!stl_ut::Reserve_N(r_s_id, n_length))
		return false;
	// make sure there's enough space

	CStreamHash<TMD5> hash;

	hash.Process_Data(&p_shader->n_processor, sizeof(int));
	hash.Process_Data(p_shader->p_s_name, strlen(p_shader->p_s_name) * sizeof(char));
	hash.Process_Data(&p_shader->n_geometry_in_type, sizeof(GLenum));
	hash.Process_Data(&p_shader->n_geometry_out_type, sizeof(GLenum));
	hash.Process_Data(&p_shader->n_max_vertex_num, sizeof(int));
	if(p_shader->p_high) {
		int n = p_shader->p_high->n_Sampler_Num();
		hash.Process_Data(&n, sizeof(int));
		for(int i = 0; i < n; ++ i)
			Hash_TNamedResource(hash, p_shader->p_high->t_Sampler(i));
		hash.Process_Data(p_shader->p_high->p_s_Source_Code(),
			strlen(p_shader->p_high->p_s_Source_Code()) * sizeof(char));
	}
	if(p_shader->p_low) {
		int n = p_shader->p_low->n_Sampler_Num();
		hash.Process_Data(&n, sizeof(int));
		for(int i = 0; i < n; ++ i)
			Hash_TNamedResource(hash, p_shader->p_low->t_Sampler(i));
		hash.Process_Data(p_shader->p_low->p_s_Source_Code(),
			strlen(p_shader->p_low->p_s_Source_Code()) * sizeof(char));
		int m = p_shader->p_low->n_Parameter_Num();
		hash.Process_Data(&m, sizeof(int));
		for(int i = 0; i < m; ++ i)
			Hash_TParamInfo(hash, p_shader->p_low->t_Parameter(i));
	}
	// hash contents of TShaderInfo (do not hash the whole structure, members might be aligned)

	TMD5 t_md5 = hash.t_Result();

	const char *p_processor_name[] = {"v|", "f|", "g|"};
	/*char p_s_md5[33];
	sprintf(p_s_md5, "%08x%08x%08x%08x", t_md5[0], t_md5[1], t_md5[2], t_md5[3]);
	// print md5

	r_s_id = p_processor_name[p_shader->n_processor];
	r_s_id += p_shader->p_s_name;
	r_s_id += "|";
	r_s_id += p_s_md5;
	// concatenate*/

	return stl_ut::Format(r_s_id, "%s%s|%08x%08x%08x%08x", p_processor_name[p_shader->n_processor],
		p_shader->p_s_name, t_md5[0], t_md5[1], t_md5[2], t_md5[3]);
}

/*
 *	static bool CGLImageProc::Generate_SourceString(std::string &r_s_source,
 *		const char *p_s_vertex_filename, const char *p_s_geometry_filename,
 *		const char *p_s_fragment_filename)
 *		- concatenates shader source file names into r_s_source
 *		- returns true on success, false on failure (not enough memory)
 */
bool CGLImageProc::Generate_SourceString(std::string &r_s_source,
	const char *p_s_vertex_filename, const char *p_s_geometry_filename,
	const char *p_s_fragment_filename)
{
	int n_length = ((p_s_vertex_filename)? strlen(p_s_vertex_filename) : 0) +
		((p_s_geometry_filename)? strlen(p_s_geometry_filename) : 0) +
		((p_s_fragment_filename)? strlen(p_s_fragment_filename) : 0) + 2;
	// calculate string length

	r_s_source.erase();
	if(!stl_ut::Reserve_N(r_s_source, n_length))
		return false;
	// make sure there's enough space

	if(p_s_vertex_filename)
		r_s_source = p_s_vertex_filename;
	r_s_source += "|";
	if(p_s_geometry_filename)
		r_s_source += p_s_geometry_filename;
	r_s_source += "|";
	if(p_s_fragment_filename)
		r_s_source += p_s_fragment_filename;
	// concatenate filenames

	return true;
}

/*
 *	static inline void CGLImageProc::DeleteRenderBuffer(TLockableFBO &t_buffer)
 *		- cleanup helper function for std::for_each
 */
inline void CGLImageProc::DeleteRenderBuffer(TLockableFBO &t_buffer)
{
	delete t_buffer.p_fbo;
}

/*
 *	static inline void CGLImageProc::DeleteShader(TShaderInstance &t_shader)
 *		- cleanup helper function for std::for_each
 */
inline void CGLImageProc::DeleteShader(TShaderInstance &t_shader)
{
	delete t_shader.p_shader;
}

/*
 *								=== ~CGLImageProc ===
 */

/*
 *								=== CGLPerlinNoise ===
 */

const unsigned char CGLPerlinNoise::m_p_perm[256] = {151, 160, 137, 91, 90, 15,
	131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8,
	99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62,
	94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174,
	20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77,
	146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55,
	46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76,
	132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164,
	100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38,
	147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182,
	189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221,
	153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79,
	113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193,
	238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107,
	49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45,
	127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141,
	128, 195, 78, 66, 215, 61, 156, 180};

const int CGLPerlinNoise::m_p_grad3[16][3] = {{0, 1, 1}, {0, 1, -1},
	{0, -1, 1}, {0, -1, -1}, {1, 0, 1}, {1, 0, -1}, {-1, 0, 1}, {-1, 0, -1},
	{1, 1, 0}, {1, -1, 0}, {-1, 1, 0}, {-1, -1, 0}, {1, 0, -1}, {-1, 0, -1},
	{0, -1, 1}, {0, 1, 1}};

/*
 *	CGLPerlinNoise::CGLPerlinNoise()
 *		- default constructor; does nothing
 */
CGLPerlinNoise::CGLPerlinNoise()
	:m_p_noise_perm_tex(0), m_p_noise_shader(0),
	m_t_scale(0, 0, 0, 0), m_t_turbulence(0, 0, 0, 0),
	m_t_amplitude(0, 0, 0, 0), m_t_octave_num(0, 0, 0, 0)
{}

/*
 *	CGLPerlinNoise::~CGLPerlinNoise()
 *		- default destructor
 */
CGLPerlinNoise::~CGLPerlinNoise()
{
	if(m_p_noise_perm_tex)
		delete m_p_noise_perm_tex;
	if(m_p_noise_shader)
		CGLImageProc::ReturnShaderObject(m_p_noise_shader);
}

/*
 *	bool CGLPerlinNoise::Render_Noise(CGLState *p_state, CGLTexture_2D *p_dest,
 *		Vector2f v_phase, float f_scale, float f_amplitude, int n_octave_num,
 *		float f_persistence)
 *		- renders turbulence noise to 2D texture p_dest (RGBA are all set to noise value)
 *		- p_state is noise phase, f_scale is noise size, f_amplitude is amplitude,
 *		  n_octave_num is number of noise octaves and f_persistence is octave
 *		  amplitude exponent
 *		- returns true on success, false on failure
 */
bool CGLPerlinNoise::Render_Noise(CGLState *p_state, CGLTexture_2D *p_dest,
	Vector2f v_phase, float f_scale, float f_amplitude, int n_octave_num, float f_persistence)
{
	if(glGetError() != GL_NO_ERROR)
		return false;

	if(!m_p_noise_perm_tex && !(m_p_noise_perm_tex = p_MakePermTexture(p_state)))
		return false;
	// make sure permutation texture is ready

	if(!m_p_noise_shader) {
		const TShaderInfo t_fractal_noise(
			"fractal_noise_fs", TShaderInfo::proc_Fragment,
			"uniform sampler2D n_perm_tex;\n"
			"uniform vec2 v_scale;\n"
			"uniform int n_octave_num;\n"
			"uniform float f_turbulence;\n"
			"uniform float f_amplitude;\n"
			"\n"
			"#define f_perm_texture_pixel_size 0.00390625\n"
			"#define f_perm_texture_half_pixel_size 0.001953125\n"// 1/256, 0.5/256
			"\n"
			"float fade(float t)\n"
			"{\n"
			"    return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);\n" // improved, C2 continuous
			"}\n"
			"\n"
			"float f_Noise(vec2 v_pos)\n"
			"{\n"
			"    vec2 v_int_pos = f_perm_texture_pixel_size * floor(v_pos) +\n"
			"        f_perm_texture_half_pixel_size;\n"
			"    vec2 v_frac_pos = fract(v_pos);\n"
			"    vec2 grad00 = texture2D(n_perm_tex, v_int_pos).rg * 4.0 - 1.0;\n"
			"    float n00 = dot(grad00, v_frac_pos);\n"
			"    vec2 grad10 = texture2D(n_perm_tex, v_int_pos +\n"
			"        vec2(f_perm_texture_pixel_size, 0.0)).rg * 4.0 - 1.0;\n"
			"    float n10 = dot(grad10, v_frac_pos - vec2(1.0, 0.0));\n"
			"    vec2 grad01 = texture2D(n_perm_tex, v_int_pos +\n"
			"        vec2(0.0, f_perm_texture_pixel_size)).rg * 4.0 - 1.0;\n"
			"    float n01 = dot(grad01, v_frac_pos - vec2(0.0, 1.0));\n"
			"    vec2 grad11 = texture2D(n_perm_tex, v_int_pos +\n"
			"        vec2(f_perm_texture_pixel_size,\n"
			"        f_perm_texture_pixel_size)).rg * 4.0 - 1.0;\n"
			"    float n11 = dot(grad11, v_frac_pos - vec2(1.0, 1.0));\n"
			"    vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade(v_frac_pos.x));\n"
			"    return mix(n_x.x, n_x.y, fade(v_frac_pos.y));\n"
			"}\n"
			"\n"
			"void main()\n"
			"{\n"
			"    float f_noise = 0.0, f_amp = f_amplitude;\n"
			"    vec2 v_pos = gl_TexCoord[0].xy * v_scale;\n"
			"    for(int i = 0; i < n_octave_num; ++ i) {\n"
			"        f_noise += (f_Noise(v_pos) + f_amplitude) * f_amp;\n"
			"        f_amp *= f_turbulence;\n"
			"        v_pos *= 2.0;\n"
			"    }\n"
			"    gl_FragColor = vec4(f_noise);\n"
			"}\n");

		if(!(m_p_noise_shader = CGLImageProc::p_CompileLinkReport(p_state,
		   0, &t_fractal_noise, false)))
			return false;
		m_t_scale = m_p_noise_shader->t_Find_Parameter("v_scale");
		m_t_turbulence = m_p_noise_shader->t_Find_Parameter("f_turbulence");
		m_t_amplitude = m_p_noise_shader->t_Find_Parameter("f_amplitude");
		m_t_octave_num = m_p_noise_shader->t_Find_IntParameter("n_octave_num");
		if(!m_t_scale.b_Valid() || !m_t_turbulence.b_Valid() ||
		   !m_t_amplitude.b_Valid() || !m_t_octave_num.b_Valid())
			return false;
	}
	// make sure shader is ready

	if(!m_t_scale.SetValue(f_scale, f_scale * float(p_dest->n_Height()) / p_dest->n_Width()) ||
	   !m_t_octave_num.SetValue(n_octave_num) || !m_t_turbulence.SetValue(f_persistence) ||
	   !m_t_amplitude.SetValue(f_amplitude))
		return false;
	// set shader params

	CGLFrameBuffer_FBO *p_fbo;
	if(!(p_fbo = CGLImageProc::p_GetRenderBuffer(p_dest->n_Width(), p_dest->n_Height(), 1,
	   true, p_dest->n_Pixel_Format(), false, false, 0, false, false, 0)))
		return false;
	// get FBO

	if(!p_fbo->Bind() ||
	   !p_fbo->BindColorTexture_2D(*p_dest, GL_TEXTURE_2D, 0))
		return false;
	// bind FBO & textures

	glViewport(0, 0, p_dest->n_Width(), p_dest->n_Height());
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	// viewport, matrices & clear

	p_state->DisableBlend();
	p_state->DisableAlphaTest();
	p_state->DisableCullFace();
	// disable things shader do not control

	p_state->ActiveTextureUnit(0);
	m_p_noise_perm_tex->Bind_Enable(p_state);
	// shader needs permutation texture

	if(!m_p_noise_shader->Bind(p_state))
		return false;
	// bind shader

	if(glGetError() != GL_NO_ERROR)
		return false;

	glBegin(GL_QUADS);
	glTexCoord2f(v_phase.x + 0, v_phase.y + 0);
	glVertex2f(-1, -1);
	glTexCoord2f(v_phase.x + 0, v_phase.y + 1);
	glVertex2f(-1, 1);
	glTexCoord2f(v_phase.x + 1, v_phase.y + 1);
	glVertex2f(1, 1);
	glTexCoord2f(v_phase.x + 1, v_phase.y + 0);
	glVertex2f(1, -1);
	glEnd();
	// draw fullscreen quad

	if(glGetError() != GL_NO_ERROR)
		return false;

	if(!p_fbo->BindColorTexture_2D(0, GL_TEXTURE_2D, 0) ||
	   !p_fbo->Release())
		return false;
	// release textures & FBO

	return glGetError() == GL_NO_ERROR;
}

/*
 *	static unsigned char *CGLPerlinNoise::p_PermTextureData()
 *		- returns data for permutation texture (256 x 256, RGBA)
 *		- returns 0 in case there's not enough free memory
 */
unsigned char *CGLPerlinNoise::p_PermTextureData()
{
	unsigned char *p_pixels;
	if(!(p_pixels = new(std::nothrow) unsigned char[256 * 256 * 4]))
		return 0;
	unsigned char *p_dest = p_pixels;
	for(int i = 0; i < 256; ++ i) {
		for(int j = 0; j < 256; ++ j, p_dest += 4) {
			char n_value = m_p_perm[(j + m_p_perm[i]) & 0xff];
			p_dest[0] = m_p_grad3[n_value & 0x0f][0] * 64 + 64;
			p_dest[1] = m_p_grad3[n_value & 0x0f][1] * 64 + 64;
			p_dest[2] = m_p_grad3[n_value & 0x0f][2] * 64 + 64;
			p_dest[3] = n_value;
		}
	}
	return p_pixels;
}

/*
 *	static CGLTexture_2D *CGLPerlinNoise::p_MakePermTexture(CGLState *p_state)
 *		- returns a new 2D texture (256 x 256, RGBA) containing permutation table
 *		- p_state is OpenGL state guard
 *		- returns 0 on failure
 */
CGLTexture_2D *CGLPerlinNoise::p_MakePermTexture(CGLState *p_state)
{
	unsigned char *p_data;
	if(!(p_data = p_PermTextureData()))
		return 0;
	CGLTexture_2D *p_texture = new(std::nothrow) CGLTexture_2D(p_state, 256, 256, GL_RGBA8,
		false, 0, GL_RGBA, GL_UNSIGNED_BYTE, p_data);
	delete[] p_data;
	if(!p_texture)
		return 0;
	if(!p_texture->b_Status()) {
		delete p_texture;
		return 0;
	}
	CGLTextureParams &r_params = p_texture->r_Parameters(p_state);
	r_params.Set_Texture_Min_Filter(GL_NEAREST);
	r_params.Set_Texture_Mag_Filter(GL_NEAREST);
	return p_texture;
}

/*
 *								=== ~CGLPerlinNoise ===
 */

/*
 *								=== CGLGaussFilter ===
 */

/*
 *	CGLGaussFilter::CGLGaussFilter()
 *		- default constructor
 *		- doesn't do anything
 */
CGLGaussFilter::CGLGaussFilter()
	:m_p_gauss_shader(0), m_t_org_off(0, 0, 0, 0), m_t_scale(0, 0, 0, 0),
	m_t_kernel_size(0, 0, 0, 0), m_n_radius(0), m_f_kernel_rescale(0)
{}

/*
 *	CGLGaussFilter::~CGLGaussFilter()
 *		- destructor
 */
CGLGaussFilter::~CGLGaussFilter()
{
	if(m_p_gauss_shader)
		CGLImageProc::ReturnShaderObject(m_p_gauss_shader);
}

/*
 *	bool CGLGaussFilter::Filter(CGLState *p_state, CGLTexture_2D *p_dest,
 *		CGLTexture_2D *p_temp_tex, CGLTexture_2D *p_src, int n_radius)
 *		- applies separable gaussian filter with radius n_radius
 *		- p_src is source image, p_temp_tex is temporary texture and p_dest
 *		  is destination texture. note all three should have the same resolution.
 *		- p_state is OpenGL state guard
 *		- returns true on success, false on failure
 */
bool CGLGaussFilter::Filter(CGLState *p_state, CGLTexture_2D *p_dest,
	CGLTexture_2D *p_temp_tex, CGLTexture_2D *p_src, int n_radius)
{
	if(m_n_radius != n_radius) {
		float f_sum = 0;
		for(int i = 0; i < n_radius * 2; ++ i) {
			float f_r = float(i - n_radius) / n_radius;
			f_sum += float(exp(-.5f * f_r * f_r));
		}
		// calculate kernel sum

		m_n_radius = n_radius;
		m_f_kernel_rescale = 1.0f / f_sum;
	}
	// calculate kernel normlization factor

	if(!m_p_gauss_shader) {
		const TShaderInfo t_separable_gauss(
			"separable_gauss_fs", TShaderInfo::proc_Fragment,
			"uniform sampler2D n_tex;\n"
			"uniform vec4 v_org_off;\n" // v_org, v_off
			"uniform int n_kernel_size;\n"
			"uniform vec2 v_scale;\n" // f_rescale, f_inv_kernel_size
			"\n"
			"void main()\n"
			"{\n"
			"    vec2 v_texcoord = gl_TexCoord[0].xy + v_org_off.xy;\n"
			"    vec4 v_out = vec4(0, 0, 0, 0);\n"
			"    for(int i = 0; i < n_kernel_size; ++ i) {\n"
			"        vec2 v_r = (v_texcoord - gl_TexCoord[0].xy) * v_scale.y;\n"
			"        float f_gauss = exp(-.5 * dot(v_r, v_r));\n"
			"        v_out += texture2D(n_tex, v_texcoord) * f_gauss;\n"
			"        v_texcoord += v_org_off.zw;\n"
			"    }\n"
			"    gl_FragColor = v_out * v_scale.x;\n"
			"}\n");
		// separable gaussian blur filter

		if(!(m_p_gauss_shader =
		   CGLImageProc::p_CompileLinkReport(p_state, 0, &t_separable_gauss, false)))
			return false;
		m_t_org_off = m_p_gauss_shader->t_Find_Parameter("v_org_off");
		m_t_scale = m_p_gauss_shader->t_Find_Parameter("v_scale");
		m_t_kernel_size = m_p_gauss_shader->t_Find_IntParameter("n_kernel_size");
		if(!m_t_org_off.b_Valid() || !m_t_scale.b_Valid() || !m_t_kernel_size.b_Valid())
			return false;
	}
	// make sure we have shader

	if(!m_t_kernel_size.SetValue(n_radius * 2))
		return false;
	// set common shader parameters

	CGLFrameBuffer_FBO *p_fbo;
	if(!(p_fbo = CGLImageProc::p_GetRenderBuffer(p_dest->n_Width(), p_dest->n_Height(), 1,
	   true, p_dest->n_Pixel_Format(), false, false, 0, false, false, 0)))
		return false;
	// get FBO for dest texture
	
	if(!p_fbo->Bind())
		return false;
	// bind FBO

	glViewport(0, 0, p_dest->n_Width(), p_dest->n_Height());
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	// viewport, matrices & clear

	p_state->DisableBlend();
	p_state->DisableAlphaTest();
	p_state->DisableCullFace();
	// disable things shader do not control

	p_state->ActiveTextureUnit(0);

	for(int n_pass = 0; n_pass < 2; ++ n_pass) {
		if(!p_fbo->BindColorTexture_2D((n_pass)? *p_dest : *p_temp_tex, GL_TEXTURE_2D, 0))
			return false;
		// bind output texture

		((n_pass)? p_temp_tex : p_src)->Bind_Enable(p_state);
		// bind input texture

		if(n_pass) {
			float f_pixel_width = 1.0f / p_dest->n_Width();
			if(!m_t_org_off.SetValue(-f_pixel_width * n_radius, 0, f_pixel_width, 0) ||
			   !m_t_scale.SetValue(m_f_kernel_rescale, 1 / (f_pixel_width * n_radius)))
				return false;
		} else {
			float f_pixel_height = 1.0f / p_dest->n_Height();
			if(!m_t_org_off.SetValue(0, -f_pixel_height * n_radius, 0, f_pixel_height) ||
			   !m_t_scale.SetValue(m_f_kernel_rescale, 1 / (f_pixel_height * n_radius)))
				return false;
		}
		// set shader params

		if(!m_p_gauss_shader->Bind(p_state))
			return false;
		// bind shader (must reload uniforms in each pass)

		if(glGetError() != GL_NO_ERROR)
			return false;

		glBegin(GL_QUADS);
		glTexCoord2f(0, 0);
		glVertex2f(-1, -1);
		glTexCoord2f(0, 1);
		glVertex2f(-1, 1);
		glTexCoord2f(1, 1);
		glVertex2f(1, 1);
		glTexCoord2f(1, 0);
		glVertex2f(1, -1);
		glEnd();
		// draw fullscreen quad

		if(glGetError() != GL_NO_ERROR)
			return false;

		if(!p_fbo->BindColorTexture_2D(0, GL_TEXTURE_2D, 0))
			return false;
		// release the texture
	}

	if(!p_fbo->Release())
		return false;
	// release texture & FBO

	return glGetError() == GL_NO_ERROR;
}

/*
 *								=== ~CGLGaussFilter ===
 */

/*
 *								=== CGLMinMaxFilter ===
 */

/*
 *	CGLMinMaxFilter::CGLMinMaxFilter()
 *		- default constructor; does nothing
 */
CGLMinMaxFilter::CGLMinMaxFilter()
	:m_p_minmax_shader1(0), m_p_minmax_shader2(0),
	m_t_right_off_center(0, 0, 0, 0), m_t_off_left_off_right(0, 0, 0, 0),
	m_t_kernel_size_side_size1(0, 0, 0, 0), m_t_up_off_center(0, 0, 0, 0),
	m_t_off_top_off_bottom(0, 0, 0, 0), m_t_kernel_size_side_size2(0, 0, 0, 0)
{}

/*
 *	CGLMinMaxFilter::~CGLMinMaxFilter()
 *		- destructor
 */
CGLMinMaxFilter::~CGLMinMaxFilter()
{
	if(m_p_minmax_shader1)
		CGLImageProc::ReturnShaderObject(m_p_minmax_shader1);
	if(m_p_minmax_shader2)
		CGLImageProc::ReturnShaderObject(m_p_minmax_shader2); // deleted automatically
}

/*
 *	bool CGLMinMaxFilter::Filter(CGLState *p_state, CGLTexture_2D *p_dest_min,
 *		CGLTexture_2D *p_dest_max, CGLTexture_2D *p_temp_tex[4], CGLTexture_2D *p_src,
 *		int n_radius)
 *		- finds minimal and maximal pixel values in p_src image under cross-shaped
 *		  kernel with radius n_radius
 *		- p_dest_min is filled with minima and p_dest_max is filled with maxima
 *		- p_temp_tex is array of four temporary textures
 *		- p_state is OpenGL state guard
 *		- note all textures must have the same dimensions and should have the same format
 *		- returns true on success, false on failure
 */
bool CGLMinMaxFilter::Filter(CGLState *p_state, CGLTexture_2D *p_dest_min,
	CGLTexture_2D *p_dest_max, CGLTexture_2D *p_temp_tex[4], CGLTexture_2D *p_src, int n_kernel_size)
{
	if(!m_p_minmax_shader1) {
		const TShaderInfo t_minmax_pass1(
			"hifi_minmax1_fs", TShaderInfo::proc_Fragment,
			"uniform sampler2D n_tex;\n"
			"uniform vec4 v_right_off_center, v_off_left_off_right;\n"
			"uniform ivec2 v_kernel_size_side_size;\n"
			"\n"
			"void main()\n"
			"{\n"
			"    vec2 v_texcoord = gl_TexCoord[0].xy + v_right_off_center.zw;\n"
			"    vec4 v_min = texture2D(n_tex, v_texcoord);\n"
			"    vec4 v_max = v_min;\n"
			"    for(int i = 1; i < v_kernel_size_side_size.x; ++ i) {\n"
			"        v_texcoord += v_right_off_center.xy;\n"
			"        vec4 v_sample = texture2D(n_tex, v_texcoord);\n"
			"        v_min = min(v_min, v_sample);\n"
			"        v_max = max(v_max, v_sample);\n"
			"    }\n"
			"    vec4 v_min_wide = v_min;\n"
			"    vec4 v_max_wide = v_max;\n"
			"    vec4 v_texcoord2 = gl_TexCoord[0].xyxy + v_off_left_off_right;\n"
			"    for(int i = 0; i < v_kernel_size_side_size.y; ++ i) {\n"
			"        v_texcoord2 += v_right_off_center.xyxy;\n"
			"        vec4 v_sample_l = texture2D(n_tex, v_texcoord2.xy);\n"
			"        v_min_wide = min(v_min_wide, v_sample_l);\n"
			"        v_max_wide = max(v_max_wide, v_sample_l);\n"
			"        vec4 v_sample_r = texture2D(n_tex, v_texcoord2.zw);\n"
			"        v_min_wide = min(v_min_wide, v_sample_r);\n"
			"        v_max_wide = max(v_max_wide, v_sample_r);\n"
			"    }\n"
			"    gl_FragData[0] = v_min;\n"
			"    gl_FragData[1] = v_min_wide;\n"
			"    gl_FragData[2] = v_max;\n"
			"    gl_FragData[3] = v_max_wide;\n"
			"}\n");
		// min-max shader for pass 1

		if(!(m_p_minmax_shader1 = CGLImageProc::p_CompileLinkReport(p_state,
		   0, &t_minmax_pass1, false)))
			return false;
		m_t_right_off_center = m_p_minmax_shader1->t_Find_Parameter("v_right_off_center");
		m_t_off_left_off_right = m_p_minmax_shader1->t_Find_Parameter("v_off_left_off_right");
		m_t_kernel_size_side_size1 =
			m_p_minmax_shader1->t_Find_IntParameter("v_kernel_size_side_size");
		if(!m_t_right_off_center.b_Valid() || !m_t_off_left_off_right.b_Valid() ||
		   !m_t_kernel_size_side_size1.b_Valid())
			return false;
	}
	if(!m_p_minmax_shader2) {
		const TShaderInfo t_minmax_pass2(
			"hifi_minmax2_fs", TShaderInfo::proc_Fragment,
			"uniform sampler2D n_min_tex, n_min_w_tex;\n"
			"uniform sampler2D n_max_tex, n_max_w_tex;\n"
			"uniform vec4 v_up_off_center, v_off_top_off_bottom;\n"
			"uniform ivec2 v_kernel_size_side_size;\n"
			"\n"
			"void main()\n"
			"{\n"
			"    vec2 v_texcoord = gl_TexCoord[0].xy + v_up_off_center.zw;\n"
			"    vec4 v_min = texture2D(n_min_w_tex, v_texcoord);\n"
			"    vec4 v_max = texture2D(n_max_w_tex, v_texcoord);\n"
			"    for(int i = 1; i < v_kernel_size_side_size.x; ++ i) {\n"
			"        v_texcoord += v_up_off_center.xy;\n"
			"        vec4 v_min_sample = texture2D(n_min_w_tex, v_texcoord);\n"
			"        vec4 v_max_sample = texture2D(n_max_w_tex, v_texcoord);\n"
			"        v_min = min(v_min, v_min_sample);\n"
			"        v_max = max(v_max, v_max_sample);\n"
			"    }\n"
			"    vec4 v_texcoord2 = gl_TexCoord[0].xyxy + v_off_top_off_bottom;\n"
			"    for(int i = 0; i < v_kernel_size_side_size.y; ++ i) {\n"
			"        v_texcoord2 += v_up_off_center.xyxy;\n"
			"        vec4 v_min_sample_l = texture2D(n_min_tex, v_texcoord2.xy);\n"
			"        vec4 v_max_sample_l = texture2D(n_max_tex, v_texcoord2.xy);\n"
			"        v_min = min(v_min, v_min_sample_l);\n"
			"        v_max = max(v_max, v_max_sample_l);\n"
			"        vec4 v_min_sample_r = texture2D(n_min_tex, v_texcoord2.zw);\n"
			"        vec4 v_max_sample_r = texture2D(n_max_tex, v_texcoord2.zw);\n"
			"        v_min = min(v_min, v_min_sample_r);\n"
			"        v_max = max(v_max, v_max_sample_r);\n"
			"    }\n"
			"    gl_FragData[0] = v_min;\n"
			"    gl_FragData[1] = v_max;\n"
			"}\n", "n_min_tex|n_min_w_tex|n_max_tex|n_max_w_tex");
		// min-max shader for pass 2

		if(!(m_p_minmax_shader2 = CGLImageProc::p_CompileLinkReport(p_state,
		   0, &t_minmax_pass2, false)))
			return false;
		m_t_up_off_center = m_p_minmax_shader2->t_Find_Parameter("v_up_off_center");
		m_t_off_top_off_bottom = m_p_minmax_shader2->t_Find_Parameter("v_off_top_off_bottom");
		m_t_kernel_size_side_size2 =
			m_p_minmax_shader2->t_Find_IntParameter("v_kernel_size_side_size");
		if(!m_t_up_off_center.b_Valid() || !m_t_off_top_off_bottom.b_Valid() ||
		   !m_t_kernel_size_side_size1.b_Valid())
			return false;
	}
	// make sure both shaders are ready

	float f_pixel_width = 1.0f / p_src->n_Width();
	if(!m_t_kernel_size_side_size1.SetValue(n_kernel_size, n_kernel_size / 2) ||
	   !m_t_right_off_center.SetValue(f_pixel_width, 0, f_pixel_width * -n_kernel_size / 2, 0) ||
	   !m_t_off_left_off_right.SetValue(f_pixel_width * -n_kernel_size, 0,
	   f_pixel_width * (n_kernel_size / 2 - 1), 0))
		return false;
	// set pass1 parameters

	float f_pixel_height = 1.0f / p_src->n_Height();
	if(!m_t_kernel_size_side_size2.SetValue(n_kernel_size, n_kernel_size / 2) ||
	   !m_t_up_off_center.SetValue(0, f_pixel_height, 0, f_pixel_height * -n_kernel_size / 2) ||
	   !m_t_off_top_off_bottom.SetValue(0, f_pixel_height * -n_kernel_size,
	   0, f_pixel_height * (n_kernel_size / 2 - 1)))
		return false;
	// set pass2 parameters

	CGLFrameBuffer_FBO *p_fbo2, *p_fbo4;
	if(!(p_fbo2 = CGLImageProc::p_GetRenderBuffer(p_src->n_Width(), p_src->n_Height(), 2,
	   true, p_src->n_Pixel_Format(), false, false, 0, false, false, 0)) ||
	   !(p_fbo4 = CGLImageProc::p_GetRenderBuffer(p_src->n_Width(), p_src->n_Height(), 4,
	   true, p_src->n_Pixel_Format(), false, false, 0, false, false, 0)))
		return false;
	// get FBO's

	p_state->DisableBlend();
	p_state->DisableAlphaTest();
	p_state->DisableCullFace();
	// disable things shaders do not control

	{
		if(!p_fbo4->Bind() ||
		   !p_fbo4->BindColorTexture_2D(*p_temp_tex[0], GL_TEXTURE_2D, 0) ||
		   !p_fbo4->BindColorTexture_2D(*p_temp_tex[1], GL_TEXTURE_2D, 1) ||
		   !p_fbo4->BindColorTexture_2D(*p_temp_tex[2], GL_TEXTURE_2D, 2) ||
		   !p_fbo4->BindColorTexture_2D(*p_temp_tex[3], GL_TEXTURE_2D, 3))
			return false;
		// bind FBO & textures

		if(glGetError() != GL_NO_ERROR)
			return false;

		glViewport(0, 0, p_src->n_Width(), p_src->n_Height());
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		// viewport, matrices & clear

		p_state->ActiveTextureUnit(0);
		p_src->Bind_Enable(p_state);

		if(!m_p_minmax_shader1->Bind(p_state))
			return false;
		// bind shader

		if(glGetError() != GL_NO_ERROR)
			return false;

		glBegin(GL_QUADS);
		glTexCoord2f(0, 0);
		glVertex2f(-1, -1);
		glTexCoord2f(0, 1);
		glVertex2f(-1, 1);
		glTexCoord2f(1, 1);
		glVertex2f(1, 1);
		glTexCoord2f(1, 0);
		glVertex2f(1, -1);
		glEnd();
		// draw fullscreen quad

		if(glGetError() != GL_NO_ERROR)
			return false;

		if(!p_fbo4->BindColorTexture_2D(0, GL_TEXTURE_2D, 0) ||
		   !p_fbo4->BindColorTexture_2D(0, GL_TEXTURE_2D, 1) ||
		   !p_fbo4->BindColorTexture_2D(0, GL_TEXTURE_2D, 2) ||
		   !p_fbo4->BindColorTexture_2D(0, GL_TEXTURE_2D, 3) ||
		   !p_fbo4->Release())
			return false;
		// release textures & FBO

		if(glGetError() != GL_NO_ERROR)
			return false;
	}
	// first pass

	{
		if(!p_fbo2->Bind() ||
		   !p_fbo2->BindColorTexture_2D(*p_dest_min, GL_TEXTURE_2D, 0) ||
		   !p_fbo2->BindColorTexture_2D(*p_dest_max, GL_TEXTURE_2D, 1))
			return false;
		// bind FBO & textures

		if(glGetError() != GL_NO_ERROR)
			return false;

		for(int i = 0; i < 4; ++ i) {
			p_state->ActiveTextureUnit(i);
			p_temp_tex[i]->Bind_Enable(p_state);
		}
		// bind outputs from the first pass

		if(!m_p_minmax_shader2->Bind(p_state))
			return false;
		// bind shader

		if(glGetError() != GL_NO_ERROR)
			return false;

		glBegin(GL_QUADS);
		glTexCoord2f(0, 0);
		glVertex2f(-1, -1);
		glTexCoord2f(0, 1);
		glVertex2f(-1, 1);
		glTexCoord2f(1, 1);
		glVertex2f(1, 1);
		glTexCoord2f(1, 0);
		glVertex2f(1, -1);
		glEnd();
		// draw fullscreen quad

		if(glGetError() != GL_NO_ERROR)
			return false;

		if(!p_fbo2->BindColorTexture_2D(0, GL_TEXTURE_2D, 0) ||
		   !p_fbo2->BindColorTexture_2D(0, GL_TEXTURE_2D, 1) ||
		   !p_fbo2->Release())
			return false;
		// release textures & FBO

		for(int i = 1; i < 4; ++ i) {
			p_state->ActiveTextureUnit(i);
			p_state->DisableTexture2D();
		}
		p_state->ActiveTextureUnit(0);
	}
	// second pass

	return glGetError() == GL_NO_ERROR;
}

/*
 *								=== ~CGLMinMaxFilter ===
 */
