// Copyright 2016 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// utilities.cpp: Conversion functions and other utility routines.

#include "utilities.h"

#include "mathutil.h"
#include "Context.h"
#include "common/debug.h"

#include <limits>
#include <stdio.h>

namespace es1
{
	bool IsCompressed(GLenum format)
	{
		return format == GL_COMPRESSED_RGB_S3TC_DXT1_EXT ||
		       format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ||
               format == GL_ETC1_RGB8_OES;
	}

	bool IsSizedInternalFormat(GLint internalformat)
	{
		switch(internalformat)
		{
		case GL_ALPHA8_EXT:
		case GL_LUMINANCE8_ALPHA8_EXT:
		case GL_LUMINANCE8_EXT:
		case GL_RGBA4_OES:
		case GL_RGB5_A1_OES:
		case GL_RGB565_OES:
		case GL_RGB8_OES:
		case GL_RGBA8_OES:
		case GL_BGRA8_EXT:   // GL_APPLE_texture_format_BGRA8888
		case GL_DEPTH_COMPONENT16_OES:
		case GL_STENCIL_INDEX8_OES:
		case GL_DEPTH24_STENCIL8_OES:
			return true;
		default:
			return false;
		}
	}

	GLenum ValidateSubImageParams(bool compressed, bool copy, GLenum target, GLint level, GLint xoffset, GLint yoffset,
	                              GLsizei width, GLsizei height, GLenum format, GLenum type, Texture *texture)
	{
		if(!texture)
		{
			return GL_INVALID_OPERATION;
		}

		GLenum sizedInternalFormat = texture->getFormat(target, level);

		if(compressed)
		{
			if(format != sizedInternalFormat)
			{
				return GL_INVALID_OPERATION;
			}
		}
		else if(!copy)   // CopyTexSubImage doesn't have format/type parameters.
		{
			GLenum validationError = ValidateTextureFormatType(format, type, sizedInternalFormat, target);
			if(validationError != GL_NO_ERROR)
			{
				return validationError;
			}
		}

		if(compressed)
		{
			if((width % 4 != 0 && width != texture->getWidth(target, 0)) ||
			   (height % 4 != 0 && height != texture->getHeight(target, 0)))
			{
				return GL_INVALID_OPERATION;
			}
		}

		if(xoffset + width > texture->getWidth(target, level) ||
		   yoffset + height > texture->getHeight(target, level))
		{
			return GL_INVALID_VALUE;
		}

		return GL_NO_ERROR;
	}

	bool IsDepthTexture(GLenum format)
	{
		return format == GL_DEPTH_STENCIL_OES;
	}

	bool IsStencilTexture(GLenum format)
	{
		return format == GL_DEPTH_STENCIL_OES;
	}

	bool IsCubemapTextureTarget(GLenum target)
	{
		return (target >= GL_TEXTURE_CUBE_MAP_POSITIVE_X_OES && target <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_OES);
	}

	int CubeFaceIndex(GLenum cubeFace)
	{
		switch(cubeFace)
		{
		case GL_TEXTURE_CUBE_MAP_OES:
		case GL_TEXTURE_CUBE_MAP_POSITIVE_X_OES: return 0;
		case GL_TEXTURE_CUBE_MAP_NEGATIVE_X_OES: return 1;
		case GL_TEXTURE_CUBE_MAP_POSITIVE_Y_OES: return 2;
		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_OES: return 3;
		case GL_TEXTURE_CUBE_MAP_POSITIVE_Z_OES: return 4;
		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_OES: return 5;
		default: UNREACHABLE(cubeFace); return 0;
		}
	}

	bool IsTextureTarget(GLenum target)
	{
		return target == GL_TEXTURE_2D;
	}

