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

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


//-----------------------------------------------------------------------------------------------
// Include the platform-and-API-appropriate header file
#if defined( PLATFORM_RENDERER_XBOX360 )
	#error BUILD ERROR: Gypsy Renderer not yet implemented for Xbox360!
#elif defined( PLATFORM_RENDERER_PS3 )
	#error BUILD ERROR: Gypsy Renderer not yet implemented for PS3!
#elif defined( PLATFORM_RENDERER_IOS )
	#error BUILD ERROR: Gypsy Renderer not yet implemented for iOS!
#elif defined( PLATFORM_RENDERER_WIN32_DIRECTX )
	#error BUILD ERROR: Gypsy Renderer not yet implemented for DirectX under Windows!
#elif defined( PLATFORM_RENDERER_WIN32_OPENGL )
	#include "Engine/Renderer/Renderer_OpenGL3_Win32.hpp"
#else
	#error BUILD ERROR: No Renderer implementation is yet available for the current platform!
#endif


//-----------------------------------------------------------------------------------------------
STATIC void Renderer::Create()
{
#if defined( PLATFORM_RENDERER_XBOX360 )
#error BUILD ERROR: Gypsy Renderer not yet implemented for Xbox360!
#elif defined( PLATFORM_RENDERER_PS3 )
#error BUILD ERROR: Gypsy Renderer not yet implemented for PS3!
#elif defined( PLATFORM_RENDERER_IOS )
#error BUILD ERROR: Gypsy Renderer not yet implemented for iOS!
#elif defined( PLATFORM_RENDERER_WIN32_DIRECTX )
#error BUILD ERROR: Gypsy Renderer not yet implemented for DirectX under Windows!
#elif defined( PLATFORM_RENDERER_WIN32_OPENGL )
	new Renderer_OpenGL3_Win32();
#else
#error BUILD ERROR: No Renderer implementation is yet available for the current platform!
#endif
}


/////////////////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------------------
Shader::Shader( ShaderID shaderID, ShaderType shaderType, const std::string& shaderName, const std::string& shaderRelativePath, const std::string& shaderVersionString, bool hasGenericVertexAttribs )
	: m_shaderName( shaderName )
	, m_shaderVersionString( shaderVersionString )
	, m_shaderRelativePath( shaderRelativePath )
	, m_shaderType( shaderType )
	, m_shaderID( shaderID )
	, m_hasGenericVertexAttribs( hasGenericVertexAttribs )
{
}


//-----------------------------------------------------------------------------------------------
std::string Shader::GetFileName() const
{
	const char* shaderName = m_shaderName.c_str();
	const char* shaderType = GetNameForShaderType( m_shaderType );
	const char* shaderVersion = m_shaderVersionString.c_str();
	const char* shaderExtension = theRenderer->GetShaderFileExtension();

	return Stringf( "%s_%s_%s.%s", shaderName, shaderType, shaderVersion, shaderExtension );
}


/////////////////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------------------
ShaderProgram::ShaderProgram( ShaderProgramID programID, const Shader& vertexShader, const Shader& fragmentShader )
	: m_vertexShader( &vertexShader )
	, m_fragmentShader( &fragmentShader )
	, m_programID( programID )
	, m_uniformLocation_objectToWorldMatrix( -1 )
	, m_uniformLocation_worldToClipMatrix( -1 )
	, m_uniformLocation_diffuseTexture( -1 )
	, m_uniformLocation_noiseTexture( -1 )
	, m_uniformLocation_time( -1 )
	, m_uniformLocation_effectStrength( -1 )
	, m_uniformLocation_effectScale( -1 )
	, m_uniformLocation_effectSpeed( -1 )
	, m_uniformLocation_edgeThreshold( -1 )
	, m_uniformLocation_softness( -1 )
{
}


