#include "stdafx.h"

////////////////////////////////////////////////////////////////////////////////////////////////////
// グローバル宣言
////////////////////////////////////////////////////////////////////////////////////////////////////

struct DDS_PIXELFORMAT
{
	DWORD size;
	DWORD flags; //DDS_PIXELFORMAT_FLAGS
	DWORD fourCC;
	DWORD rgbBitCount;
	DWORD rBitMask;
	DWORD gBitMask;
	DWORD bBitMask;
	DWORD rgbAlphaBitMask;
};

struct DDS_CAPS
{
	DWORD caps1; //DDS_CAPS1
	DWORD caps2; //DDS_CAPS2
	DWORD reserved[2];
};

struct DDS_DESC
{
	DWORD size;
	DWORD flags; //DDS_FLAGS
	DWORD height;
	DWORD width;
	DWORD pitchOrLinearSize;
	DWORD depth;
	DWORD mipMapCount;
	DWORD reserved1[11];
	DDS_PIXELFORMAT pixelFormat;
	DDS_CAPS caps;
	DWORD reserved2;
};

int Load_BC( Mix::Plugin::IReader* pSrc, Mix::Plugin::Graphics::ITextureKit* pDst, const DDS_DESC& desc, unsigned int mipLevel, unsigned int depth );
int Load_R8G8B8( Mix::Plugin::IReader* pSrc, Mix::Plugin::Graphics::ITextureKit* pDst, const DDS_DESC& desc, unsigned int mipLevel, unsigned int depth );
int Load_R8G8B8A8( Mix::Plugin::IReader* pSrc, Mix::Plugin::Graphics::ITextureKit* pDst, const DDS_DESC& desc, unsigned int mipLevel, unsigned int depth );

typedef int ( *LoadFunc )( Mix::Plugin::IReader* pSrc, Mix::Plugin::Graphics::ITextureKit* pDst, const DDS_DESC& desc, unsigned int mipLevel, unsigned int depth );

static Mix::Plugin::INFORMATION g_Info = { Mix::Plugin::TEXTURE_LOADER, L"DDS", L"1.0.0.0" };

static unsigned int CUBE_PLANE_TABLE[6][2] =
{
	{ DDSCAPS2_CUBEMAP_POSITIVEX, Mix::Plugin::Graphics::ITextureKit::CUBE_POSITIVEX, },
	{ DDSCAPS2_CUBEMAP_NEGATIVEX, Mix::Plugin::Graphics::ITextureKit::CUBE_NEGATIVEX, },
	{ DDSCAPS2_CUBEMAP_POSITIVEY, Mix::Plugin::Graphics::ITextureKit::CUBE_POSITIVEY, },
	{ DDSCAPS2_CUBEMAP_NEGATIVEY, Mix::Plugin::Graphics::ITextureKit::CUBE_NEGATIVEY, },
	{ DDSCAPS2_CUBEMAP_POSITIVEZ, Mix::Plugin::Graphics::ITextureKit::CUBE_POSITIVEZ, },
	{ DDSCAPS2_CUBEMAP_NEGATIVEZ, Mix::Plugin::Graphics::ITextureKit::CUBE_NEGATIVEZ, },
};

static unsigned int CUBE_PLANE_COUNT = sizeof( CUBE_PLANE_TABLE ) / sizeof( unsigned int[2] );

////////////////////////////////////////////////////////////////////////////////////////////////////
// プラグイン情報の取得
////////////////////////////////////////////////////////////////////////////////////////////////////