	// Verify that format/type are one of the combinations from table 3.4.
	GLenum ValidateTextureFormatType(GLenum format, GLenum type, GLint internalformat, GLenum target)
	{
		switch(type)
		{
		case GL_UNSIGNED_BYTE:
		case GL_UNSIGNED_SHORT_4_4_4_4:
		case GL_UNSIGNED_SHORT_5_5_5_1:
		case GL_UNSIGNED_SHORT_5_6_5:
		case GL_UNSIGNED_INT_24_8_OES:   // GL_OES_packed_depth_stencil
			break;
		default:
			return GL_INVALID_ENUM;
		}

		switch(format)
		{
		case GL_ALPHA:
		case GL_RGB:
		case GL_RGBA:
		case GL_LUMINANCE:
		case GL_LUMINANCE_ALPHA:
		case GL_BGRA_EXT:            // GL_EXT_texture_format_BGRA8888
			break;
		case GL_DEPTH_STENCIL_OES:   // GL_OES_packed_depth_stencil (GL_DEPTH_STENCIL_OES)
			switch(target)
			{
			case GL_TEXTURE_2D:
				break;
			default:
				return GL_INVALID_OPERATION;
			}
			break;
		default:
			return GL_INVALID_ENUM;
		}

		if((GLenum)internalformat != format)
		{
			if(gl::IsUnsizedInternalFormat(internalformat))
			{
				return GL_INVALID_OPERATION;
			}

			if(!IsSizedInternalFormat(internalformat))
			{
				return GL_INVALID_VALUE;
			}
		}

		if((GLenum)internalformat == format)
		{
			// Validate format, type, and unsized internalformat combinations [OpenGL ES 1.1 Table 3.3]
			switch(format)
			{
			case GL_RGBA:
				switch(type)
				{
				case GL_UNSIGNED_BYTE:
				case GL_UNSIGNED_SHORT_4_4_4_4:
				case GL_UNSIGNED_SHORT_5_5_5_1:
					break;
				default:
					return GL_INVALID_OPERATION;
				}
				break;
			case GL_RGB:
				switch(type)
				{
				case GL_UNSIGNED_BYTE:
				case GL_UNSIGNED_SHORT_5_6_5:
					break;
				default:
					return GL_INVALID_OPERATION;
				}
				break;
			case GL_LUMINANCE_ALPHA:
			case GL_LUMINANCE:
			case GL_ALPHA:
				switch(type)
				{
				case GL_UNSIGNED_BYTE:
					break;
				default:
					return GL_INVALID_OPERATION;
				}
				break;
			case GL_DEPTH_STENCIL_OES:
				switch(type)
				{
				case GL_UNSIGNED_INT_24_8_OES:   // GL_OES_packed_depth_stencil
					break;
				default:
					return GL_INVALID_OPERATION;
				}
				break;
			case GL_BGRA_EXT:
				if(type != GL_UNSIGNED_BYTE)   // GL_APPLE_texture_format_BGRA8888 / GL_EXT_texture_format_BGRA8888
				{
					return GL_INVALID_OPERATION;
				}
				break;
			default:
				UNREACHABLE(format);
				return GL_INVALID_ENUM;
			}

			return GL_NO_ERROR;
		}

		// Validate format, type, and sized internalformat combinations [OpenGL ES 3.0 Table 3.2]
		bool validSizedInternalformat = false;
		#define VALIDATE_INTERNALFORMAT(...) { GLint validInternalformats[] = {__VA_ARGS__}; for(GLint v : validInternalformats) {if(internalformat == v) validSizedInternalformat = true;} } break;

		switch(format)
		{
		case GL_RGBA:
			switch(type)
			{
			case GL_UNSIGNED_BYTE:               VALIDATE_INTERNALFORMAT(GL_RGBA8_OES, GL_RGB5_A1_OES, GL_RGBA4_OES)
			case GL_UNSIGNED_SHORT_4_4_4_4:      VALIDATE_INTERNALFORMAT(GL_RGBA4_OES)
			case GL_UNSIGNED_SHORT_5_5_5_1:      VALIDATE_INTERNALFORMAT(GL_RGB5_A1_OES)
			default:                             return GL_INVALID_OPERATION;
			}
			break;
		case GL_RGB:
			switch(type)
			{
			case GL_UNSIGNED_BYTE:                VALIDATE_INTERNALFORMAT(GL_RGB8_OES, GL_RGB565_OES)
			case GL_UNSIGNED_SHORT_5_6_5:         VALIDATE_INTERNALFORMAT(GL_RGB565_OES)
			default:                              return GL_INVALID_OPERATION;
			}
			break;
		case GL_DEPTH_STENCIL_OES:
			switch(type)
			{
			case GL_UNSIGNED_INT_24_8_OES: VALIDATE_INTERNALFORMAT(GL_DEPTH24_STENCIL8_OES)
			default:                       return GL_INVALID_OPERATION;
			}
			break;
		case GL_LUMINANCE_ALPHA:
			switch(type)
			{
			case GL_UNSIGNED_BYTE:  VALIDATE_INTERNALFORMAT(GL_LUMINANCE8_ALPHA8_EXT)
			default:
				return GL_INVALID_OPERATION;
			}
			break;
		case GL_LUMINANCE:
			switch(type)
			{
			case GL_UNSIGNED_BYTE:  VALIDATE_INTERNALFORMAT(GL_LUMINANCE8_EXT)
			default:
				return GL_INVALID_OPERATION;
			}
			break;
		case GL_ALPHA:
			switch(type)
			{
			case GL_UNSIGNED_BYTE:  VALIDATE_INTERNALFORMAT(GL_ALPHA8_EXT)
			default:
				return GL_INVALID_OPERATION;
			}
			break;
		case GL_BGRA_EXT:   // GL_APPLE_texture_format_BGRA8888
			switch(type)
			{
			case GL_UNSIGNED_BYTE: VALIDATE_INTERNALFORMAT(GL_BGRA8_EXT)
			default:               return GL_INVALID_OPERATION;
			}
			break;
		default:
			UNREACHABLE(format);
			return GL_INVALID_ENUM;
		}

		#undef VALIDATE_INTERNALFORMAT

		if(!validSizedInternalformat)
		{
			return GL_INVALID_OPERATION;
		}

		return GL_NO_ERROR;
	}