/////////////////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------------------
FramebufferObject::FramebufferObject( const IntVector2& renderTargetTexelSize, TextureFilterMode minFilter, TextureFilterMode magFilter )
	: m_renderTargetTexelSize( renderTargetTexelSize )
	, m_colorTexture( nullptr )
	, m_depthTexture( nullptr )
{
	static int fboNumber = 0;
	std::string colorTextureInternalName = Stringf( "GypsyInternalFBOColorTexture%d", fboNumber );
	std::string depthTextureInternalName = Stringf( "GypsyInternalFBODepthTexture%d", fboNumber );
	++ fboNumber;

	m_colorTexture = theRenderer->CreateTextureFromData( colorTextureInternalName.c_str(), nullptr, renderTargetTexelSize, 3, minFilter, magFilter );
	m_depthTexture = theRenderer->CreateTextureFromData( depthTextureInternalName.c_str(), nullptr, renderTargetTexelSize, 1 ); // FIXME: Temp-hack way to requests depth-format texture
}



/////////////////////////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------------------
EXTERN Renderer* theRenderer = nullptr;


//-----------------------------------------------------------------------------------------------
Renderer::Renderer()
	: m_defaultFont( nullptr )
	, m_defaultFontName( "Gypsy" )
	, m_plainWhiteTexture( nullptr )
	, m_defaultNormalMap( nullptr )
	, m_defaultSpecGlossEmitMap( nullptr )
	, m_missingTextureTexture( nullptr )
	, m_capabilityLevel( RENDERER_CAPABILITY_OPENGL11 ) // Assume lowest common denominator (OpenGL1.1/DirectX8) until proven otherwise
	, m_maxCapabilityLevel( RENDERER_CAPABILITY_OPENGL43 )
	, m_currentShaderProgram( nullptr )
	, m_currentFramebufferObject( nullptr )
	, m_currentLineThickness( 1.f )
{
	theRenderer = this;
}


