/*
-----------------------------------------------------------------------------
This source file is part of OGRE
    (Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright (c) 2000-2005 The OGRE Team
Also see acknowledgements in Readme.html

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt.
-----------------------------------------------------------------------------
*/
#include "OgreD3D9HardwarePixelBuffer.h"
#include "OgreD3D9Texture.h"
#include "OgreException.h"
#include "OgreLogManager.h"
#include "OgreStringConverter.h"
#include "OgreBitwise.h"

#include "OgreNoMemoryMacros.h"
#include <d3dx9.h>
#include <dxerr9.h>
#include "OgreMemoryMacros.h"

namespace Ogre {

//-----------------------------------------------------------------------------  

D3D9HardwarePixelBuffer::D3D9HardwarePixelBuffer(HardwareBuffer::Usage usage):
	HardwarePixelBuffer(0, 0, 0, PF_UNKNOWN, usage, false, false),
	mpDev(0),
	mSurface(0), mVolume(0), mTempSurface(0), mTempVolume(0),
	mDoMipmapGen(0), mHWMipmaps(0), mMipTex(0)
{
}
D3D9HardwarePixelBuffer::~D3D9HardwarePixelBuffer()
{
}
//-----------------------------------------------------------------------------  
void D3D9HardwarePixelBuffer::bind(IDirect3DDevice9 *dev, IDirect3DSurface9 *surface)
{
	mpDev = dev;
	mSurface = surface;
	
	D3DSURFACE_DESC desc;
	if(mSurface->GetDesc(&desc) != D3D_OK)
		OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "Could not get surface information",
		 "D3D9HardwarePixelBuffer::D3D9HardwarePixelBuffer");
	mWidth = desc.Width;
	mHeight = desc.Height;
	mDepth = 1;
	mFormat = D3D9Texture::_getPF(desc.Format);
	// Default
	mRowPitch = mWidth;
	mSlicePitch = mHeight*mWidth;
	mSizeInBytes = PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat);
}
//-----------------------------------------------------------------------------
void D3D9HardwarePixelBuffer::bind(IDirect3DDevice9 *dev, IDirect3DVolume9 *volume)
{
	mpDev = dev;
	mVolume = volume;
	
	D3DVOLUME_DESC desc;
	if(mVolume->GetDesc(&desc) != D3D_OK)
		OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "Could not get volume information",
		 "D3D9HardwarePixelBuffer::D3D9HardwarePixelBuffer");
	mWidth = desc.Width;
	mHeight = desc.Height;
	mDepth = desc.Depth;
	mFormat = D3D9Texture::_getPF(desc.Format);
	// Default
	mRowPitch = mWidth;
	mSlicePitch = mHeight*mWidth;
	mSizeInBytes = PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat);
}
//-----------------------------------------------------------------------------  
// Util functions to convert a D3D locked box to a pixel box
void fromD3DLock(PixelBox &rval, const D3DLOCKED_RECT &lrect)
{
	rval.rowPitch = lrect.Pitch / PixelUtil::getNumElemBytes(rval.format);
	rval.slicePitch = rval.rowPitch * rval.getHeight();
	assert((lrect.Pitch % PixelUtil::getNumElemBytes(rval.format))==0);
	rval.data = lrect.pBits;
}
void fromD3DLock(PixelBox &rval, const D3DLOCKED_BOX &lbox)
{
	rval.rowPitch = lbox.RowPitch / PixelUtil::getNumElemBytes(rval.format);
	rval.slicePitch = lbox.SlicePitch / PixelUtil::getNumElemBytes(rval.format);
	assert((lbox.RowPitch % PixelUtil::getNumElemBytes(rval.format))==0);
	assert((lbox.SlicePitch % PixelUtil::getNumElemBytes(rval.format))==0);
	rval.data = lbox.pBits;
}
// Convert Ogre integer Box to D3D rectangle
RECT toD3DRECT(const Box &lockBox)
{
	RECT prect;
	assert(lockBox.getDepth() == 1);
	prect.left = lockBox.left;
	prect.right = lockBox.right;
	prect.top = lockBox.top;
	prect.bottom = lockBox.bottom;
	return prect;
}
// Convert Ogre integer Box to D3D box
D3DBOX toD3DBOX(const Box &lockBox)
{
	D3DBOX pbox;
	pbox.Left = lockBox.left;
	pbox.Right = lockBox.right;
	pbox.Top = lockBox.top;
	pbox.Bottom = lockBox.bottom;
	pbox.Front = lockBox.front;
	pbox.Back = lockBox.back;
	return pbox;
}
// Convert Ogre pixelbox extent to D3D rectangle
RECT toD3DRECTExtent(const PixelBox &lockBox)
{
	RECT prect;
	assert(lockBox.getDepth() == 1);
	prect.left = 0;
	prect.right = lockBox.getWidth();
	prect.top = 0;
	prect.bottom = lockBox.getHeight();
	return prect;
}
// Convert Ogre pixelbox extent to D3D box
D3DBOX toD3DBOXExtent(const PixelBox &lockBox)
{
	D3DBOX pbox;
	pbox.Left = 0;
	pbox.Right = lockBox.getWidth();
	pbox.Top = 0;
	pbox.Bottom = lockBox.getHeight();
	pbox.Front = 0;
	pbox.Back = lockBox.getDepth();
	return pbox;
}
//-----------------------------------------------------------------------------  
PixelBox D3D9HardwarePixelBuffer::lockImpl(const Image::Box lockBox,  LockOptions options)
{
	// Set extents and format
	PixelBox rval(lockBox, mFormat);
	// Set locking flags according to options
	DWORD flags = 0;
	switch(options)
	{
	case HBL_DISCARD:
		// D3D only likes D3DLOCK_DISCARD if you created the texture with D3DUSAGE_DYNAMIC
		// debug runtime flags this up, could cause problems on some drivers
		if (mUsage & HBU_DYNAMIC)
			flags |= D3DLOCK_DISCARD;
		break;
	case HBL_READ_ONLY:
		flags |= D3DLOCK_READONLY;
		break;
	default: 
		break;
	};
	
	if(mSurface) 
	{
		// Surface
		D3DLOCKED_RECT lrect; // Filled in by D3D
		HRESULT hr;

		if (lockBox.left == 0 && lockBox.top == 0 
			&& lockBox.right == mWidth && lockBox.bottom == mHeight)
		{
			// Lock whole surface
			hr = mSurface->LockRect(&lrect, NULL, flags);
		}
		else
		{
			RECT prect = toD3DRECT(lockBox); // specify range to lock
			hr = mSurface->LockRect(&lrect, &prect, flags);
		}
		if (FAILED(hr))		
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "Surface locking failed",
		 		"D3D9HardwarePixelBuffer::lockImpl");
		fromD3DLock(rval, lrect);
	} 
	else 
	{
		// Volume
		D3DBOX pbox = toD3DBOX(lockBox); // specify range to lock
		D3DLOCKED_BOX lbox; // Filled in by D3D
		
		if(mVolume->LockBox(&lbox, &pbox, flags) != D3D_OK)
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "Volume locking failed",
		 		"D3D9HardwarePixelBuffer::lockImpl");
		fromD3DLock(rval, lbox);
	}

	return rval;
}
//-----------------------------------------------------------------------------  
void D3D9HardwarePixelBuffer::unlockImpl(void)
{
	if(mSurface) 
	{
		// Surface
		mSurface->UnlockRect();
	} else {
		// Volume
		mVolume->UnlockBox();
	}
	if(mDoMipmapGen)
		_genMipmaps();
}
//-----------------------------------------------------------------------------  
void D3D9HardwarePixelBuffer::blit(HardwarePixelBuffer *rsrc, const Image::Box &srcBox, const Image::Box &dstBox)
{
	D3D9HardwarePixelBuffer *src = static_cast<D3D9HardwarePixelBuffer*>(rsrc);
	if(mSurface && src->mSurface)
	{
		// Surface-to-surface
		RECT dsrcRect = toD3DRECT(srcBox);
		RECT ddestRect = toD3DRECT(dstBox);
		// D3DXLoadSurfaceFromSurface
		if(D3DXLoadSurfaceFromSurface(
			mSurface, NULL, &ddestRect, 
			src->mSurface, NULL, &dsrcRect,
			 D3DX_DEFAULT, 0) != D3D_OK)
		{
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "D3DXLoadSurfaceFromSurface failed",
		 		"D3D9HardwarePixelBuffer::blit");
		}
	}
	else if(mVolume && src->mVolume)
	{
		// Volume-to-volume
		D3DBOX dsrcBox = toD3DBOX(srcBox);
		D3DBOX ddestBox = toD3DBOX(dstBox);
		
		// D3DXLoadVolumeFromVolume
		if(D3DXLoadVolumeFromVolume(
			mVolume, NULL, &ddestBox, 
			src->mVolume, NULL, &dsrcBox,
			 D3DX_DEFAULT, 0) != D3D_OK)
		{
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "D3DXLoadVolumeFromVolume failed",
		 		"D3D9HardwarePixelBuffer::blit");
		}
	}
	else
	{
		HardwarePixelBuffer::blit(src, srcBox, dstBox);
	}
}
//-----------------------------------------------------------------------------  
void D3D9HardwarePixelBuffer::blitFromMemory(const PixelBox &src, const Image::Box &dstBox)
{
	// for scoped deletion of conversion buffer
	MemoryDataStreamPtr buf;
	PixelBox converted = src;

	// convert to pixelbuffer's native format if necessary
	if (D3D9Texture::_getPF(src.format) == D3DFMT_UNKNOWN)
	{
		buf.bind(new MemoryDataStream(
			PixelUtil::getMemorySize(src.getWidth(), src.getHeight(), src.getDepth(),
										mFormat)));
		converted = PixelBox(src.getWidth(), src.getHeight(), src.getDepth(), mFormat, buf->getPtr());
		PixelUtil::bulkPixelConversion(src, converted);
	}

	if(mSurface)
	{
		RECT destRect, srcRect;
		srcRect = toD3DRECTExtent(converted);
		destRect = toD3DRECT(dstBox);
		
		if(D3DXLoadSurfaceFromMemory(mSurface, NULL, &destRect, 
			converted.data, D3D9Texture::_getPF(converted.format),
			converted.rowPitch * PixelUtil::getNumElemBytes(converted.format),
			NULL, &srcRect, D3DX_DEFAULT, 0) != D3D_OK)
		{
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "D3DXLoadSurfaceFromMemory failed",
		 		"D3D9HardwarePixelBuffer::blitFromMemory");
		}
	}
	else
	{
		D3DBOX destBox, srcBox;
		srcBox = toD3DBOXExtent(converted);
		destBox = toD3DBOX(dstBox);
		
		if(D3DXLoadVolumeFromMemory(mVolume, NULL, &destBox, 
			converted.data, D3D9Texture::_getPF(converted.format),
			converted.rowPitch * PixelUtil::getNumElemBytes(converted.format),
			converted.slicePitch * PixelUtil::getNumElemBytes(converted.format),
			NULL, &srcBox, D3DX_DEFAULT, 0) != D3D_OK)
		{
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "D3DXLoadSurfaceFromMemory failed",
		 		"D3D9HardwarePixelBuffer::blitFromMemory");
		}
	}
	if(mDoMipmapGen)
		_genMipmaps();
}
//-----------------------------------------------------------------------------  
void D3D9HardwarePixelBuffer::blitToMemory(const Image::Box &srcBox, const PixelBox &dst)
{
	// Decide on pixel format of temp surface
	PixelFormat tmpFormat = mFormat; 
	if(D3D9Texture::_getPF(dst.format) != D3DFMT_UNKNOWN)
	{
		tmpFormat = dst.format;
	}
	if(mSurface)
	{
		assert(srcBox.getDepth() == 1 && dst.getDepth() == 1);
		// Create temp texture
		IDirect3DTexture9 *tmp;
		IDirect3DSurface9 *surface;
	
		if(D3DXCreateTexture(
			mpDev,
			dst.getWidth(), dst.getHeight(), 
			1, // 1 mip level ie topmost, generate no mipmaps
			0, D3D9Texture::_getPF(tmpFormat), D3DPOOL_SCRATCH,
			&tmp
			) != D3D_OK)
		{
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "Create temporary texture failed",
		 		"D3D9HardwarePixelBuffer::blitToMemory");
		}
		if(tmp->GetSurfaceLevel(0, &surface) != D3D_OK)
		{
			tmp->Release();
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "Get surface level failed",
				"D3D9HardwarePixelBuffer::blitToMemory");
		}
		// Copy texture to this temp surface
		RECT destRect, srcRect;
		srcRect = toD3DRECT(srcBox);
		destRect = toD3DRECTExtent(dst);
		
		if(D3DXLoadSurfaceFromSurface(
			surface, NULL, &destRect, 
			mSurface, NULL, &srcRect,
			 D3DX_DEFAULT, 0) != D3D_OK)
		{
			surface->Release();
			tmp->Release();
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "D3DXLoadSurfaceFromSurface failed",
		 		"D3D9HardwarePixelBuffer::blitToMemory");
		}
		// Lock temp surface and copy it to memory
		D3DLOCKED_RECT lrect; // Filled in by D3D
		if(surface->LockRect(&lrect, NULL,  D3DLOCK_READONLY) != D3D_OK)
		{
			surface->Release();
			tmp->Release();
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "surface->LockRect",
		 		"D3D9HardwarePixelBuffer::blitToMemory");
		}
		// Copy it
		PixelBox locked(dst.getWidth(), dst.getHeight(), dst.getDepth(), tmpFormat);
		fromD3DLock(locked, lrect);
		PixelUtil::bulkPixelConversion(locked, dst);
		surface->UnlockRect();
		// Release temporary surface and texture
		surface->Release();
		tmp->Release();
	}
	else
	{
		// Create temp texture
		IDirect3DVolumeTexture9 *tmp;
		IDirect3DVolume9 *surface;
	
		if(D3DXCreateVolumeTexture(
			mpDev,
			dst.getWidth(), dst.getHeight(), dst.getDepth(), 0,
			0, D3D9Texture::_getPF(tmpFormat), D3DPOOL_SCRATCH,
			&tmp
			) != D3D_OK)
		{
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "Create temporary texture failed",
		 		"D3D9HardwarePixelBuffer::blitToMemory");
		}
		if(tmp->GetVolumeLevel(0, &surface) != D3D_OK)
		{
			tmp->Release();
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "Get volume level failed",
				"D3D9HardwarePixelBuffer::blitToMemory");
		}
		// Volume
		D3DBOX ddestBox, dsrcBox;
		ddestBox = toD3DBOXExtent(dst);
		dsrcBox = toD3DBOX(srcBox);
		
		if(D3DXLoadVolumeFromVolume(
			surface, NULL, &ddestBox, 
			mVolume, NULL, &dsrcBox,
			 D3DX_DEFAULT, 0) != D3D_OK)
		{
			surface->Release();
			tmp->Release();
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "D3DXLoadVolumeFromVolume failed",
		 		"D3D9HardwarePixelBuffer::blitToMemory");
		}
		// Lock temp surface and copy it to memory
		D3DLOCKED_BOX lbox; // Filled in by D3D
		if(surface->LockBox(&lbox, NULL,  D3DLOCK_READONLY) != D3D_OK)
		{
			surface->Release();
			tmp->Release();
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "surface->LockBox",
		 		"D3D9HardwarePixelBuffer::blitToMemory");
		}
		// Copy it
		PixelBox locked(dst.getWidth(), dst.getHeight(), dst.getDepth(), tmpFormat);
		fromD3DLock(locked, lbox);
		PixelUtil::bulkPixelConversion(locked, dst);
		surface->UnlockBox();
		// Release temporary surface and texture
		surface->Release();
		tmp->Release();
	}
}
//-----------------------------------------------------------------------------  
void D3D9HardwarePixelBuffer::_genMipmaps()
{
	assert(mMipTex);
	// Mipmapping
	if (mHWMipmaps)
	{
		// Hardware mipmaps
		mMipTex->GenerateMipSubLevels();
	}
	else
	{
		// Software mipmaps
		if( D3DXFilterTexture( mMipTex, NULL, D3DX_DEFAULT, D3DX_DEFAULT ) != D3D_OK )
		{
			OGRE_EXCEPT( Exception::ERR_RENDERINGAPI_ERROR, 
			"Failed to filter texture (generate mipmaps)",
			 "D3D9HardwarePixelBuffer::_genMipmaps" );
		}
	}

}
//----------------------------------------------------------------------------- 
void D3D9HardwarePixelBuffer::_setMipmapping(bool doMipmapGen, bool HWMipmaps, IDirect3DBaseTexture9 *mipTex)
{
	mDoMipmapGen = doMipmapGen;
	mHWMipmaps = HWMipmaps;
	mMipTex = mipTex;
}


};