	bool IsColorRenderable(GLint internalformat)
	{
		switch(internalformat)
		{
		case GL_RGBA4_OES:
		case GL_RGB5_A1_OES:
		case GL_RGB565_OES:
		case GL_RGB8_OES:
		case GL_RGBA8_OES:
			return true;
		case GL_DEPTH_COMPONENT16_OES:
		case GL_STENCIL_INDEX8_OES:
		case GL_DEPTH24_STENCIL8_OES:
			return false;
		default:
			UNIMPLEMENTED();
		}

		return false;
	}

	bool IsDepthRenderable(GLint internalformat)
	{
		switch(internalformat)
		{
		case GL_DEPTH_COMPONENT16_OES:
		case GL_DEPTH24_STENCIL8_OES:
			return true;
		case GL_STENCIL_INDEX8_OES:
		case GL_RGBA4_OES:
		case GL_RGB5_A1_OES:
		case GL_RGB565_OES:
		case GL_RGB8_OES:
		case GL_RGBA8_OES:
			return false;
		default:
			UNIMPLEMENTED();
		}

		return false;
	}

	bool IsStencilRenderable(GLint internalformat)
	{
		switch(internalformat)
		{
		case GL_STENCIL_INDEX8_OES:
		case GL_DEPTH24_STENCIL8_OES:
			return true;
		case GL_RGBA4_OES:
		case GL_RGB5_A1_OES:
		case GL_RGB565_OES:
		case GL_RGB8_OES:
		case GL_RGBA8_OES:
		case GL_DEPTH_COMPONENT16_OES:
			return false;
		default:
			UNIMPLEMENTED();
		}

		return false;
	}