//-----------------------------------------------------------------------------------------------
Renderer::~Renderer()
{
	theRenderer = nullptr;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer::StartUp()
{
	DetermineCapabilityLevel();
	CreateBuiltInTextures();
	CreateBuiltInShaders( m_capabilityLevel );

	m_defaultFont = CreateOrGetFont( m_defaultFontName.c_str() );
//	m_currentShaderProgram = CreateOrGetShaderProgram( "UnlitBasic", "UnlitBasic" );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL Texture* Renderer::CreateOrGetTextureFromFile( const char* imageFilePath, TextureFilterMode minFilter, TextureFilterMode magFilter, TextureEdgeMode edgeMode )
{
	HashedCaseInsensitiveString hashedTextureName( imageFilePath );
	std::map< HashedCaseInsensitiveString, Texture* >::iterator found = m_textureRegistry.find( hashedTextureName );
	if( found != m_textureRegistry.end() )
	{
		return found->second;
	}

	Texture* newTexture = CreateTextureFromFile( imageFilePath, minFilter, magFilter, edgeMode );
	m_textureRegistry[ hashedTextureName ] = newTexture;
	return newTexture;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL LogicalTexture Renderer::CreateOrGetLogicalTextureFromFile( const char* imageFilePath, const AABB2& texCoords, TextureFilterMode minFilter, TextureFilterMode magFilter, TextureEdgeMode edgeMode )
{
	Texture* physicalTexture = CreateOrGetTextureFromFile( imageFilePath, minFilter, magFilter, edgeMode );
	LogicalTexture logicalTexture( physicalTexture, texCoords );
	return logicalTexture;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL LogicalTexture Renderer::CreateOrGetLogicalTextureFromFile( const char* imageFilePath, const IntVector2& sheetCoords, const IntVector2& sheetLayout, TextureFilterMode minFilter, TextureFilterMode magFilter, TextureEdgeMode edgeMode )
{
	Texture* physicalTexture = CreateOrGetTextureFromFile( imageFilePath, minFilter, magFilter, edgeMode );
	float sheetTileWidth = (1.f / (float) sheetLayout.x);
	float sheetTileHeight = (1.f / (float) sheetLayout.y);
	float minTexCoordU = sheetTileWidth * (float) sheetCoords.x;
	float minTexCoordV = sheetTileHeight * (float) sheetCoords.y;
	float maxTexCoordU = minTexCoordU + sheetTileWidth;
	float maxTexCoordV = minTexCoordV + sheetTileHeight;
	AABB2 texCoords( minTexCoordU, minTexCoordV, maxTexCoordU, maxTexCoordV );
	LogicalTexture logicalTexture( physicalTexture, texCoords );
	return logicalTexture;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL BitmapFont* Renderer::CreateOrGetFont( const char* bitmapFontName )
{
	HashedCaseInsensitiveString hashedFontName( bitmapFontName );
	std::map< HashedCaseInsensitiveString, BitmapFont* >::iterator found = m_fontRegistry.find( hashedFontName );
	if( found != m_fontRegistry.end() )
	{
		return found->second;
	}

	BitmapFont* newFont = CreateBitmapFont( bitmapFontName );
	m_fontRegistry[ hashedFontName ] = newFont;
	return newFont;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer::SetDefaultFont( const char* defaultFontName )
{
	m_defaultFontName = defaultFontName;

	if( IsReady() )
	{
		m_defaultFont = CreateOrGetFont( m_defaultFontName.c_str() );
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL BitmapFont* Renderer::CreateBitmapFont( const char* bitmapFontName )
{
	std::string fontFilePath = "Data/Fonts/";
	fontFilePath += bitmapFontName;
	std::string fontImageFilePathBMFont = fontFilePath + "_0.png";
	std::string fontImageFilePathJazzOrFixed = fontFilePath + ".png";

	BitmapFont* newFont = new BitmapFont();
	newFont->m_glyphsTexture = CreateOrGetTextureFromFile( fontImageFilePathBMFont.c_str(), TEXTURE_FILTER_LINEAR, TEXTURE_FILTER_LINEAR, TEXTURE_EDGE_CLAMP );
	if( !newFont->m_glyphsTexture )
		newFont->m_glyphsTexture = CreateOrGetTextureFromFile( fontImageFilePathJazzOrFixed.c_str(), TEXTURE_FILTER_LINEAR, TEXTURE_FILTER_LINEAR, TEXTURE_EDGE_CLAMP );

	newFont->PopulateMetaData( fontFilePath.c_str() );
	if( !newFont->IsValid() )
	{
		delete newFont;
		newFont = nullptr;
	}

	return newFont;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL ShaderProgram* Renderer::CreateShaderProgramFromFiles( const char* vertexShaderName, const char* fragmentShaderName )
{
	ShaderProgram* newShaderProgram = nullptr;
	Shader* vertexShader = CreateOrGetShader( vertexShaderName, SHADER_TYPE_VERTEX );
	FATAL_GUARANTEE( vertexShader, Stringf( "Error: could not find any version of a vertex shader called \"%s\"!", vertexShaderName ) );

	Shader* fragmentShader = CreateOrGetShader( fragmentShaderName, SHADER_TYPE_FRAGMENT );
	FATAL_GUARANTEE( fragmentShader, Stringf( "Error: could not find any version of a fragment shader called \"%s\"!", fragmentShaderName ) );

	if( vertexShader && fragmentShader )
	{
		newShaderProgram = LinkShaderProgram( *vertexShader, *fragmentShader );
	}
	FATAL_GUARANTEE( newShaderProgram, Stringf( "Error: could not link shader program with vertex shader \"%s\" and fragment shader \"%s\"!", vertexShaderName, fragmentShaderName ) );

	return newShaderProgram;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL ShaderProgram* Renderer::CreateOrGetShaderProgram( const char* vertexShaderName, const char* fragmentShaderName )
{
	std::string shaderProgramName = Stringf( "%s+%s", vertexShaderName, fragmentShaderName );
	HashedCaseInsensitiveString hashedProgramName( shaderProgramName );
	std::map< HashedCaseInsensitiveString, ShaderProgram* >::iterator found = m_shaderProgramRegistry.find( hashedProgramName );
	if( found != m_shaderProgramRegistry.end() )
	{
		return found->second;
	}

	ShaderProgram* newShaderProgram = CreateShaderProgramFromFiles( vertexShaderName, fragmentShaderName );
	m_shaderProgramRegistry[ hashedProgramName ] = newShaderProgram; // May be nullptr, in which case we are in essence 'caching' the fact that that shader does not exist
	return newShaderProgram;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL Shader* Renderer::CreateOrGetShader( const char* shaderName, ShaderType shaderType )
{
	const char* shaderRelativePath = "Data/Shaders"; // TODO: allow caller to load shaders from anywhere
	const char* shaderTypeName = GetNameForShaderType( shaderType );
	std::string shaderNameWithType = Stringf( "%s_%s", shaderName, shaderTypeName );
	HashedCaseInsensitiveString hashedShaderNameWithType( shaderNameWithType );
	std::map< HashedCaseInsensitiveString, Shader* >::iterator found = m_shaderRegistry.find( hashedShaderNameWithType );
	if( found != m_shaderRegistry.end() )
	{
		return found->second;
	}

	std::vector< std::string > legalShaderVersionStrings;
	GetLegalShaderVersionStringList( legalShaderVersionStrings );
	Shader* newShader = nullptr;

	// Try to load the newest legally supported version of that shader which exists on disk
	for( unsigned int versionStringIndex = 0; versionStringIndex < legalShaderVersionStrings.size(); ++ versionStringIndex )
	{
		std::string& versionString = legalShaderVersionStrings[ versionStringIndex ];
		newShader = CreateShader( shaderType, shaderName, shaderRelativePath, versionString );
		if( newShader )
			break;
	}

	m_shaderRegistry[ hashedShaderNameWithType ] = newShader;
	return newShader;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL Shader* Renderer::CreateShader( ShaderType shaderType, const std::string& shaderName, const std::string& shaderRelativePath, const std::string& shaderVersionString )
{
	Shader* compiledShader = nullptr;
	const char* shaderTypeName = GetNameForShaderType( shaderType );
	const char* shaderFileExtension = GetShaderFileExtension();
	std::string shaderFilePath = Stringf( "%s/%s_%s_%s.%s", shaderRelativePath.c_str(), shaderName.c_str(), shaderTypeName, shaderVersionString.c_str(), shaderFileExtension );
	const char* shaderSource = LoadTextFileToNewBuffer( shaderFilePath.c_str() );
	if( shaderSource )
	{
		compiledShader = CompileShader( shaderSource, shaderType, shaderName, shaderRelativePath, shaderVersionString );
		delete shaderSource;
	}

	return compiledShader;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer::SetScreenVirtualOrtho( const Vector2& virtualScreenCoordSize, bool isOriginAtTop )
{
	Vector2 virtualScreenCoordsBottomLeft( 0.f, 0.f );
	Vector2 virtualScreenCoordsTopRight( virtualScreenCoordSize );
	if( isOriginAtTop )
	{
		virtualScreenCoordsTopRight.y = 0.f;
		virtualScreenCoordsBottomLeft.y = virtualScreenCoordSize.y;
	}

	SetScreenVirtualOrtho( virtualScreenCoordsBottomLeft, virtualScreenCoordsTopRight );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Renderer::Draw2dTextGlyph( unsigned char glyphUCS, const Vector2& center, float cellHeight, const Rgba& tint, BitmapFont* font )
{
	if( font == nullptr )
	{
		if( m_defaultFont == nullptr )
			return;

		font = m_defaultFont;
	}

	Texture* texture = font->m_glyphsTexture;
	GlyphData& glyphData = font->m_glyphsData[ glyphUCS ];

	float halfCellHeight = 0.5f * cellHeight;
	float halfCellWidth = halfCellHeight * glyphData.m_cellHeightFractionDuringGlyph;
	
	AABB2 bounds( center.x - halfCellWidth, center.y - halfCellHeight, center.x + halfCellWidth, center.y + halfCellHeight );
	Vector2 texCoordsAtMins( glyphData.m_texCoordMins.x, glyphData.m_texCoordMaxs.y );
	Vector2 texCoordsAtMaxs( glyphData.m_texCoordMaxs.x, glyphData.m_texCoordMins.y );
	Draw2dTexturedAABB( bounds, texCoordsAtMins, texCoordsAtMaxs, tint, *texture );
}


//---------------------------------------------------------------------------
VIRTUAL void Renderer::Draw2dTexturedAABB( const AABB2& bounds, const Vector2& relativeTexCoordsAtMins, const Vector2& relativeTexCoordsAtMaxs, const Rgba& tint, const LogicalTexture& logicalTexture )
{
	Vector2 logicalTexCoordsSize = logicalTexture.m_texCoords.maxs - logicalTexture.m_texCoords.mins;
	AABB2 actualTexCoords;
	actualTexCoords.mins.x = logicalTexture.m_texCoords.mins.x + (relativeTexCoordsAtMins.x * logicalTexCoordsSize.x);
	actualTexCoords.mins.y = logicalTexture.m_texCoords.mins.y + (relativeTexCoordsAtMins.y * logicalTexCoordsSize.y);
	actualTexCoords.maxs.x = logicalTexture.m_texCoords.mins.x + (relativeTexCoordsAtMaxs.x * logicalTexCoordsSize.x);
	actualTexCoords.maxs.y = logicalTexture.m_texCoords.mins.y + (relativeTexCoordsAtMaxs.y * logicalTexCoordsSize.y);
	Draw2dTexturedAABB( bounds, actualTexCoords.mins, actualTexCoords.maxs, tint, *logicalTexture.m_texture );
}


//---------------------------------------------------------------------------
// Draws text relative to a specified anchor position.
// anchorLocation determines where the anchor is assumed to be relative to
//	the text; e.g. ALIGNMENT_2D_BOTTOMLEFT means the anchor marks the bottom-
//	left of the text.
// 
VIRTUAL void Renderer::Draw2dTextString( const char* latinChars, const Vector2& anchorPos, float cellHeight, const Rgba& tint, Alignment2D anchorLocation, BitmapFont* font )
{
	if( font == nullptr )
		font = m_defaultFont;
	FATAL_GUARANTEE( font != nullptr, "Draw2dTextString() - no font specified, and no default font" );

	Vector2 drawPos = anchorPos;
	switch( anchorLocation )
	{
	case ALIGNMENT_2D_BOTTOMLEFT:
		break;
	case ALIGNMENT_2D_TOPLEFT:
		drawPos.y -= cellHeight;
		break;
	case ALIGNMENT_2D_CENTERLEFT:
		drawPos.y -= (0.5f * cellHeight);
		break;
	case ALIGNMENT_2D_BOTTOMRIGHT:
		drawPos.x -= font->CalcTextStringWidth( latinChars, cellHeight );
		break;
	case ALIGNMENT_2D_TOPRIGHT:
		drawPos.x -= font->CalcTextStringWidth( latinChars, cellHeight );
		drawPos.y -= cellHeight;
		break;
	case ALIGNMENT_2D_CENTERRIGHT:
		drawPos.x -= font->CalcTextStringWidth( latinChars, cellHeight );
		drawPos.y -= (0.5f * cellHeight);
		break;
	case ALIGNMENT_2D_BOTTOMCENTER:
		drawPos.x -= (0.5f * font->CalcTextStringWidth( latinChars, cellHeight ));
		break;
	case ALIGNMENT_2D_TOPCENTER:
		drawPos.x -= (0.5f * font->CalcTextStringWidth( latinChars, cellHeight ));
		drawPos.y -= cellHeight;
		break;
	case ALIGNMENT_2D_CENTER:
		drawPos.x -= (0.5f * font->CalcTextStringWidth( latinChars, cellHeight ));
		drawPos.y -= (0.5f * cellHeight);
		break;
	}

	Draw2dTextStringAtPosition( latinChars, drawPos, cellHeight, tint, font );
}


//---------------------------------------------------------------------------
VIRTUAL void Renderer::Draw2dTextStringInBox( const char* latinChars, const AABB2& box, float cellHeight, const Rgba& tint, Alignment2D alignmentInBox, BitmapFont* font )
{
	if( font == nullptr )
		font = m_defaultFont;
	FATAL_GUARANTEE( font != nullptr, "Draw2dTextStringInBox() - no font specified, and no default font" );

	float boxHeight = box.maxs.y - box.mins.y;
	float boxWidth = box.maxs.x - box.mins.x;
	if( boxHeight <= 0.f || boxWidth <= 0.f )
		return;

	float textNaturalWidth = font->CalcTextStringWidth( latinChars, cellHeight );
	float verticalScaleRequired = cellHeight <= boxHeight ? 1.f : (boxHeight / cellHeight);
	float horizontalScaleRequired = textNaturalWidth <= boxWidth ? 1.f : (boxWidth / textNaturalWidth);
	float scaleRequired = GetMin( horizontalScaleRequired, verticalScaleRequired );
	if( scaleRequired <= 0.f )
		return;

	Vector2 anchorPos = box.mins;
	switch( alignmentInBox )
	{
	case ALIGNMENT_2D_TOPLEFT:
		anchorPos.y = box.maxs.y;
		break;
	case ALIGNMENT_2D_CENTERLEFT:
		anchorPos.y = 0.5f * (box.mins.y + box.maxs.y);
		break;
	case ALIGNMENT_2D_BOTTOMRIGHT:
		anchorPos.x = box.maxs.x;
		break;
	case ALIGNMENT_2D_TOPRIGHT:
		anchorPos = box.maxs;
		break;
	case ALIGNMENT_2D_CENTERRIGHT:
		anchorPos.x = box.maxs.x;
		anchorPos.y = 0.5f * (box.mins.y + box.maxs.y);
		break;
	case ALIGNMENT_2D_BOTTOMCENTER:
		anchorPos.x = 0.5f * (box.mins.x + box.maxs.x);
		break;
	case ALIGNMENT_2D_TOPCENTER:
		anchorPos.x = 0.5f * (box.mins.x + box.maxs.x);
		anchorPos.y = box.maxs.y;
		break;
	case ALIGNMENT_2D_CENTER:
		anchorPos.x = 0.5f * (box.mins.x + box.maxs.x);
		anchorPos.y = 0.5f * (box.mins.y + box.maxs.y);
		break;
	}

	Draw2dTextString( latinChars, anchorPos, scaleRequired * cellHeight, tint, alignmentInBox, font );
}


//---------------------------------------------------------------------------
VIRTUAL void Renderer::Draw2dTextStringInBoxWordWrapped( const char* latinChars, const AABB2& box, float cellHeight, const Rgba& tint, float interiorPadding, BitmapFont* font )
{
	if( font == nullptr )
		font = m_defaultFont;
	FATAL_GUARANTEE( font != nullptr, "Draw2dTextStringInBox() - no font specified, and no default font" );

	float boxHeight = (box.maxs.y - box.mins.y) - (2.f * interiorPadding);
	float boxWidth = (box.maxs.x - box.mins.x) - (2.f * interiorPadding);
	if( boxHeight <= 0.f || boxWidth <= 0.f )
		return;

	Vector2 startDrawPos( box.mins.x + interiorPadding, box.maxs.y - interiorPadding - cellHeight );
	Vector2 drawPos( startDrawPos );

	const int MAX_LINE_LENGTH = 2048;
	char lineText[ MAX_LINE_LENGTH ];
	int lineTextLength = 0;
	const char* scan = latinChars;
	const char* startOfLine = nullptr;

	while( *scan != '\0' )
	{
		startOfLine = scan;
		font->AdvanceToEndOfLineByFit( scan, boxWidth, cellHeight );
		const char* endOfLine = scan;
		if( *scan != '\0' && !isspace( *scan ) )
		{
			// We ran out of room in the middle of a word, apparently.  Back up to start of word.
			while( scan > startOfLine && !isspace( *scan ) )
				-- scan;

			if( scan == startOfLine )
			{
				// Word doesn't fit anyway, so wrap it manually wherever the edge falls
				font->AdvanceToEndOfLineByFit( scan, boxWidth, cellHeight );
			}
			endOfLine = scan;
		}

		lineTextLength = endOfLine - startOfLine;
		if( lineTextLength > 0 )
		{
			memcpy( lineText, startOfLine, lineTextLength );
			lineText[ lineTextLength ] = '\0';
			Draw2dTextStringAtPosition( lineText, drawPos, cellHeight, tint );
		}

		drawPos.x = startDrawPos.x;
		drawPos.y -= cellHeight;

		scan = endOfLine;
		if( isspace( *scan ) )
			++ scan;
	}
}


//---------------------------------------------------------------------------
VIRTUAL void Renderer::CreateBuiltInTextures()
{
	unsigned char plainWhiteTexel[3] = { 255, 255, 255 }; // A single white opaque texel
	m_plainWhiteTexture = CreateTextureFromData( "PlainWhite", plainWhiteTexel, IntVector2::ONE, 3 );

	unsigned char lightBlueTexel[3] = { 127, 127, 255 }; // A single light-blue opaque texel, which is RGB encoding for a "straight out" tangent space normal of xyz(0,0,1)
	m_defaultNormalMap = CreateTextureFromData( "DefaultNormal", lightBlueTexel, IntVector2::ONE, 3 );

	unsigned char darkYellowTexel[3] = { 127, 127, 0 }; // A single dark-yellow opaque texel for Specular-Gloss-Emissive maps, so defaults are spec=50%, gloss=50%, emissive=0%
	m_defaultSpecGlossEmitMap = CreateTextureFromData( "DefaultSpecGlossEmit", darkYellowTexel, IntVector2::ONE, 3 );

	unsigned char blackMagentaCheckerboardTexels[ 4 * 4 * 3 ] = // 4x4 texture of magenta/black (checkerboard-alternated)
	{
		255, 0, 255,		0,   0, 0,			255, 0, 255,		0,   0, 0,
		0,   0, 0,			255, 0, 255,		0,   0, 0,			255, 0, 255,
		255, 0, 255,		0,   0, 0,			255, 0, 255,		0,   0, 0,
		0,   0, 0,			255, 0, 255,		0,   0, 0,			255, 0, 255
	}; 
	m_missingTextureTexture = CreateTextureFromData( "MissingTexture", blackMagentaCheckerboardTexels, IntVector2( 4, 4 ), 3 );
}


//---------------------------------------------------------------------------
VIRTUAL ShaderProgram* Renderer::CreateShaderProgramFromSource( const char* vertexShaderSource, const char* fragmentShaderSource, const char* shaderName, const char* shaderVersionString )
{
	ShaderProgram* newShaderProgram = nullptr;
	Shader* vertexShader = CompileShader( vertexShaderSource, SHADER_TYPE_VERTEX, shaderName, "[GypsyBuiltInShader]", shaderVersionString );
	FATAL_GUARANTEE( vertexShader, Stringf( "Error: Could not create shader \"%s\" (version %s) from source text buffer; vertex shader failed to compile.", shaderName, shaderVersionString ) );

	Shader* fragmentShader = CompileShader( fragmentShaderSource, SHADER_TYPE_FRAGMENT, shaderName, "[GypsyBuiltInShader]", shaderVersionString );
	FATAL_GUARANTEE( fragmentShader, Stringf( "Error: Could not create shader \"%s\" (version %s) from source text buffer; fragment shader failed to compile.", shaderName, shaderVersionString ) );

	if( vertexShader && fragmentShader )
	{
		newShaderProgram = LinkShaderProgram( *vertexShader, *fragmentShader );
	}
	FATAL_GUARANTEE( newShaderProgram, Stringf( "Error: could not link shader program \"%s\" (version %s) created from source text buffers.", shaderName, shaderVersionString ) );

	return newShaderProgram;
}



#endif // GYPSY_ENABLE_RENDERER
