//-----------------------------------------------------------------------------------------------
// Renderer_OpenGL3.cpp
//
#include "GypsyPrecompiledHeader.hpp"
#include "Core/PlatformDefines.hpp"
#if defined( GYPSY_ENABLE_RENDERER )

//-----------------------------------------------------------------------------------------------
#include "Renderer/Renderer_OpenGL3.hpp"


/////////////////////////////////////////////////////////////////////////////////////////////////
// Texture_OpenGL3
//

//-----------------------------------------------------------------------------------------------
Texture_OpenGL3::Texture_OpenGL3()
	: m_openglTextureID( 0 )
{
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Texture_OpenGL3::PopulateFromFile( const char* imageFilePath, TextureFilterMode minFilter, TextureFilterMode magFilter, TextureEdgeMode edgeMode )
{
	UNUSED( imageFilePath );
	UNUSED( minFilter );
	UNUSED( magFilter );
	UNUSED( edgeMode );
	FATAL_ERROR( Stringf( "Texture_OpenGL3::PopulateFromFile() not implemented - should be calling Texture_OpenGL3_Win32 (or other) version instead!" ) );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Texture_OpenGL3::PopulateFromData( unsigned char* imageData, const IntVector2& dimensions, int numChannels, TextureFilterMode minFilter, TextureFilterMode magFilter, TextureEdgeMode edgeMode )
{
//	if( !imageData )
//		return;

	Renderer_OpenGL3* openglRenderer = dynamic_cast< Renderer_OpenGL3* >( theRenderer );
	if( !openglRenderer )
		return;

	m_dimensions = dimensions;
	m_numChannels = numChannels;

	// Enable texturing
	glEnable( GL_TEXTURE_2D );

	// Tell OpenGL that our pixel data is single-byte aligned
	glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );

	// Ask OpenGL for an unused texName (ID number) to use for this texture
	glGenTextures( 1, (GLuint*) &m_openglTextureID );

	// Tell OpenGL to bind (set) this as the currently active texture
	glBindTexture( GL_TEXTURE_2D, m_openglTextureID );

	// Set texture clamp vs. wrap (repeat)
	GLint openglEdgeMode = openglRenderer->GetOpenGLEdgeEnumForEdgeMode( edgeMode );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, openglEdgeMode ); // GL_CLAMP_TO_EDGE or GL_REPEAT
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, openglEdgeMode ); // GL_CLAMP_TO_EDGE or GL_REPEAT

	// Set magnification (texel > pixel) and minification (texel < pixel) filters
	GLint openglMinFilter = openglRenderer->GetOpenGLFilterEnumForFilterMode( minFilter );
	GLint openglMagFilter = openglRenderer->GetOpenGLFilterEnumForFilterMode( magFilter );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, openglMinFilter ); // one of: GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, openglMagFilter ); // one of: GL_NEAREST, GL_LINEAR

	GLenum bufferFormat = GL_RGBA; // the format our source texel data is currently in; any of: GL_RGB, GL_RGBA, GL_LUMINANCE, GL_LUMINANCE_ALPHA, ...
	GLenum texelComponentType = GL_UNSIGNED_BYTE; // data format of each color component of source (and destination) texels
	if( m_numChannels == 3 )
	{
		bufferFormat = GL_RGB;
	}
	else if( m_numChannels == 1 )
	{
		bufferFormat = GL_DEPTH_COMPONENT; // Temp-hacky way of specifying a depth texture (e.g. for FBO attachment); need to differentiate between this and a luminance-only 1-channel greyscale texture (if we decide to support that)
		texelComponentType = GL_UNSIGNED_INT;
	}

	GLenum internalFormat = bufferFormat; // the format we want the texture to be on the card; allows us to translate into a different texture format as we upload to OpenGL

	glTexImage2D(			// Upload this pixel data to our new OpenGL texture
		GL_TEXTURE_2D,		// Creating this as a 2d texture
		0,					// Which mipmap level to use as the "root" (0 = the highest-quality, full-res image), if mipmaps are enabled
		internalFormat,		// Type of texel format we want OpenGL to use for this texture internally on the video card
		m_dimensions.x,			// Texel-width of image; for maximum compatibility, use 2^N + 2^B, where N is some integer in the range [3,10], and B is the border thickness [0,1]
		m_dimensions.y,			// Texel-height of image; for maximum compatibility, use 2^M + 2^B, where M is some integer in the range [3,10], and B is the border thickness [0,1]
		0,					// Border size, in texels (must be 0 or 1)
		bufferFormat,		// Texel format describing the composition of the texel data in buffer
		texelComponentType,	// Texel color components are unsigned bytes (one byte per color/alpha channel)
		imageData );		// Location of the actual pixel data bytes/buffer
}


/////////////////////////////////////////////////////////////////////////////////////////////////
// Renderer_OpenGL3
//

//-----------------------------------------------------------------------------------------------
Renderer_OpenGL3::Renderer_OpenGL3()
{
}


//-----------------------------------------------------------------------------------------------
Renderer_OpenGL3::~Renderer_OpenGL3()
{
}