	GLuint GetAlphaSize(GLint internalformat)
	{
		switch(internalformat)
		{
		case GL_NONE_OES:    return 0;
		case GL_RGBA4_OES:   return 4;
		case GL_RGB5_A1_OES: return 1;
		case GL_RGB565_OES:  return 0;
		case GL_RGB8_OES:    return 0;
		case GL_RGBA8_OES:   return 8;
		case GL_BGRA8_EXT:   return 8;
		default:
		//	UNREACHABLE(internalformat);
			return 0;
		}
	}

	GLuint GetRedSize(GLint internalformat)
	{
		switch(internalformat)
		{
		case GL_NONE_OES:    return 0;
		case GL_RGBA4_OES:   return 4;
		case GL_RGB5_A1_OES: return 5;
		case GL_RGB565_OES:  return 5;
		case GL_RGB8_OES:    return 8;
		case GL_RGBA8_OES:   return 8;
		case GL_BGRA8_EXT:   return 8;
		default:
		//	UNREACHABLE(internalformat);
			return 0;
		}
	}

	GLuint GetGreenSize(GLint internalformat)
	{
		switch(internalformat)
		{
		case GL_NONE_OES:    return 0;
		case GL_RGBA4_OES:   return 4;
		case GL_RGB5_A1_OES: return 5;
		case GL_RGB565_OES:  return 6;
		case GL_RGB8_OES:    return 8;
		case GL_RGBA8_OES:   return 8;
		case GL_BGRA8_EXT:   return 8;
		default:
		//	UNREACHABLE(internalformat);
			return 0;
		}
	}

	GLuint GetBlueSize(GLint internalformat)
	{
		switch(internalformat)
		{
		case GL_NONE_OES:    return 0;
		case GL_RGBA4_OES:   return 4;
		case GL_RGB5_A1_OES: return 5;
		case GL_RGB565_OES:  return 5;
		case GL_RGB8_OES:    return 8;
		case GL_RGBA8_OES:   return 8;
		case GL_BGRA8_EXT:   return 8;
		default:
		//	UNREACHABLE(internalformat);
			return 0;
		}
	}

	GLuint GetDepthSize(GLint internalformat)
	{
		switch(internalformat)
		{
		case GL_STENCIL_INDEX8_OES:    return 0;
		case GL_DEPTH_COMPONENT16_OES: return 16;
		case GL_DEPTH24_STENCIL8_OES:  return 24;
		default:
		//	UNREACHABLE(internalformat);
			return 0;
		}
	}

	GLuint GetStencilSize(GLint internalformat)
	{
		switch(internalformat)
		{
		case GL_STENCIL_INDEX8_OES:    return 8;
		case GL_DEPTH_COMPONENT16_OES: return 0;
		case GL_DEPTH24_STENCIL8_OES:  return 8;
		default:
		//	UNREACHABLE(internalformat);
			return 0;
		}
	}

	bool IsAlpha(GLint internalformat)
	{
		switch(internalformat)
		{
		case GL_ALPHA8_EXT:
			return true;
		default:
			return false;
		}
	}

	bool IsRGB(GLint internalformat)
	{
		switch(internalformat)
		{
		case GL_LUMINANCE8_EXT:
		case GL_RGB565_OES:
		case GL_RGB8_OES:
		case SW_YV12_BT601:
		case SW_YV12_BT709:
		case SW_YV12_JFIF:
			return true;
		default:
			return false;
		}
	}

	bool IsRGBA(GLint internalformat)
	{
		switch(internalformat)
		{
		case GL_LUMINANCE8_ALPHA8_EXT:
		case GL_RGBA:
		case GL_BGRA8_EXT:     // GL_EXT_texture_format_BGRA8888
		case GL_RGBA4_OES:
		case GL_RGB5_A1_OES:
		case GL_RGBA8_OES:
			return true;
		default:
			return false;
		}
	}
}