const Mix::Plugin::INFORMATION& __stdcall GetInformation( void )
{
	return g_Info;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// ロード
////////////////////////////////////////////////////////////////////////////////////////////////////

int __stdcall Load( Mix::Plugin::IReader* pSrc, Mix::Plugin::Graphics::ITextureKit* pDst )
{
	char magic[4];
	DDS_DESC desc;
	LoadFunc loadFunc;
	int result;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// マジック値
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( pSrc->Read( magic, sizeof( char[4] ) ) != sizeof( char[4] ) )
	{
		return Mix::Plugin::Graphics::TP_NOT_SUPPORTED;
	}

	if( ::strncmp( magic, "DDS", 3 ) != 0 )
	{
		return Mix::Plugin::Graphics::TP_NOT_SUPPORTED;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ヘッダ
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( pSrc->Read( &desc, sizeof( DDS_DESC ) ) != sizeof( DDS_DESC ) )
	{
		return Mix::Plugin::Graphics::TP_ILLEGAL_FORMAT;
	}

	if( ( ( desc.flags & DDSD_CAPS ) != DDSD_CAPS ) ||
		( ( desc.flags & DDSD_HEIGHT ) != DDSD_HEIGHT ) ||
		( ( desc.flags & DDSD_WIDTH ) != DDSD_WIDTH ) ||
		( ( desc.flags & DDSD_PIXELFORMAT ) != DDSD_PIXELFORMAT ) )
	{
		// DDSファイルに必要なものがそろっていない
		return Mix::Plugin::Graphics::TP_ILLEGAL_FORMAT;
	}

	if( ( desc.flags & DDSD_MIPMAPCOUNT ) != DDSD_MIPMAPCOUNT )
	{
		// ミップマップが 0 の場合はサブリースの確保のため最低 1 にしておく。
		desc.mipMapCount = 1;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// フォーマットとロードファンクションの決定
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( ( desc.pixelFormat.flags & DDPF_FOURCC ) == DDPF_FOURCC )
	{
		switch( desc.pixelFormat.fourCC )
		{
		case FOURCC_DXT1:
			pDst->SetFormat( Mix::Plugin::Graphics::ITextureKit::BC1 );
			loadFunc = Load_BC;
			break;
		case FOURCC_DXT3:
			pDst->SetFormat( Mix::Plugin::Graphics::ITextureKit::BC2 );
			loadFunc = Load_BC;
			break;
		case FOURCC_DXT5:
			pDst->SetFormat( Mix::Plugin::Graphics::ITextureKit::BC3 );
			loadFunc = Load_BC;
			break;

		default:
			return Mix::Plugin::Graphics::TP_NOT_SUPPORTED;
		}
	}
	else
	{
		if( ( desc.pixelFormat.flags & DDPF_RGB ) == DDPF_RGB )
		{
			if( ( desc.pixelFormat.flags & DDPF_ALPHAPIXELS ) == DDPF_ALPHAPIXELS )
			{
				if( ( desc.pixelFormat.rgbAlphaBitMask == 0xFF000000 ) &&
					( desc.pixelFormat.rBitMask == 0x00FF0000 ) &&
					( desc.pixelFormat.gBitMask == 0x0000FF00 ) &&
					( desc.pixelFormat.bBitMask == 0x000000FF ) )
				{
					pDst->SetFormat( Mix::Plugin::Graphics::ITextureKit::R8G8B8A8 );
					loadFunc = Load_R8G8B8A8;
				}
			}
			else
			{
				if( ( desc.pixelFormat.rBitMask == 0x00FF0000 ) &&
					( desc.pixelFormat.gBitMask == 0x0000FF00 ) &&
					( desc.pixelFormat.bBitMask == 0x000000FF ) )
				{
					pDst->SetFormat( Mix::Plugin::Graphics::ITextureKit::R8G8B8A8 );
					loadFunc = Load_R8G8B8;
				}
			}
		}
		else if( ( desc.pixelFormat.flags & DDPF_ALPHA ) == DDPF_ALPHA )
		{
			if( desc.pixelFormat.rgbAlphaBitMask == 0xFF000000 )
			{
				return Mix::Plugin::Graphics::TP_NOT_SUPPORTED;
			}
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ロード
	////////////////////////////////////////////////////////////////////////////////////////////////////

	pDst->SetSize( desc.width, desc.height );
	pDst->SetDepth( desc.depth );
	pDst->SetMipLevels( desc.mipMapCount );

	if( ( desc.caps.caps2 & DDSCAPS2_CUBEMAP ) == DDSCAPS2_CUBEMAP )
	{
		unsigned int flags = 0;

		pDst->SetType( Mix::Plugin::Graphics::ITextureKit::CUBE );

		result = Mix::Plugin::Graphics::TP_OK;

		for( unsigned int i = 0; ( i < CUBE_PLANE_COUNT ) && ( result == Mix::Plugin::Graphics::TP_OK ); i++ )
		{
			if( ( desc.caps.caps2 & CUBE_PLANE_TABLE[i][0] ) == CUBE_PLANE_TABLE[i][0] )
			{
				for( unsigned int j = 0; j < desc.mipMapCount; j++ )
				{
					result = loadFunc( pSrc, pDst, desc, j, 1 );

					if( result == Mix::Plugin::Graphics::TP_OK )
					{
						flags |= CUBE_PLANE_TABLE[i][1];
					}
				}
			}
		}

		if( flags != 0 )
		{
			pDst->SetFlags( flags );
		}
		else
		{
			result = Mix::Plugin::Graphics::TP_ILLEGAL_FORMAT;
		}
	}
	else if( ( desc.caps.caps2 & DDSCAPS2_VOLUME ) == DDSCAPS2_VOLUME )
	{
		DWORD sliceCount = desc.depth;

		pDst->SetType( Mix::Plugin::Graphics::ITextureKit::VOLUME );

		for( unsigned int i = 0; i < desc.mipMapCount; i++ )
		{
			result = loadFunc( pSrc, pDst, desc, i, sliceCount );
			if( result != Mix::Plugin::Graphics::TP_OK )
			{
				break;
			}

			sliceCount = max( 1, sliceCount >> 1 );
		}
	}
	else
	{
		pDst->SetType( Mix::Plugin::Graphics::ITextureKit::PLANE );

		for( unsigned int i = 0; i < desc.mipMapCount; i++ )
		{
			result = loadFunc( pSrc, pDst, desc, i, 1 );
			if( result != Mix::Plugin::Graphics::TP_OK )
			{
				break;
			}
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////

	return result;
}

int Load_BC( Mix::Plugin::IReader* pSrc, Mix::Plugin::Graphics::ITextureKit* pDst, const DDS_DESC& desc, unsigned int mipLevel, unsigned int sliceCount )
{
	unsigned int width;
	unsigned int height;

	if( mipLevel > 0 )
	{
		width = desc.width >> mipLevel;
		height = desc.height >> mipLevel;
	}
	else
	{
		width = desc.width;
		height = desc.height;
	}

	Mix::Plugin::Graphics::ITextureKit::FORMAT format;
	unsigned int minSize;

	switch( desc.pixelFormat.fourCC )
	{
	case FOURCC_DXT1: //BC1 ( Width * Height / 2 ) 1/8
		format = Mix::Plugin::Graphics::ITextureKit::BC1;
		minSize = 8;
		break;
	case FOURCC_DXT3: //BC2 ( Width * Height ) 1/4
		format = Mix::Plugin::Graphics::ITextureKit::BC2;
		minSize = 16;
		break;
	case FOURCC_DXT5: //BC3 ( Width * Height ) 1/4
		format = Mix::Plugin::Graphics::ITextureKit::BC3;
		minSize = 16;
		break;

	default:
		return Mix::Plugin::Graphics::TP_NOT_SUPPORTED;
	}

	pDst->SetFormat( format );
	
	unsigned int memRowPitch = max( 1, width >> 2 ) * minSize;
	unsigned int memSlicePitch = max( 1, width >> 2 ) * max( 1, height >> 2 ) * minSize;
	unsigned int memSize = memSlicePitch * sliceCount;

	unsigned int result;
	const void* pMem = pSrc->Read( memSize, result );

	if( memSize != result )
	{
		return Mix::Plugin::Graphics::TP_ILLEGAL_FORMAT;
	}

	pDst->AddSubResourceData( pMem, memSize, memRowPitch, memSlicePitch );

	width >>= 1;
	height >>= 1;

	return Mix::Plugin::Graphics::TP_OK;
}

int Load_R8G8B8( Mix::Plugin::IReader* pSrc, Mix::Plugin::Graphics::ITextureKit* pDst, const DDS_DESC& desc, unsigned int mipLevel, unsigned int sliceCount )
{
	unsigned int width;
	unsigned int height;

	if( mipLevel > 0 )
	{
		width = desc.width >> mipLevel;
		height = desc.height >> mipLevel;
	}
	else
	{
		width = desc.width;
		height = desc.height;
	}

	pDst->SetFormat( Mix::Plugin::Graphics::ITextureKit::R8G8B8A8 );

	unsigned int srcPitch = width * 3;

	unsigned int memRowPitch = width * 4;
	unsigned int memSlicePitch = memRowPitch * height;
	unsigned int memSize = memRowPitch * sliceCount;

	unsigned char* pTexDstBegin;
	unsigned char* pTexDst;

	const unsigned char* pTexSrc;
	const unsigned char* pTexSrcEnd;
		
	pTexDstBegin = reinterpret_cast<unsigned char*>( pDst->AddSubResourceData( memSize, memRowPitch, memSlicePitch ) );
	if( pTexDstBegin == NULL )
	{
		return Mix::Plugin::Graphics::TP_OUT_OF_MEMORY;
	}

	for( unsigned int y = 0; y < height; y++ )
	{
		unsigned int readResult;
		const void* pRow = pSrc->Read( srcPitch, readResult );

		if( srcPitch != readResult )
		{
			return Mix::Plugin::Graphics::TP_ILLEGAL_FORMAT;
		}
			
		pTexDst = pTexDstBegin;

		pTexSrc = reinterpret_cast<const unsigned char*>( pRow );
		pTexSrcEnd = pTexSrc + srcPitch;

		while( pTexSrc != pTexSrcEnd )
		{
			*pTexDst++ = *pTexSrc++;
			*pTexDst++ = *pTexSrc++;
			*pTexDst++ = *pTexSrc++;
			*pTexDst++ = 0xFF;
		}

		pTexDstBegin += memRowPitch;
	}

	return Mix::Plugin::Graphics::TP_OK;
}

int Load_R8G8B8A8( Mix::Plugin::IReader* pSrc, Mix::Plugin::Graphics::ITextureKit* pDst, const DDS_DESC& desc, unsigned int mipLevel, unsigned int sliceCount )
{
	unsigned int width;
	unsigned int height;

	if( mipLevel > 0 )
	{
		width = desc.width >> mipLevel;
		height = desc.height >> mipLevel;
	}
	else
	{
		width = desc.width;
		height = desc.height;
	}

	pDst->SetFormat( Mix::Plugin::Graphics::ITextureKit::R8G8B8A8 );

	unsigned int readResult;

	unsigned int memRowPitch = width << 2;
	unsigned int memSlicePitch = memRowPitch * height;
	unsigned int memSize = memSlicePitch * sliceCount;
	const void* pMem = pSrc->Read( memSize, readResult );

	if( memSize != readResult )
	{
		return Mix::Plugin::Graphics::TP_ILLEGAL_FORMAT;
	}

	pDst->AddSubResourceData( pMem, memSize, memRowPitch );

	return Mix::Plugin::Graphics::TP_OK;
}