//-----------------------------------------------------------------------------------------------
VIRTUAL Texture* Renderer_OpenGL3::CreateTextureFromData( const char* internalTextureName, unsigned char* imageData, const IntVector2& dimensions, int numChannels, TextureFilterMode minFilter, TextureFilterMode magFilter, TextureEdgeMode edgeMode )
{
	HashedCaseInsensitiveString hashedTextureName( internalTextureName );
	std::map< HashedCaseInsensitiveString, Texture* >::iterator found = m_textureRegistry.find( hashedTextureName );
	if( found != m_textureRegistry.end() )
	{
		FATAL_ERROR( Stringf( "Renderer_OpenGL3::CreateTextureFromData() - cannot create internal texture \"%s\", because a texture of that name already exists.", internalTextureName ) );
	}

	Texture_OpenGL3* newTexture = new Texture_OpenGL3();
	newTexture->PopulateFromData( imageData, dimensions, numChannels, minFilter, magFilter, edgeMode );
	m_textureRegistry[ hashedTextureName ] = newTexture;
	return newTexture;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL FramebufferObject* Renderer_OpenGL3::CreateFramebufferObject( const IntVector2& renderTargetTexelSize, TextureFilterMode minFilter, TextureFilterMode magFilter )
{
	FramebufferObject* newFBO = new FramebufferObject( renderTargetTexelSize, minFilter, magFilter );

	GLuint fboID;
	glGenFramebuffers( 1, &fboID );
	newFBO->m_fboID = fboID;

	Texture_OpenGL3* glFboColorTexture = dynamic_cast< Texture_OpenGL3* >( newFBO->m_colorTexture );
	Texture_OpenGL3* glFboDepthTexture = dynamic_cast< Texture_OpenGL3* >( newFBO->m_depthTexture );

	GLuint fboColorTextureID = glFboColorTexture->m_openglTextureID;
	GLuint fboDepthTextureID = glFboDepthTexture->m_openglTextureID;

	glBindFramebuffer( GL_FRAMEBUFFER, fboID );
	glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboColorTextureID, 0 );
	glFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, fboDepthTextureID, 0 );
	glBindFramebuffer( GL_FRAMEBUFFER, 0 );

	return newFBO;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL const char* Renderer_OpenGL3::GetAPIDescription() const
{
	return (const char*) glGetString( GL_VERSION );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::FlushAndFinish()
{
	glFlush();
	glFinish(); 
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::ClearScreen( const Rgba& clearColor ) const
{
	float r, g, b, a;
	clearColor.GetRGBAFloats( r, g, b, a );
	glClearColor( r, g, b, a );
	glClear( GL_COLOR_BUFFER_BIT );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::ClearDepth( const float clearDepth ) const
{
	glClearDepth( clearDepth );
	glClear( GL_DEPTH_BUFFER_BIT );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::ClearScreenAndDepth( const Rgba& clearColor, const float clearDepth ) const
{
	float r, g, b, a;
	clearColor.GetRGBAFloats( r, g, b, a );
	glClearColor( r, g, b, a );
	glClearDepth( clearDepth );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}


//---------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::SetScreenVirtualOrtho( const Vector2& virtualScreenCoordsBottomLeft, const Vector2& virtualScreenCoordsTopRight )
{
//	glMatrixMode( GL_PROJECTION );
//	glLoadIdentity();
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	glOrtho( virtualScreenCoordsBottomLeft.x, virtualScreenCoordsTopRight.x, virtualScreenCoordsBottomLeft.y, virtualScreenCoordsTopRight.y, 0.f, 1.f );
// 	glLoadIdentity();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::PushMatrix()
{
	glPushMatrix();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::PopMatrix()
{
	glPopMatrix();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::UseShaderProgram( const ShaderProgram* shaderProgram )
{
// 	if( shaderProgram == m_currentShaderProgram )
// 		return;

	m_currentShaderProgram = shaderProgram;

	if( m_currentShaderProgram )
	{
		float timeNow = (float) GetCurrentTimeSeconds();
		glUseProgram( m_currentShaderProgram->m_programID );
		Matrix44 objectToWorldMatrix; // TODO: Move this into Renderer when we refactor away from old OpenGL stack/transforms
		Matrix44 worldToClipMatrix; // TODO: Move this into Renderer when we refactor away from old OpenGL stack/transforms
		glGetFloatv( GL_MODELVIEW_MATRIX, worldToClipMatrix.GetAsFloatArray() );
		glUniformMatrix4fv( m_currentShaderProgram->m_uniformLocation_objectToWorldMatrix, 1, false, objectToWorldMatrix.GetAsFloatArray() );
		glUniformMatrix4fv( m_currentShaderProgram->m_uniformLocation_worldToClipMatrix, 1, false, worldToClipMatrix.GetAsFloatArray() );
		glUniform1i( m_currentShaderProgram->m_uniformLocation_diffuseTexture, 0 );
		glUniform1i( m_currentShaderProgram->m_uniformLocation_noiseTexture, 1 );
		glUniform1f( m_currentShaderProgram->m_uniformLocation_time, timeNow );
		glUniform1f( m_currentShaderProgram->m_uniformLocation_edgeThreshold, 0.6f );
		glUniform1f( m_currentShaderProgram->m_uniformLocation_softness, 0.2f );
	}
	else
		glUseProgram( 0 );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::UseFramebuffer( const FramebufferObject* framebufferObject )
{
	if( framebufferObject == m_currentFramebufferObject )
		return;

	m_currentFramebufferObject = framebufferObject;

	if( m_currentFramebufferObject )
		glBindFramebuffer( GL_FRAMEBUFFER, m_currentFramebufferObject->m_fboID );
	else
		glBindFramebuffer( GL_FRAMEBUFFER, 0 );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::SetLineThickness( float lineThickness )
{
	if( lineThickness == m_currentLineThickness )
		return;

	m_currentLineThickness = lineThickness;
	glLineWidth( m_currentLineThickness );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::BindTextureToSampler( const Texture* texture, int samplerIndex )
{
	const Texture* textureToUse = texture ? texture : m_plainWhiteTexture;
	const Texture_OpenGL3* openglTexture = reinterpret_cast< const Texture_OpenGL3* >( textureToUse );
	int textureID = openglTexture ? openglTexture->m_openglTextureID : 0;
	glActiveTexture( GL_TEXTURE0 + samplerIndex );
	glBindTexture( GL_TEXTURE_2D, textureID );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::Draw2dLineSegment( const Vector2& startPos, const Vector2& endPos, const Rgba& startColor, const Rgba& endColor )
{
	if( m_currentShaderProgram )
	{
		glBegin( GL_LINES );
		{
			// Constant vertex attributes
			glVertexAttrib2fv( VERTEX_ATTRIB_TEXCOORDS, Vector2::ZERO.GetAsFloatArray() );

			// Dynamic vertex attributes
			glVertexAttrib4Nubv( VERTEX_ATTRIB_COLOR, startColor.GetAsByteArray() );
			glVertexAttrib2fv( VERTEX_ATTRIB_POSITION, startPos.GetAsFloatArray() );
			
			glVertexAttrib4Nubv( VERTEX_ATTRIB_COLOR, endColor.GetAsByteArray() );
			glVertexAttrib2fv( VERTEX_ATTRIB_POSITION, endPos.GetAsFloatArray() );
		}
		glEnd();
	}
	else
	{
		glBegin( GL_LINES );
		{
			glColor4ubv( startColor.GetAsByteArray() );
			glVertex2f( startPos.x, startPos.y );

			glColor4ubv( endColor.GetAsByteArray() );
			glVertex2f( endPos.x, endPos.y );
		}
		glEnd();
	}
}


//---------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::Draw2dDisc( const Vector2& center, float radius, float numSides, const Rgba& color )
{
	float radiansPerSide = fTWO_PI / numSides;

	if( m_currentShaderProgram )
	{
		glBegin( GL_TRIANGLE_FAN );
		{
			// Constant vertex attributes
			glVertexAttrib4Nubv( VERTEX_ATTRIB_COLOR, color.GetAsByteArray() );
			glVertexAttrib2f( VERTEX_ATTRIB_TEXCOORDS, 0.f, 0.f );

			// Dynamic vertex attributes
			glVertexAttrib2f( VERTEX_ATTRIB_POSITION, center.x, center.y );
			for( float radians = 0; radians < fTWO_PI; radians += radiansPerSide )
			{
				Vector2 displacement;
				displacement.SetLengthAndYawRadians( radius, radians );
				Vector2 rimPosition = center + displacement;
				glVertexAttrib2f( VERTEX_ATTRIB_POSITION, rimPosition.x, rimPosition.y );
			}
			glVertexAttrib2f( VERTEX_ATTRIB_POSITION, center.x + radius, center.y );
		}
		glEnd();
	}
	else
	{
		glDisable( GL_TEXTURE_2D );
		glColor4ubv( color.GetAsByteArray() );
		glBegin( GL_TRIANGLE_FAN );
		{
			glVertex2f( center.x, center.y );
			for( float radians = 0; radians < fTWO_PI; radians += radiansPerSide )
			{
				Vector2 displacement;
				displacement.SetLengthAndYawRadians( radius, radians );
				Vector2 rimPosition = center + displacement;
				glVertex2f( rimPosition.x, rimPosition.y );
			}
			glVertex2f( center.x + radius, center.y );
		}
		glEnd();
	}
}


//---------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::Draw2dDiscOutline( const Vector2& center, float radius, float numSides, const Rgba& color )
{
	float radiansPerSide = fTWO_PI / numSides;

	glDisable( GL_TEXTURE_2D );
	glColor4ubv( color.GetAsByteArray() );
	glBegin( GL_LINE_LOOP );
	{
		for( float radians = 0; radians < fTWO_PI; radians += radiansPerSide )
		{
			Vector2 displacement;
			displacement.SetLengthAndYawRadians( radius, radians );
			Vector2 rimPosition = center + displacement;
			glVertex2f( rimPosition.x, rimPosition.y );
		}
	}
	glEnd();
}


//---------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::Draw2dAABB( const Vector2& mins, const Vector2& maxs, const Rgba& color )
{
	if( m_currentShaderProgram )
	{
		glBegin( GL_QUADS );
		{
			// Constant vertex attributes
			glVertexAttrib4Nubv( VERTEX_ATTRIB_COLOR, color.GetAsByteArray() );

			// Dynamic vertex attributes
			glVertexAttrib2f( VERTEX_ATTRIB_TEXCOORDS, 0.f, 0.f );
			glVertexAttrib2f( VERTEX_ATTRIB_POSITION, mins.x, mins.y );

			glVertexAttrib2f( VERTEX_ATTRIB_TEXCOORDS, 1.f, 0.f );
			glVertexAttrib2f( VERTEX_ATTRIB_POSITION, maxs.x, mins.y );

			glVertexAttrib2f( VERTEX_ATTRIB_TEXCOORDS, 1.f, 1.f );
			glVertexAttrib2f( VERTEX_ATTRIB_POSITION, maxs.x, maxs.y );

			glVertexAttrib2f( VERTEX_ATTRIB_TEXCOORDS, 0.f, 1.f );
			glVertexAttrib2f( VERTEX_ATTRIB_POSITION, mins.x, maxs.y );
		}
		glEnd();
	}
	else
	{
		glDisable( GL_TEXTURE_2D );
		glColor4ubv( color.GetAsByteArray() );
		glBegin( GL_QUADS );
		{
			glVertex2f( mins.x, mins.y );
			glVertex2f( maxs.x, mins.y );
			glVertex2f( maxs.x, maxs.y );
			glVertex2f( mins.x, maxs.y );
		}
		glEnd();
	}
}


//---------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::Draw2dAABB( const AABB2& bounds, const Rgba& color )
{
	Draw2dAABB( bounds.mins, bounds.maxs, color );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::Draw2dTexturedAABB( const AABB2& bounds, const Vector2& texCoordsAtMins, const Vector2& texCoordsAtMaxs, const Rgba& tint, const Texture& texture )
{
	const Texture_OpenGL3* openglTexture = reinterpret_cast< const Texture_OpenGL3* >( &texture );
	if( !openglTexture )
		openglTexture = reinterpret_cast< const Texture_OpenGL3* >( m_missingTextureTexture );

	if( !openglTexture )
		return;

	glEnable( GL_TEXTURE_2D );
	glActiveTexture( GL_TEXTURE0 );
	glBindTexture( GL_TEXTURE_2D, openglTexture->m_openglTextureID );

	if( m_currentShaderProgram )
	{
		const Vector2& mins = bounds.mins;
		const Vector2& maxs = bounds.maxs;
		// Constant vertex attributes
		glVertexAttrib4Nubv( VERTEX_ATTRIB_COLOR, tint.GetAsByteArray() );

		glBegin( GL_QUADS );
		{
			// Dynamic vertex attributes
			glVertexAttrib2f( VERTEX_ATTRIB_TEXCOORDS, texCoordsAtMins.x, texCoordsAtMins.y );
			glVertexAttrib2f( VERTEX_ATTRIB_POSITION, mins.x, mins.y );

			glVertexAttrib2f( VERTEX_ATTRIB_TEXCOORDS, texCoordsAtMaxs.x, texCoordsAtMins.y );
			glVertexAttrib2f( VERTEX_ATTRIB_POSITION, maxs.x, mins.y );

			glVertexAttrib2f( VERTEX_ATTRIB_TEXCOORDS, texCoordsAtMaxs.x, texCoordsAtMaxs.y );
			glVertexAttrib2f( VERTEX_ATTRIB_POSITION, maxs.x, maxs.y );

			glVertexAttrib2f( VERTEX_ATTRIB_TEXCOORDS, texCoordsAtMins.x, texCoordsAtMaxs.y );
			glVertexAttrib2f( VERTEX_ATTRIB_POSITION, mins.x, maxs.y );
		}
		glEnd();
	}
	else
	{
		glColor4ubv( tint.GetAsByteArray() );
		glBegin( GL_QUADS );
		{
			glTexCoord2f( texCoordsAtMins.x, texCoordsAtMins.y );
			glVertex2f( bounds.mins.x, bounds.mins.y );

			glTexCoord2f( texCoordsAtMaxs.x, texCoordsAtMins.y );
			glVertex2f( bounds.maxs.x, bounds.mins.y );

			glTexCoord2f( texCoordsAtMaxs.x, texCoordsAtMaxs.y );
			glVertex2f( bounds.maxs.x, bounds.maxs.y );

			glTexCoord2f( texCoordsAtMins.x, texCoordsAtMaxs.y );
			glVertex2f( bounds.mins.x, bounds.maxs.y );
		}
		glEnd();
	}
}


//---------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::Draw2dConvexPoly( const Vector2& anchorPos, const Vector2* ccwVertexesRelativeToAnchor, int numVertexes, const Rgba& color )
{
	glDisable( GL_TEXTURE_2D );
	glColor4ubv( color.GetAsByteArray() );
	glBegin( GL_POLYGON );
	{
		for( int vertIndex = 0; vertIndex < numVertexes; ++ vertIndex )
		{
			const Vector2& pos = ccwVertexesRelativeToAnchor[ vertIndex ];
			glVertex2f( pos.x + anchorPos.x, pos.y + anchorPos.y );
		}
	}
	glEnd();
}


//---------------------------------------------------------------------------
// Draws text at a given position, which is the text mins coordinates.
// Text is assumed to be flowing in the +X direction with letter tops
//	pointing in the +Y direction.
//
VIRTUAL void Renderer_OpenGL3::Draw2dTextStringAtPosition( const char* latinChars, const Vector2& textMins, float cellHeight, const Rgba& tint, BitmapFont* font )
{
	if( font == nullptr )
		font = m_defaultFont;
	FATAL_GUARANTEE( font != nullptr, "Draw2dTextStringAtPosition() - no font specified, and no default font" );

	Texture* texture = font->m_glyphsTexture;
	Texture_OpenGL3* openglTexture = reinterpret_cast< Texture_OpenGL3* >( texture );

	glEnable( GL_TEXTURE_2D );
	glActiveTexture( GL_TEXTURE0 );
	glBindTexture( GL_TEXTURE_2D, openglTexture->m_openglTextureID );

	if( m_currentShaderProgram )
	{
		float stringWidth = font->CalcTextStringWidth( latinChars, cellHeight );
		float invStringWidth = stringWidth == 0.f ? 1.f : (1.f / stringWidth);
		glBegin( GL_QUADS );
		{
			// Constant vertex attributes
			glVertexAttrib4Nubv( VERTEX_ATTRIB_COLOR, tint.GetAsByteArray() );

			Vector2 glyphMins = textMins;
			Vector2 glyphMaxs( textMins.x, textMins.y + cellHeight );
			float charIndex = 0.f;
			for( const char* scan = latinChars; *scan != '\0'; ++ scan )
			{
				unsigned char glyphUCS = static_cast< unsigned char >( *scan );
				GlyphData& glyphData = font->m_glyphsData[ glyphUCS ];
				float glyphPreAdvance = (cellHeight * glyphData.m_cellHeightFractionBeforeGlyph);
				float glyphPostAdvance = (cellHeight * glyphData.m_cellHeightFractionAfterGlyph);
				float glyphWidth = (cellHeight * glyphData.m_cellHeightFractionDuringGlyph);

				glyphMins.x += glyphPreAdvance;
				glyphMaxs.x = glyphMins.x + glyphWidth;

				float stringHorizontalOffsetAtLeftEdge = glyphMins.x - textMins.x;
				float stringHorizontalOffsetAtRightEdge = glyphMaxs.x - textMins.x;
				float stringFractionAtLeftEdge = stringHorizontalOffsetAtLeftEdge * invStringWidth;
				float stringFractionAtRightEdge = stringHorizontalOffsetAtRightEdge * invStringWidth;

				glVertexAttrib1f( VERTEX_ATTRIB_STRINGFRACTION, stringFractionAtLeftEdge );
				glVertexAttrib1f( VERTEX_ATTRIB_CHARINDEX, charIndex );
				glVertexAttrib1f( VERTEX_ATTRIB_CELLHEIGHT, 0.f );
				glVertexAttrib2f( VERTEX_ATTRIB_TEXCOORDS, glyphData.m_texCoordMins.x, glyphData.m_texCoordMaxs.y );
				glVertexAttrib2f( VERTEX_ATTRIB_POSITION, glyphMins.x, glyphMins.y );

				glVertexAttrib1f( VERTEX_ATTRIB_STRINGFRACTION, stringFractionAtRightEdge );
				glVertexAttrib1f( VERTEX_ATTRIB_CHARINDEX, charIndex + 1.f );
				glVertexAttrib1f( VERTEX_ATTRIB_CELLHEIGHT, 0.f );
				glVertexAttrib2f( VERTEX_ATTRIB_TEXCOORDS, glyphData.m_texCoordMaxs.x, glyphData.m_texCoordMaxs.y );
				glVertexAttrib2f( VERTEX_ATTRIB_POSITION, glyphMaxs.x, glyphMins.y );

				glVertexAttrib1f( VERTEX_ATTRIB_STRINGFRACTION, stringFractionAtRightEdge );
				glVertexAttrib1f( VERTEX_ATTRIB_CHARINDEX, charIndex + 1.f );
				glVertexAttrib1f( VERTEX_ATTRIB_CELLHEIGHT, 1.f );
				glVertexAttrib2f( VERTEX_ATTRIB_TEXCOORDS, glyphData.m_texCoordMaxs.x, glyphData.m_texCoordMins.y );
				glVertexAttrib2f( VERTEX_ATTRIB_POSITION, glyphMaxs.x, glyphMaxs.y );

				glVertexAttrib1f( VERTEX_ATTRIB_STRINGFRACTION, stringFractionAtLeftEdge );
				glVertexAttrib1f( VERTEX_ATTRIB_CHARINDEX, charIndex );
				glVertexAttrib1f( VERTEX_ATTRIB_CELLHEIGHT, 1.f );
				glVertexAttrib2f( VERTEX_ATTRIB_TEXCOORDS, glyphData.m_texCoordMins.x, glyphData.m_texCoordMins.y );
				glVertexAttrib2f( VERTEX_ATTRIB_POSITION, glyphMins.x, glyphMaxs.y );

				glyphMins.x = glyphMaxs.x;
				glyphMins.x += glyphPostAdvance;
				charIndex += 1.f;
			}
		}
		glEnd();
	}
	else
	{
		glColor4ubv( tint.GetAsByteArray() );
		glBegin( GL_QUADS );
		{
			Vector2 glyphMins = textMins;
			Vector2 glyphMaxs( textMins.x, textMins.y + cellHeight );
			for( const char* scan = latinChars; *scan != '\0'; ++ scan )
			{
				unsigned char glyphUCS = static_cast< unsigned char >( *scan );
				GlyphData& glyphData = font->m_glyphsData[ glyphUCS ];
				glyphMins.x += (cellHeight * glyphData.m_cellHeightFractionBeforeGlyph);
				glyphMaxs.x = glyphMins.x + (cellHeight * glyphData.m_cellHeightFractionDuringGlyph);

				glTexCoord2f( glyphData.m_texCoordMins.x, glyphData.m_texCoordMaxs.y );
				glVertex2f( glyphMins.x, glyphMins.y );

				glTexCoord2f( glyphData.m_texCoordMaxs.x, glyphData.m_texCoordMaxs.y );
				glVertex2f( glyphMaxs.x, glyphMins.y );

				glTexCoord2f( glyphData.m_texCoordMaxs.x, glyphData.m_texCoordMins.y );
				glVertex2f( glyphMaxs.x, glyphMaxs.y );

				glTexCoord2f( glyphData.m_texCoordMins.x, glyphData.m_texCoordMins.y );
				glVertex2f( glyphMins.x, glyphMaxs.y );

				glyphMins.x = glyphMaxs.x;
				glyphMins.x += (cellHeight * glyphData.m_cellHeightFractionAfterGlyph);
			}
		}
		glEnd();
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::DetermineCapabilityLevel()
{
	int majorVersionNumber = 1;
	int minorVersionNumber = 0;
	const char* versionString = (const char*) glGetString( GL_VERSION );
	int scanIndex = 0;
	while( versionString[ scanIndex ] != '\0' )
	{
		char character = versionString[ scanIndex ];
		if( character == '.' )
		{
			std::string majorVersionString( versionString, 0, scanIndex );
			majorVersionNumber = atoi( majorVersionString.c_str() );
			break;
		}

		++ scanIndex;
	}

	++ scanIndex;
	int minorVersionStartIndex = scanIndex;
	while( versionString[ scanIndex ] != '\0' )
	{
		char character = versionString[ scanIndex ];
		if( character < '0' || character > '9' )
		{
			std::string minorVersionString( versionString, minorVersionStartIndex, scanIndex - minorVersionStartIndex );
			minorVersionNumber = atoi( minorVersionString.c_str() );
			break;
		}

		++ scanIndex;
	}

	int majorMinorVersion = (100 * majorVersionNumber) + minorVersionNumber; // e.g. 430 for OpenGL 4.3
	if( majorMinorVersion >= 430 )
		m_capabilityLevel = RENDERER_CAPABILITY_OPENGL43;
	else if( majorMinorVersion >= 330 )
		m_capabilityLevel = RENDERER_CAPABILITY_OPENGL33;
	else if( majorMinorVersion >= 310 )
		m_capabilityLevel = RENDERER_CAPABILITY_OPENGL31;
	else if( majorMinorVersion >= 300 )
		m_capabilityLevel = RENDERER_CAPABILITY_OPENGL30;
	else if( majorMinorVersion >= 210 )
		m_capabilityLevel = RENDERER_CAPABILITY_OPENGL20;
	else if( majorMinorVersion >= 150 )
		m_capabilityLevel = RENDERER_CAPABILITY_OPENGL15;
	else
		m_capabilityLevel = RENDERER_CAPABILITY_OPENGL11;

	if( m_capabilityLevel > m_maxCapabilityLevel )
		m_capabilityLevel = m_maxCapabilityLevel;
}


//-----------------------------------------------------------------------------------------------
// Returns true if shader appears to be GLSL 130 or better, and shows no sign of
//	deprecated-style fixed vertex attributes (e.g. gl_Vertex, gl_Color, gl_Normal,
//	gl_MultiTexCoord0, gl_ModelViewProjectionMatrix, etc.
//
// NOTE: This is a really cheesy way to do this, and doesn't (for example) take into
//	account variable names, commented-out code, or #ifdef'd out code.  #Fixme??
//
VIRTUAL bool Renderer_OpenGL3::DoesShaderUseGenericVertexAttribs( const char* shaderSource )
{
	const char* scan = shaderSource;
	while( *scan != '\0' )
	{
		// Skip comment lines ("//" style)
		if( *scan == '/' && *(scan+1) == '/' )
		{
			while( *scan != '\0' && *scan != '\r' && *scan != '\n' )
				++ scan;

			while( *scan == '\r' || *scan == '\n' )
				++ scan;
		}

		// Skip comment blocks ("/* */" style)
		if( *scan == '/' && *(scan+1) == '*' )
		{
			while( *scan != '\0' && !(*scan == '*' && *(scan+1) == '/' ) )
				++ scan;

			if( *scan == '*' && *(scan+1) == '/' )
				scan += 2;
		}

		SkipWhitespace( scan );
		if( *scan == '#' )
		{
			std::string contiguousString = ParseContiguousStringAndAdvance( scan );
			if( contiguousString == "#version" )
			{
				SkipInlineWhitespace( scan );
				std::string versionString = ParseContiguousStringAndAdvance( scan );
				int versionNumber = atoi( versionString.c_str() );
				return( versionNumber > 120 );
			}
		}
		else
		{
			++ scan;
		}
	}

	return false;
}


//-----------------------------------------------------------------------------------------------
// Returns an ordered list of string names of shader language (GLSL or HLSL) versions supported
//	based on the current graphics capability level detected by the Renderer.  Specifically,
//	this is used in shader filenames to denote version (e.g. Basic_Vertex_140.glsl).
// 
VIRTUAL void Renderer_OpenGL3::GetLegalShaderVersionStringList( std::vector< std::string >& out_legalShaderVersionStrings )
{
	out_legalShaderVersionStrings.clear();

	if( m_capabilityLevel >= RENDERER_CAPABILITY_OPENGL43 )
	{
		out_legalShaderVersionStrings.push_back( "430" );
		out_legalShaderVersionStrings.push_back( "420" );
		out_legalShaderVersionStrings.push_back( "410" );
		out_legalShaderVersionStrings.push_back( "400" );
	}

	if( m_capabilityLevel >= RENDERER_CAPABILITY_OPENGL33 )
	{
		out_legalShaderVersionStrings.push_back( "330" );
		out_legalShaderVersionStrings.push_back( "150" );
	}

	if( m_capabilityLevel >= RENDERER_CAPABILITY_OPENGL31 )
	{
		out_legalShaderVersionStrings.push_back( "140" );
	}

	if( m_capabilityLevel >= RENDERER_CAPABILITY_OPENGL30 )
	{
		out_legalShaderVersionStrings.push_back( "130" );
		out_legalShaderVersionStrings.push_back( "120" );
	}

	if( m_capabilityLevel >= RENDERER_CAPABILITY_OPENGL20 )
	{
		out_legalShaderVersionStrings.push_back( "110" );
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL Shader* Renderer_OpenGL3::CompileShader( const char* shaderSource, ShaderType shaderType, const std::string& shaderName, const std::string& shaderRelativePath, const std::string& shaderVersionString )
{
	GLenum glShaderType = GetOpenGLShaderTypeEnumForShaderType( shaderType );
	ShaderID shaderID = glCreateShader( glShaderType );
	bool hasGenericVertexAttribs = DoesShaderUseGenericVertexAttribs( shaderSource );
	glShaderSource( shaderID, 1, &shaderSource, nullptr );
	glCompileShader( shaderID );

	GLint wasSuccessful;
	glGetShaderiv( shaderID, GL_COMPILE_STATUS, &wasSuccessful );
	if( wasSuccessful != GL_TRUE )
	{
		const char* shaderTypeName = GetNameForShaderType( shaderType );
		const char* shaderFileExtension = GetShaderFileExtension();
		std::string shaderFileName = Stringf( "%s_%s_%s.%s", shaderName.c_str(), shaderTypeName, shaderVersionString.c_str(), shaderFileExtension );
		ReportShaderError( shaderID, shaderSource, shaderType, shaderName, shaderFileName, shaderRelativePath );
	}

	Shader* newShader = new Shader( shaderID, shaderType, shaderName, shaderRelativePath, shaderVersionString, hasGenericVertexAttribs );
	return newShader;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL ShaderProgram* Renderer_OpenGL3::LinkShaderProgram( const Shader& vertexShader, const Shader& fragmentShader )
{
	ShaderProgramID programID = glCreateProgram();
	glAttachShader( programID, vertexShader.m_shaderID );
	glAttachShader( programID, fragmentShader.m_shaderID );

	glBindAttribLocation( programID, VERTEX_ATTRIB_POSITION,		"a_position" );
	glBindAttribLocation( programID, VERTEX_ATTRIB_COLOR,			"a_color" );
	glBindAttribLocation( programID, VERTEX_ATTRIB_TEXCOORDS,		"a_texCoords" );
	glBindAttribLocation( programID, VERTEX_ATTRIB_NORMAL,			"a_normal" );
	glBindAttribLocation( programID, VERTEX_ATTRIB_TANGENT,			"a_tangent" );
	glBindAttribLocation( programID, VERTEX_ATTRIB_BITANGENT,		"a_bitangent" );
	glBindAttribLocation( programID, VERTEX_ATTRIB_STRINGFRACTION,	"a_stringFraction" );
	glBindAttribLocation( programID, VERTEX_ATTRIB_CHARINDEX,		"a_charIndex" );
	glBindAttribLocation( programID, VERTEX_ATTRIB_CELLHEIGHT,		"a_cellHeight" );

	glLinkProgram( programID );

	GLint wasSuccessful;
	glGetProgramiv( programID, GL_LINK_STATUS, &wasSuccessful );
	if( wasSuccessful != GL_TRUE )
	{
		ReportShaderProgramError( programID, vertexShader, fragmentShader );
	}

	ShaderProgram* newShaderProgram = new ShaderProgram( programID, vertexShader, fragmentShader );

	newShaderProgram->m_uniformLocation_objectToWorldMatrix = glGetUniformLocation( programID, "u_objectToWorldTransform" );
	newShaderProgram->m_uniformLocation_worldToClipMatrix	= glGetUniformLocation( programID, "u_worldToClipTransform" );
	newShaderProgram->m_uniformLocation_diffuseTexture		= glGetUniformLocation( programID, "u_diffuseTexture" );
	newShaderProgram->m_uniformLocation_noiseTexture		= glGetUniformLocation( programID, "u_noiseTexture" );
	newShaderProgram->m_uniformLocation_time				= glGetUniformLocation( programID, "u_time" );
	newShaderProgram->m_uniformLocation_effectStrength		= glGetUniformLocation( programID, "u_effectStrength" );
	newShaderProgram->m_uniformLocation_effectScale			= glGetUniformLocation( programID, "u_effectScale" );
	newShaderProgram->m_uniformLocation_effectSpeed			= glGetUniformLocation( programID, "u_effectSpeed" );
	newShaderProgram->m_uniformLocation_edgeThreshold		= glGetUniformLocation( programID, "u_edgeThreshold" );
	newShaderProgram->m_uniformLocation_softness			= glGetUniformLocation( programID, "u_softness" );

	return newShaderProgram;
/*
	GLenum glShaderType = GetOpenGLShaderTypeEnumForShaderType( shaderType );
	ShaderID shaderID = glCreateShader( glShaderType );
	glShaderSource( shaderID, 1, &shaderSource, nullptr );
	glCompileShader( shaderID );

	GLint wasSuccessful;
	glGetShaderiv( shaderID, GL_COMPILE_STATUS, &wasSuccessful );
	if( wasSuccessful != GL_TRUE )
	{
		const char* shaderTypeName = GetNameForShaderType( shaderType );
		const char* shaderFileExtension = GetShaderFileExtension();
		std::string shaderFileName = Stringf( "%s_%s_%s.%s", shaderName.c_str(), shaderTypeName, shaderVersionString.c_str(), shaderFileExtension );
		ReportShaderError( shaderID, shaderSource, shaderType, shaderName, shaderFileName, shaderRelativePath );
	}

	Shader* newShader = new Shader( shaderID, shaderType, shaderName, shaderRelativePath );
	return newShader;*/
}


//-----------------------------------------------------------------------------------------------
GLenum Renderer_OpenGL3::GetOpenGLShaderTypeEnumForShaderType( ShaderType shaderType ) const
{
	switch( shaderType )
	{
		case SHADER_TYPE_VERTEX:	return GL_VERTEX_SHADER;
		case SHADER_TYPE_FRAGMENT:	return GL_FRAGMENT_SHADER;
		default:					return 0;
	}
}


//-----------------------------------------------------------------------------------------------
GLint Renderer_OpenGL3::GetOpenGLFilterEnumForFilterMode( TextureFilterMode filterMode ) const
{
	switch( filterMode )
	{
		case TEXTURE_FILTER_NEAREST:	return GL_NEAREST;
		case TEXTURE_FILTER_LINEAR:		return GL_LINEAR;
		default:						return 0;
	}
}


//-----------------------------------------------------------------------------------------------
GLint Renderer_OpenGL3::GetOpenGLEdgeEnumForEdgeMode( TextureEdgeMode edgeMode ) const
{
	switch( edgeMode )
	{
		case TEXTURE_EDGE_CLAMP:	return GL_CLAMP_TO_EDGE;
		case TEXTURE_EDGE_REPEAT:	return GL_REPEAT;
		default:					return 0;
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void __declspec( noreturn ) Renderer_OpenGL3::ReportShaderError( ShaderID shaderID, const char* shaderSource, ShaderType shaderType, const std::string& shaderName, const std::string& shaderFileName, const std::string& shaderRelativePath )
{
	const char* shaderTypeName = GetNameForShaderType( shaderType );
	GLint infoLogLength;
	glGetShaderiv( shaderID, GL_INFO_LOG_LENGTH, &infoLogLength );
	char* infoLogText = new char[ infoLogLength + 1 ];
	glGetShaderInfoLog( shaderID, infoLogLength, NULL, infoLogText );
	int errorLineNumber = -1;
	std::string infoLogString( infoLogText );
	delete[] infoLogText;

	std::string currentFolder = "C:/SOME_PATH_NOT_YET_IMPLEMENTED"; // theApplication->GetCurrentWorkingFolder();
	std::string shaderFileLocation = (currentFolder + "/") + shaderRelativePath;
	std::string errorTitle = Stringf( "%s shader compile error", shaderTypeName );
	std::string errorMessage;
	errorMessage += Stringf( "%s shader compile error in '%s':\n", shaderTypeName, shaderName.c_str() );
	errorMessage += "\n";
	errorMessage += Stringf( "File: %s\n", shaderFileName.c_str() );
	errorMessage += Stringf( "Folder: %s\n", shaderFileLocation.c_str() );
	if( errorLineNumber >= 0 )
	{
		const int maxLinesToPrint = 5;
		int lowestLineNumberToPrint = Clamp( 1 + errorLineNumber - maxLinesToPrint, 0, 999999 );
		errorMessage += Stringf( "On or near line %d of %s:\n\n", errorLineNumber+1, shaderFileName.c_str() );
		std::string shaderSourceString( shaderSource );
		for( int lineNumber = lowestLineNumberToPrint; lineNumber <= errorLineNumber; ++ lineNumber )
		{
			std::string lineText = GetLineFromTextBuffer( shaderSourceString, lineNumber );
			errorMessage += Stringf( "%04d) %s\n", lineNumber+1, lineText.c_str() );
		}
	}

	errorMessage += Stringf( "Raw error log follows:\n%s", infoLogString.c_str() );

	FATAL_ERROR( errorMessage );
/*
	size_t openParenIndex = infoLogString.find( '(' );
	size_t closeParenIndex = infoLogString.find( ')' );
	if( openParenIndex != std::string::npos && closeParenIndex != std::string::npos && closeParenIndex > openParenIndex )
	{
		std::string errorLineNumberString( infoLogString, openParenIndex + 1, closeParenIndex - openParenIndex - 1 );
		errorLineNumber = atoi( errorLineNumberString.c_str() );
		std::string errorLineTextUntrimmed = GetLineFromTextBuffer( shaderSource, errorLineNumber );
		size_t firstNonSpace  = errorLineTextUntrimmed.find_first_not_of( " \t" );
		errorLineText = firstNonSpace == std::string::npos ? errorLineTextUntrimmed : std::string( errorLineTextUntrimmed, firstNonSpace );
		size_t firstColonIndex = infoLogString.find( ':' );
		errorTextWithErrorCode = std::string( infoLogString, firstColonIndex + 1 );
		size_t secondColonIndex = infoLogString.find( ':', firstColonIndex + 1 );
		errorTextOnly = std::string( infoLogString, secondColonIndex + 2 );
	}

	const int LONG_PATH = 1024;
	char currentDirectory[ LONG_PATH ];
	DWORD currentDirectoryLength = GetCurrentDirectoryA( LONG_PATH, currentDirectory );
	std::string fullPath = currentDirectory + ("/" + shaderFilePath);
	if( currentDirectoryLength > 0 )
	{
		DebuggerPrintf( "------------------------------------------------------------------------------\n" );
		DebuggerPrintf( "  SHADER COMPILATION ERROR\n" );
		DebuggerPrintf( "------------------------------------------------------------------------------\n" );
		DebuggerPrintf( "%s(%d): %s", fullPath.c_str(), errorLineNumber, errorTextWithErrorCode.c_str() );
		DebuggerPrintf( "GLSL COMPILE ERROR in \"%s\" (%s shader), line %d:\n", shaderFileName.c_str(), shaderTypeName, errorLineNumber );
		DebuggerPrintf( "\n> %s\n\n", errorLineText.c_str() );
		DebuggerPrintf( "ERROR: %s", errorTextOnly.c_str() );
		DebuggerPrintf( "OpenGL version is %s\n", m_openGLVersionString );
		DebuggerPrintf( "GLSL version is %s\n", m_glslVersionString );
		DebuggerPrintf( "Raw error log follows:\n\n%s\n", infoLogString.c_str() );
	}
*/
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void __declspec( noreturn ) Renderer_OpenGL3::ReportShaderProgramError( ShaderProgramID programID, const Shader& vertexShader, const Shader& fragmentShader )
{
	GLint infoLogLength;
	glGetProgramiv( programID, GL_INFO_LOG_LENGTH, &infoLogLength );
	char* infoLogText = new char[ infoLogLength + 1 ];
	glGetProgramInfoLog( programID, infoLogLength, NULL, infoLogText );
	std::string infoLogString( infoLogText );
	delete[] infoLogText;

//	std::string vertexShaderFileName = (vertexShader.m_shaderFileNameWithoutExtension + ".") + GetShaderFileExtension();
//	std::string fragmentShaderFileName = (fragmentShader.m_shaderFileNameWithoutExtension + ".") + GetShaderFileExtension();
	std::string vertexShaderFileName = vertexShader.GetFileName();
	std::string fragmentShaderFileName = fragmentShader.GetFileName();
	std::string errorMessage = Stringf( "GLSL shader program link error!\n\nVertex shader: %s\nFragment shader: %s\n\nRAW ERROR MESSAGE:\n  %s", vertexShaderFileName.c_str(), fragmentShaderFileName.c_str(), infoLogString.c_str() );
	FATAL_ERROR( errorMessage );
}


/*
//-----------------------------------------------------------------------------------------------
void ReportShaderError( ShaderType shaderType, ShaderID shaderID, const char* shaderText, const std::string& shaderFileName, const std::string& shaderFilePath )
{
	GLint infoLogLength;
	glGetShaderiv( shaderID, GL_INFO_LOG_LENGTH, &infoLogLength );
	char* infoLogText = new char[ infoLogLength + 1 ];
	glGetShaderInfoLog( shaderID, infoLogLength, NULL, infoLogText );
	std::string infoLogString( infoLogText );
	delete[] infoLogText;
	int errorLineNumber = -1;
	std::string errorLineText;
	std::string errorTextWithErrorCode( infoLogString );
	std::string errorTextOnly( infoLogString );

	size_t openParenIndex = infoLogString.find( '(' );
	size_t closeParenIndex = infoLogString.find( ')' );
	if( openParenIndex != std::string::npos && closeParenIndex != std::string::npos )
	{
		std::string errorLineNumberString( infoLogString, openParenIndex + 1, closeParenIndex - openParenIndex - 1 );
		errorLineNumber = atoi( errorLineNumberString.c_str() );
		std::string errorLineTextUntrimmed = GetLineFromTextBuffer( (const char*) shaderText, errorLineNumber );
		size_t firstNonSpace  = errorLineTextUntrimmed.find_first_not_of( " \t" );
		errorLineText = firstNonSpace == std::string::npos ? errorLineTextUntrimmed : std::string( errorLineTextUntrimmed, firstNonSpace );
		size_t firstColonIndex = infoLogString.find( ':' );
		errorTextWithErrorCode = std::string( infoLogString, firstColonIndex + 1 );
		size_t secondColonIndex = infoLogString.find( ':', firstColonIndex + 1 );
		errorTextOnly = std::string( infoLogString, secondColonIndex + 2 );
	}

	const char* shaderTypeName = NULL;
	switch( shaderType )
	{
	case GL_VERTEX_SHADER:		shaderTypeName = "vertex";		break;
	case GL_FRAGMENT_SHADER:	shaderTypeName = "fragment";	break;
	case GL_GEOMETRY_SHADER:	shaderTypeName = "geometry";	break;
	default:					shaderTypeName = "UNKNOWN";		break;
	}

	const int LONG_PATH = 1024;
	char currentDirectory[ LONG_PATH ];
	DWORD currentDirectoryLength = GetCurrentDirectoryA( LONG_PATH, currentDirectory );
	std::string fullPath = currentDirectory + ("/" + shaderFilePath);
	if( currentDirectoryLength > 0 )
	{
		DebuggerPrintf( "------------------------------------------------------------------------------\n" );
		DebuggerPrintf( "  SHADER COMPILATION ERROR\n" );
		DebuggerPrintf( "------------------------------------------------------------------------------\n" );
		DebuggerPrintf( "%s(%d): %s", fullPath.c_str(), errorLineNumber, errorTextWithErrorCode.c_str() );
		DebuggerPrintf( "GLSL COMPILE ERROR in \"%s\" (%s shader), line %d:\n", shaderFileName.c_str(), shaderTypeName, errorLineNumber );
		DebuggerPrintf( "\n> %s\n\n", errorLineText.c_str() );
		DebuggerPrintf( "ERROR: %s", errorTextOnly.c_str() );
		DebuggerPrintf( "OpenGL version is %s\n", m_openGLVersionString );
		DebuggerPrintf( "GLSL version is %s\n", m_glslVersionString );
		DebuggerPrintf( "Raw error log follows:\n\n%s\n", infoLogString.c_str() );
	}

	std::string errorMessage;
	errorMessage += Stringf( "GLSL %s shader compile error!\n", shaderTypeName );
	errorMessage += Stringf( "\n%s, line %d:\n", shaderFileName.c_str(), errorLineNumber );
	errorMessage += Stringf( "\nERROR: %s\n", errorTextOnly.c_str() );
	errorMessage += Stringf( "Current OpenGL version: %s\n", g_openGLVersionString );
	errorMessage += Stringf( "GLSL version: %s\n", g_glslVersionString );
	errorMessage += Stringf( "\nFile location:\n%s\n", fullPath.c_str() );
	errorMessage += Stringf( "\nRAW ERROR LOG:\n  %s", infoLogString.c_str() );

	//	std::string errorMessage = Stringf( "GLSL %s shader compile error!\n\n%s, line %d:\n\n> %s\n\nERROR: %s\nCurrent OpenGL version: %s\nGLSL version: %s\n\nRAW ERROR LOG:\n  %s",
	//		shaderTypeName, shaderFileName.c_str(), errorLineNumber, errorLineText.c_str(), errorTextOnly.c_str(), g_openGLVersionString, g_glslVersionString, infoLogString.c_str() );
	std::string errorTitle = Stringf( "GLSL compile error in %s", shaderFileName.c_str() );
	FatalError( errorTitle, errorMessage );
}


//-----------------------------------------------------------------------------------------------
void ReportShaderProgramError( ShaderProgramID programID, OpenGLShader* vertexShader, OpenGLShader* fragmentShader )
{
	GLint infoLogLength;
	glGetProgramiv( programID, GL_INFO_LOG_LENGTH, &infoLogLength );
	char* infoLogText = new char[ infoLogLength + 1 ];
	glGetProgramInfoLog( programID, infoLogLength, NULL, infoLogText );
	std::string infoLogString( infoLogText );
	delete[] infoLogText;

	const char* vertexShaderName = vertexShader ? vertexShader->m_shaderFilePath.c_str() : "unknown";
	const char* fragmentShaderName = fragmentShader ? fragmentShader->m_shaderFilePath.c_str() : "unknown";
	std::string errorTitle = Stringf( "GLSL link error" );
	std::string errorMessage = Stringf( "GLSL shader program link error!\n\nVertex shader: %s\nFragment shader: %s\n\nRAW ERROR MESSAGE:\n  %s", vertexShaderName, fragmentShaderName, infoLogString.c_str() );
	FatalError( errorTitle, errorMessage );
}
*/


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer_OpenGL3::CreateBuiltInShaders( RendererCapabilityLevel renderingCapabilityLevel )
{
	if( renderingCapabilityLevel < RENDERER_CAPABILITY_OPENGL20 )
		return; // No shader support

	if( renderingCapabilityLevel >= RENDERER_CAPABILITY_OPENGL30 )
	{
		m_builtInShaderUnlit = CreateShaderProgramFromSource( s_builtInShader_UnlitVertexGLSL130, s_builtInShader_UnlitFragmentGLSL130, "BuiltIn-Unlit", "130" );
	}
	else
	{
		m_builtInShaderUnlit = CreateShaderProgramFromSource( s_builtInShader_UnlitVertexGLSL110, s_builtInShader_UnlitFragmentGLSL110, "BuiltIn-Unlit", "110" );
	}
}


//-----------------------------------------------------------------------------------------------
STATIC const char* Renderer_OpenGL3::s_builtInShader_UnlitVertexGLSL110 =
"																		\n\
	#version 110														\n\
																		\n\
	uniform mat4 u_worldToClipTransform;								\n\
	uniform mat4 u_objectToWorldTransform;								\n\
																		\n\
	varying vec4 v_surfaceColor;										\n\
	varying vec2 v_texCoords;											\n\
																		\n\
	void main()															\n\
	{																	\n\
		v_surfaceColor = gl_Color;										\n\
		v_texCoords = gl_MultiTexCoord0.xy;								\n\
																		\n\
		vec4 worldPosition = u_objectToWorldTransform * gl_Vertex;		\n\
		gl_Position = u_worldToClipTransform * worldPosition;			\n\
	}																	\n\
";


//-----------------------------------------------------------------------------------------------
STATIC const char* Renderer_OpenGL3::s_builtInShader_UnlitVertexGLSL130 =
"																					\n\
	#version 130																	\n\
																					\n\
	uniform mat4 u_worldToClipTransform;											\n\
	uniform mat4 u_objectToWorldTransform;											\n\
																					\n\
	in vec3 a_position;																\n\
	in vec4 a_color;																\n\
	in vec2 a_texCoords;															\n\
																					\n\
	out vec4 v_surfaceColor;														\n\
	out vec2 v_texCoords;															\n\
																					\n\
	void main()																		\n\
	{																				\n\
		v_surfaceColor = a_color;													\n\
		v_texCoords = a_texCoords;													\n\
																					\n\
		vec4 worldPosition = u_objectToWorldTransform * vec4( a_position, 1.0 );	\n\
		gl_Position = u_worldToClipTransform * worldPosition;						\n\
	}																				\n\
";


//-----------------------------------------------------------------------------------------------
STATIC const char* Renderer_OpenGL3::s_builtInShader_UnlitFragmentGLSL110 =
"																			\n\
	#version 110															\n\
																			\n\
	uniform sampler2D u_diffuseTexture;										\n\
																			\n\
	varying vec4 v_surfaceColor;											\n\
	varying vec2 v_texCoords;												\n\
																			\n\
	void main()																\n\
	{																		\n\
		vec4 diffuseTexel = texture2D( u_diffuseTexture, v_texCoords );		\n\
		gl_FragColor = diffuseTexel * v_surfaceColor;						\n\
	}																		\n\
";


//-----------------------------------------------------------------------------------------------
STATIC const char* Renderer_OpenGL3::s_builtInShader_UnlitFragmentGLSL130 =
"																		\n\
	#version 130														\n\
																		\n\
	uniform sampler2D u_diffuseTexture;									\n\
																		\n\
	in vec4 v_surfaceColor;												\n\
	in vec2 v_texCoords;												\n\
																		\n\
	out vec4 o_fragColor;												\n\
																		\n\
	void main()															\n\
	{																	\n\
		vec4 diffuseTexel = texture( u_diffuseTexture, v_texCoords );	\n\
		o_fragColor = diffuseTexel * v_surfaceColor;					\n\
	}																	\n\
";



#endif // GYPSY_ENABLE_RENDERER
