Browse Source

Added cached CPU data to Texture

Marko Pintera 11 years ago
parent
commit
6c74a3d7c0

+ 6 - 0
BansheeCore/Include/BsPixelUtil.h

@@ -101,6 +101,12 @@ namespace BansheeEngine
 		 */
 		static UINT32 getMemorySize(UINT32 width, UINT32 height, UINT32 depth, PixelFormat format);
 		
+		/**
+		 * @brief	Calculates the size of a mip level of a texture with the provided size.
+		 */
+		static void getSizeForMipLevel(UINT32 width, UINT32 height, UINT32 depth, UINT32 mipLevel, 
+			UINT32& mipWidth, UINT32& mipHeight, UINT32& mipDepth);
+
 		/**
 		 * @brief	Returns property flags for this pixel format.
 		 *

+ 33 - 0
BansheeCore/Include/BsTexture.h

@@ -18,6 +18,7 @@ namespace BansheeEngine
 		TU_RENDERTARGET = 0x200, /**< Texture used for rendering by the GPU. */
 		TU_DEPTHSTENCIL = 0x400, /**< Texture used as a depth/stencil buffer by the GPU. */
 		TU_LOADSTORE = 0x800, /**< Texture that allows load/store operations from the GPU program. */
+		TU_CPUCACHED = 0x1000, /**< All texture data will also be cached in system memory. */
 		TU_DEFAULT = TU_STATIC
     };
 
@@ -130,6 +131,11 @@ namespace BansheeEngine
          */
         virtual UINT32 getNumFaces() const;
 
+		/**
+		 * @copydoc GpuResource::_writeSubresourceSim
+		 */
+		virtual void _writeSubresourceSim(UINT32 subresourceIdx, const GpuResourceData& data, bool discardEntireBuffer);
+
 		/**
 		 * @copydoc GpuResource::writeSubresource
 		 */
@@ -212,6 +218,24 @@ namespace BansheeEngine
 		 */
 		void copy(UINT32 srcSubresourceIdx, UINT32 destSubresourceIdx, TexturePtr& target);
 
+		/**
+		 * @brief	Reads data from the cached system memory texture buffer into the provided buffer. 
+		 * 		  
+		 * @param	dest		Previously allocated buffer to read data into.
+		 * @param	mipLevel	(optional) Mipmap level to read from.
+		 * @param	face		(optional) Texture face to read from.
+		 *
+		 * @note	Sim thread only.
+		 *
+		 *			The data read is the cached texture data. Any data written to the texture from the GPU 
+		 *			or core thread will not be reflected in this data. Use "readData" if you require
+		 *			those changes.
+		 *
+		 *			The texture must have been created with TU_CPUCACHED usage otherwise this method
+		 *			will not return any data.
+		 */
+		virtual void readDataSim(PixelData& dest, UINT32 mipLevel = 0, UINT32 face = 0);
+
 		/**
 		 * @brief	Reads data from the texture buffer into the provided buffer.
 		 * 		  
@@ -389,6 +413,13 @@ namespace BansheeEngine
 		 */
 		UINT32 calculateSize() const;
 
+		/**
+		 * @brief	Creates buffers used for caching of CPU texture data.
+		 *
+		 * @note	Make sure to initialize all texture properties before calling this.
+		 */
+		void createCPUBuffers();
+
 	protected:
 		UINT32 mHeight; // Immutable
 		UINT32 mWidth; // Immutable
@@ -402,6 +433,8 @@ namespace BansheeEngine
 		PixelFormat mFormat; // Immutable
 		int mUsage; // Immutable
 
+		Vector<PixelDataPtr> mCPUSubresourceData;
+
 		/************************************************************************/
 		/* 								SERIALIZATION                      		*/
 		/************************************************************************/

+ 15 - 0
BansheeCore/Source/BsPixelUtil.cpp

@@ -974,6 +974,21 @@ namespace BansheeEngine
 		}
 	}
 