namespace es2sw
{
	sw::DepthCompareMode ConvertDepthComparison(GLenum comparison)
	{
		switch(comparison)
		{
		case GL_NEVER:    return sw::DEPTH_NEVER;
		case GL_ALWAYS:   return sw::DEPTH_ALWAYS;
		case GL_LESS:     return sw::DEPTH_LESS;
		case GL_LEQUAL:   return sw::DEPTH_LESSEQUAL;
		case GL_EQUAL:    return sw::DEPTH_EQUAL;
		case GL_GREATER:  return sw::DEPTH_GREATER;
		case GL_GEQUAL:   return sw::DEPTH_GREATEREQUAL;
		case GL_NOTEQUAL: return sw::DEPTH_NOTEQUAL;
		default: UNREACHABLE(comparison);
		}

		return sw::DEPTH_ALWAYS;
	}

	sw::StencilCompareMode ConvertStencilComparison(GLenum comparison)
	{
		switch(comparison)
		{
		case GL_NEVER:    return sw::STENCIL_NEVER;
		case GL_ALWAYS:   return sw::STENCIL_ALWAYS;
		case GL_LESS:     return sw::STENCIL_LESS;
		case GL_LEQUAL:   return sw::STENCIL_LESSEQUAL;
		case GL_EQUAL:    return sw::STENCIL_EQUAL;
		case GL_GREATER:  return sw::STENCIL_GREATER;
		case GL_GEQUAL:   return sw::STENCIL_GREATEREQUAL;
		case GL_NOTEQUAL: return sw::STENCIL_NOTEQUAL;
		default: UNREACHABLE(comparison);
		}

		return sw::STENCIL_ALWAYS;
	}

	sw::AlphaCompareMode ConvertAlphaComparison(GLenum comparison)
	{
		switch(comparison)
		{
		case GL_NEVER:    return sw::ALPHA_NEVER;
		case GL_ALWAYS:   return sw::ALPHA_ALWAYS;
		case GL_LESS:     return sw::ALPHA_LESS;
		case GL_LEQUAL:   return sw::ALPHA_LESSEQUAL;
		case GL_EQUAL:    return sw::ALPHA_EQUAL;
		case GL_GREATER:  return sw::ALPHA_GREATER;
		case GL_GEQUAL:   return sw::ALPHA_GREATEREQUAL;
		case GL_NOTEQUAL: return sw::ALPHA_NOTEQUAL;
		default: UNREACHABLE(comparison);
		}

		return sw::ALPHA_ALWAYS;
	}

	sw::Color<float> ConvertColor(es1::Color color)
	{
		return sw::Color<float>(color.red, color.green, color.blue, color.alpha);
	}

	sw::BlendFactor ConvertBlendFunc(GLenum blend)
	{
		switch(blend)
		{
		case GL_ZERO:                     return sw::BLEND_ZERO;
		case GL_ONE:                      return sw::BLEND_ONE;
		case GL_SRC_COLOR:                return sw::BLEND_SOURCE;
		case GL_ONE_MINUS_SRC_COLOR:      return sw::BLEND_INVSOURCE;
		case GL_DST_COLOR:                return sw::BLEND_DEST;
		case GL_ONE_MINUS_DST_COLOR:      return sw::BLEND_INVDEST;
		case GL_SRC_ALPHA:                return sw::BLEND_SOURCEALPHA;
		case GL_ONE_MINUS_SRC_ALPHA:      return sw::BLEND_INVSOURCEALPHA;
		case GL_DST_ALPHA:                return sw::BLEND_DESTALPHA;
		case GL_ONE_MINUS_DST_ALPHA:      return sw::BLEND_INVDESTALPHA;
		case GL_SRC_ALPHA_SATURATE:       return sw::BLEND_SRCALPHASAT;
		default: UNREACHABLE(blend);
		}

		return sw::BLEND_ZERO;
	}

	sw::BlendOperation ConvertBlendOp(GLenum blendOp)
	{
		switch(blendOp)
		{
		case GL_FUNC_ADD_OES:              return sw::BLENDOP_ADD;
		case GL_FUNC_SUBTRACT_OES:         return sw::BLENDOP_SUB;
		case GL_FUNC_REVERSE_SUBTRACT_OES: return sw::BLENDOP_INVSUB;
		case GL_MIN_EXT:                   return sw::BLENDOP_MIN;
		case GL_MAX_EXT:                   return sw::BLENDOP_MAX;
		default: UNREACHABLE(blendOp);
		}

		return sw::BLENDOP_ADD;
	}

	sw::LogicalOperation ConvertLogicalOperation(GLenum logicalOperation)
	{
		switch(logicalOperation)
		{
		case GL_CLEAR:         return sw::LOGICALOP_CLEAR;
		case GL_SET:           return sw::LOGICALOP_SET;
		case GL_COPY:          return sw::LOGICALOP_COPY;
		case GL_COPY_INVERTED: return sw::LOGICALOP_COPY_INVERTED;
		case GL_NOOP:          return sw::LOGICALOP_NOOP;
		case GL_INVERT:        return sw::LOGICALOP_INVERT;
		case GL_AND:           return sw::LOGICALOP_AND;
		case GL_NAND:          return sw::LOGICALOP_NAND;
		case GL_OR:            return sw::LOGICALOP_OR;
		case GL_NOR:           return sw::LOGICALOP_NOR;
		case GL_XOR:           return sw::LOGICALOP_XOR;
		case GL_EQUIV:         return sw::LOGICALOP_EQUIV;
		case GL_AND_REVERSE:   return sw::LOGICALOP_AND_REVERSE;
		case GL_AND_INVERTED:  return sw::LOGICALOP_AND_INVERTED;
		case GL_OR_REVERSE:    return sw::LOGICALOP_OR_REVERSE;
		case GL_OR_INVERTED:   return sw::LOGICALOP_OR_INVERTED;
		default: UNREACHABLE(logicalOperation);
		}

		return sw::LOGICALOP_COPY;
	}

	sw::StencilOperation ConvertStencilOp(GLenum stencilOp)
	{
		switch(stencilOp)
		{
		case GL_ZERO:          return sw::OPERATION_ZERO;
		case GL_KEEP:          return sw::OPERATION_KEEP;
		case GL_REPLACE:       return sw::OPERATION_REPLACE;
		case GL_INCR:          return sw::OPERATION_INCRSAT;
		case GL_DECR:          return sw::OPERATION_DECRSAT;
		case GL_INVERT:        return sw::OPERATION_INVERT;
		case GL_INCR_WRAP_OES: return sw::OPERATION_INCR;
		case GL_DECR_WRAP_OES: return sw::OPERATION_DECR;
		default: UNREACHABLE(stencilOp);
		}

		return sw::OPERATION_KEEP;
	}

	sw::AddressingMode ConvertTextureWrap(GLenum wrap)
	{
		switch(wrap)
		{
		case GL_REPEAT:              return sw::ADDRESSING_WRAP;
		case GL_CLAMP_TO_EDGE:       return sw::ADDRESSING_CLAMP;
		case GL_MIRRORED_REPEAT_OES: return sw::ADDRESSING_MIRROR;
		default: UNREACHABLE(wrap);
		}

		return sw::ADDRESSING_WRAP;
	}

	sw::CullMode ConvertCullMode(GLenum cullFace, GLenum frontFace)
	{
		switch(cullFace)
		{
		case GL_FRONT:
			return (frontFace == GL_CCW ? sw::CULL_CLOCKWISE : sw::CULL_COUNTERCLOCKWISE);
		case GL_BACK:
			return (frontFace == GL_CCW ? sw::CULL_COUNTERCLOCKWISE : sw::CULL_CLOCKWISE);
		case GL_FRONT_AND_BACK:
			return sw::CULL_NONE;   // culling will be handled during draw
		default: UNREACHABLE(cullFace);
		}

		return sw::CULL_COUNTERCLOCKWISE;
	}