+	void PixelUtil::getSizeForMipLevel(UINT32 width, UINT32 height, UINT32 depth, UINT32 mipLevel,
+		UINT32& mipWidth, UINT32& mipHeight, UINT32& mipDepth)
+	{
+		mipWidth = width;
+		mipHeight = height;
+		mipDepth = depth;
+
+		for (UINT32 i = 0; i < mipLevel; i++)
+		{
+			if (mipWidth != 1) mipWidth /= 2;
+			if (mipHeight != 1) mipHeight /= 2;
+			if (mipDepth != 1) mipDepth /= 2;
+		}
+	}
+
     UINT32 PixelUtil::getNumElemBits(PixelFormat format)
     {
         return getDescriptionFor(format).elemBytes * 8;

+ 126 - 6
BansheeCore/Source/BsTexture.cpp

@@ -7,6 +7,7 @@
 #include "BsCoreThread.h"
 #include "BsAsyncOp.h"
 #include "BsResources.h"
+#include "BsPixelUtil.h"
 
 namespace BansheeEngine 
 {
@@ -34,6 +35,10 @@ namespace BansheeEngine
 		mFormat = TextureManager::instance().getNativeFormat(mTextureType, format, mUsage, hwGamma);
 		mSize = getNumFaces() * PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat);
 
+		// Allocate CPU buffers if needed
+		if ((usage & TU_CPUCACHED) != 0)
+			createCPUBuffers();
+
 		Resource::initialize();
 	}
 
@@ -52,6 +57,49 @@ namespace BansheeEngine
 		return getTextureType() == TEX_TYPE_CUBE_MAP ? 6 : 1;
 	}
 
+	void Texture::_writeSubresourceSim(UINT32 subresourceIdx, const GpuResourceData& data, bool discardEntireBuffer)
+	{
+		if ((mUsage & TU_CPUCACHED) == 0)
+			return;
+
+		if (subresourceIdx >= (UINT32)mCPUSubresourceData.size())
+		{
+			LOGERR("Invalid subresource index: " + toString(subresourceIdx) + ". Supported range: 0 .. " + toString(mCPUSubresourceData.size()));
+			return;
+		}
+
+		if (data.getTypeId() != TID_PixelData)
+		{
+			LOGERR("Invalid GpuResourceData type. Only PixelData is supported.");
+			return;
+		}
+
+		const PixelData& pixelData = static_cast<const PixelData&>(data);
+
+		UINT32 mipLevel;
+		UINT32 face;
+		mapFromSubresourceIdx(subresourceIdx, face, mipLevel);
+
+		UINT32 mipWidth, mipHeight, mipDepth;
+		PixelUtil::getSizeForMipLevel(getWidth(), getHeight(), getDepth(),
+			mipLevel, mipWidth, mipHeight, mipDepth);
+
+		if (pixelData.getWidth() != mipWidth || pixelData.getHeight() != mipHeight ||
+			pixelData.getDepth() != mipDepth || pixelData.getFormat() != getFormat())
+		{
+			LOGERR("Provided buffer is not of valid dimensions or format in order to read from this texture.");
+			return;
+		}
+
+		if (mCPUSubresourceData[subresourceIdx]->getSize() != pixelData.getSize())
+			BS_EXCEPT(InternalErrorException, "Buffer sizes don't match.");
+
+		UINT8* dest = mCPUSubresourceData[subresourceIdx]->getData();
+		UINT8* src = pixelData.getData();
+
+		memcpy(dest, src, pixelData.getSize());
+	}
+
 	void Texture::writeSubresource(UINT32 subresourceIdx, const GpuResourceData& data, bool discardEntireBuffer)
 	{
 		THROW_IF_NOT_CORE_THREAD;
@@ -92,18 +140,22 @@ namespace BansheeEngine
 		if(data.getTypeId() != TID_PixelData)
 			BS_EXCEPT(InvalidParametersException, "Invalid GpuResourceData type. Only PixelData is supported.");
 
+		UINT32 face = 0;
+		UINT32 mip = 0;
+		mapFromSubresourceIdx(subresourceIdx, face, mip);
+
 		PixelData& pixelData = static_cast<PixelData&>(data);
 
-		if(pixelData.getWidth() != getWidth() ||pixelData.getHeight() != getHeight() || 
-			pixelData.getDepth() != getDepth() || pixelData.getFormat() != getFormat())
+		UINT32 mipWidth, mipHeight, mipDepth;
+		PixelUtil::getSizeForMipLevel(getWidth(), getHeight(), getDepth(),
+			mip, mipWidth, mipHeight, mipDepth);
+
+		if (pixelData.getWidth() != mipWidth || pixelData.getHeight() != mipHeight ||
+			pixelData.getDepth() != mipDepth || pixelData.getFormat() != getFormat())
 		{
 			BS_EXCEPT(RenderingAPIException, "Provided buffer is not of valid dimensions or format in order to read from this texture.");
 		}
 
-		UINT32 face = 0;
-		UINT32 mip = 0;
-		mapFromSubresourceIdx(subresourceIdx, face, mip);
-
 		readData(pixelData, mip, face);
 	}
 