	unsigned int ConvertColorMask(bool red, bool green, bool blue, bool alpha)
	{
		return (red   ? 0x00000001 : 0) |
			   (green ? 0x00000002 : 0) |
			   (blue  ? 0x00000004 : 0) |
			   (alpha ? 0x00000008 : 0);
	}

	sw::MipmapType ConvertMipMapFilter(GLenum minFilter)
	{
		switch(minFilter)
		{
		case GL_NEAREST:
		case GL_LINEAR:
			return sw::MIPMAP_NONE;
			break;
		case GL_NEAREST_MIPMAP_NEAREST:
		case GL_LINEAR_MIPMAP_NEAREST:
			return sw::MIPMAP_POINT;
			break;
		case GL_NEAREST_MIPMAP_LINEAR:
		case GL_LINEAR_MIPMAP_LINEAR:
			return sw::MIPMAP_LINEAR;
			break;
		default:
			UNREACHABLE(minFilter);
			return sw::MIPMAP_NONE;
		}
	}

	sw::FilterType ConvertTextureFilter(GLenum minFilter, GLenum magFilter, float maxAnisotropy)
	{
		if(maxAnisotropy > 1.0f)
		{
			return sw::FILTER_ANISOTROPIC;
		}

		sw::FilterType magFilterType = sw::FILTER_POINT;
		switch(magFilter)
		{
		case GL_NEAREST: magFilterType = sw::FILTER_POINT;  break;
		case GL_LINEAR:  magFilterType = sw::FILTER_LINEAR; break;
		default: UNREACHABLE(magFilter);
		}

		switch(minFilter)
		{
		case GL_NEAREST:
		case GL_NEAREST_MIPMAP_NEAREST:
		case GL_NEAREST_MIPMAP_LINEAR:
			return (magFilterType == sw::FILTER_POINT) ? sw::FILTER_POINT : sw::FILTER_MIN_POINT_MAG_LINEAR;
		case GL_LINEAR:
		case GL_LINEAR_MIPMAP_NEAREST:
		case GL_LINEAR_MIPMAP_LINEAR:
			return (magFilterType == sw::FILTER_POINT) ? sw::FILTER_MIN_LINEAR_MAG_POINT : sw::FILTER_LINEAR;
		default:
			UNREACHABLE(minFilter);
			return (magFilterType == sw::FILTER_POINT) ? sw::FILTER_POINT : sw::FILTER_MIN_POINT_MAG_LINEAR;
		}
	}

	bool ConvertPrimitiveType(GLenum primitiveType, GLsizei elementCount, GLenum elementType,  sw::DrawType &drawType, int &primitiveCount)
	{
		switch(primitiveType)
		{
		case GL_POINTS:
			drawType = sw::DRAW_POINTLIST;
			primitiveCount = elementCount;
			break;
		case GL_LINES:
			drawType = sw::DRAW_LINELIST;
			primitiveCount = elementCount / 2;
			break;
		case GL_LINE_LOOP:
			drawType = sw::DRAW_LINELOOP;
			primitiveCount = elementCount;
			break;
		case GL_LINE_STRIP:
			drawType = sw::DRAW_LINESTRIP;
			primitiveCount = elementCount - 1;
			break;
		case GL_TRIANGLES:
			drawType = sw::DRAW_TRIANGLELIST;
			primitiveCount = elementCount / 3;
			break;
		case GL_TRIANGLE_STRIP:
			drawType = sw::DRAW_TRIANGLESTRIP;
			primitiveCount = elementCount - 2;
			break;
		case GL_TRIANGLE_FAN:
			drawType = sw::DRAW_TRIANGLEFAN;
			primitiveCount = elementCount - 2;
			break;
		default:
			return false;
		}

		sw::DrawType elementSize;
		switch(elementType)
		{
		case GL_NONE_OES:       elementSize = sw::DRAW_NONINDEXED; break;
		case GL_UNSIGNED_BYTE:  elementSize = sw::DRAW_INDEXED8;   break;
		case GL_UNSIGNED_SHORT: elementSize = sw::DRAW_INDEXED16;  break;
		case GL_UNSIGNED_INT:   elementSize = sw::DRAW_INDEXED32;  break;
		default: return false;
		}

		drawType = sw::DrawType(drawType | elementSize);

		return true;
	}

	sw::TextureStage::StageOperation ConvertCombineOperation(GLenum operation)
	{
		switch(operation)
		{
		case GL_REPLACE:        return sw::TextureStage::STAGE_SELECTARG1;
		case GL_MODULATE:       return sw::TextureStage::STAGE_MODULATE;
		case GL_ADD:            return sw::TextureStage::STAGE_ADD;
		case GL_ADD_SIGNED:     return sw::TextureStage::STAGE_ADDSIGNED;
		case GL_INTERPOLATE:    return sw::TextureStage::STAGE_LERP;
		case GL_SUBTRACT:       return sw::TextureStage::STAGE_SUBTRACT;
		case GL_DOT3_RGB:       return sw::TextureStage::STAGE_DOT3;
		case GL_DOT3_RGBA:      return sw::TextureStage::STAGE_DOT3;
		default: UNREACHABLE(operation); return sw::TextureStage::STAGE_SELECTARG1;
		}
	}

	sw::TextureStage::SourceArgument ConvertSourceArgument(GLenum argument)
	{
		switch(argument)
		{
		case GL_TEXTURE:        return sw::TextureStage::SOURCE_TEXTURE;
		case GL_CONSTANT:       return sw::TextureStage::SOURCE_CONSTANT;
		case GL_PRIMARY_COLOR:  return sw::TextureStage::SOURCE_DIFFUSE;
		case GL_PREVIOUS:       return sw::TextureStage::SOURCE_CURRENT;
		default: UNREACHABLE(argument); return sw::TextureStage::SOURCE_CURRENT;
		}
	}

	sw::TextureStage::ArgumentModifier ConvertSourceOperand(GLenum operand)
	{
		switch(operand)
		{
		case GL_SRC_COLOR:           return sw::TextureStage::MODIFIER_COLOR;
		case GL_ONE_MINUS_SRC_COLOR: return sw::TextureStage::MODIFIER_INVCOLOR;
		case GL_SRC_ALPHA:           return sw::TextureStage::MODIFIER_ALPHA;
		case GL_ONE_MINUS_SRC_ALPHA: return sw::TextureStage::MODIFIER_INVALPHA;
		default: UNREACHABLE(operand);      return sw::TextureStage::MODIFIER_COLOR;
		}
	}
}

namespace sw2es
{
	GLenum ConvertBackBufferFormat(sw::Format format)
	{
		switch(format)
		{
		case sw::FORMAT_A4R4G4B4: return GL_RGBA4_OES;
		case sw::FORMAT_A8R8G8B8: return GL_RGBA8_OES;
		case sw::FORMAT_A8B8G8R8: return GL_RGBA8_OES;
		case sw::FORMAT_A1R5G5B5: return GL_RGB5_A1_OES;
		case sw::FORMAT_R5G6B5:   return GL_RGB565_OES;
		case sw::FORMAT_X8R8G8B8: return GL_RGB8_OES;
		case sw::FORMAT_X8B8G8R8: return GL_RGB8_OES;
		default:
			UNREACHABLE(format);
		}

		return GL_RGBA4_OES;
	}

	GLenum ConvertDepthStencilFormat(sw::Format format)
	{
		switch(format)
		{
		case sw::FORMAT_D16:
		case sw::FORMAT_D24X8:
		case sw::FORMAT_D32:
			return GL_DEPTH_COMPONENT16_OES;
		case sw::FORMAT_D24S8:
			return GL_DEPTH24_STENCIL8_OES;
		default:
			UNREACHABLE(format);
		}

		return GL_DEPTH24_STENCIL8_OES;
	}
}