@@ -149,6 +201,41 @@ namespace BansheeEngine
 		return face * (getNumMipmaps() + 1) + mip;
 	}
 
+	void Texture::readDataSim(PixelData& dest, UINT32 mipLevel, UINT32 face)
+	{
+		if ((mUsage & TU_CPUCACHED) == 0)
+		{
+			LOGERR("Attempting to read CPU data from a texture that is created without CPU caching.");
+			return;
+		}
+
+		UINT32 mipWidth, mipHeight, mipDepth;
+		PixelUtil::getSizeForMipLevel(getWidth(), getHeight(), getDepth(), 
+			mipLevel, mipWidth, mipHeight, mipDepth);
+
+		if (dest.getWidth() != mipWidth || dest.getHeight() != mipHeight ||
+			dest.getDepth() != mipDepth || dest.getFormat() != getFormat())
+		{
+			LOGERR("Provided buffer is not of valid dimensions or format in order to read from this texture.");
+			return;
+		}
+
+		UINT32 subresourceIdx = mapToSubresourceIdx(face, mipLevel);
+		if (subresourceIdx >= (UINT32)mCPUSubresourceData.size())
+		{
+			LOGERR("Invalid subresource index: " + toString(subresourceIdx) + ". Supported range: 0 .. " + toString(mCPUSubresourceData.size()));
+			return;
+		}
+
+		if (mCPUSubresourceData[subresourceIdx]->getSize() != dest.getSize())
+			BS_EXCEPT(InternalErrorException, "Buffer sizes don't match.");
+
+		UINT8* srcPtr = mCPUSubresourceData[subresourceIdx]->getData();
+		UINT8* destPtr = dest.getData();
+
+		memcpy(destPtr, srcPtr, dest.getSize());
+	}
+
 	PixelData Texture::lock(GpuLockOptions options, UINT32 mipLevel, UINT32 face)
 	{
 		THROW_IF_NOT_CORE_THREAD;
@@ -287,6 +374,39 @@ namespace BansheeEngine
 		}
 	}
 
+	void Texture::createCPUBuffers()
+	{
+		UINT32 numFaces = getNumFaces();
+		UINT32 numMips = getNumMipmaps();
+
+		UINT32 numSubresources = numFaces * numMips;
+		mCPUSubresourceData.resize(numSubresources);
+
+		for (UINT32 i = 0; i < numFaces; i++)
+		{
+			UINT32 curWidth = mWidth;
+			UINT32 curHeight = mHeight;
+			UINT32 curDepth = mDepth;
+
+			for (UINT32 j = 0; j < mNumMipmaps; j++)
+			{
+				UINT32 subresourceIdx = mapToSubresourceIdx(i, j);
+
+				mCPUSubresourceData[subresourceIdx] = bs_shared_ptr<PixelData>(curWidth, curHeight, curDepth, mFormat);
+				mCPUSubresourceData[subresourceIdx]->allocateInternalBuffer();
+
+				if (curWidth > 1)
+					curWidth = curWidth / 2;
+
+				if (curHeight > 1)
+					curHeight = curHeight / 2;
+
+				if (curDepth > 1)
+					curDepth = curDepth / 2;
+			}
+		}
+	}
+
 	/************************************************************************/
 	/* 								SERIALIZATION                      		*/
 	/************************************************************************/