ソースを参照

DirectX 9 reading from render target works

Marko Pintera 11 年 前
コミット
41dd303914

+ 16 - 2
BansheeCore/Include/BsTextureRTTI.h

@@ -26,7 +26,20 @@ namespace BansheeEngine
 		BS_SETGET_MEMBER(mMultisampleHint, String, Texture)
 		BS_SETGET_MEMBER(mTextureType, TextureType, Texture)
 		BS_SETGET_MEMBER(mFormat, PixelFormat, Texture)
-		BS_SETGET_MEMBER(mUsage, INT32, Texture)
+
+		INT32& getUsage(Texture* obj) { return obj->mUsage; }
+		void setUsage(Texture* obj, INT32& val) 
+		{ 
+			// Render target and depth stencil texture formats are for in-memory use only
+			// and don't make sense when serialized
+			if (val == TU_DEPTHSTENCIL || val == TU_RENDERTARGET)
+				obj->mUsage = TU_STATIC;
+			else
+				obj->mUsage = val; 
+		}
+
+#define BS_ADD_PLAINFIELD(name, id, parentType) \
+	addPlainField(#name, id##, &##parentType##::get##name, &##parentType##::Set##name);
 
 		PixelDataPtr getPixelData(Texture* obj, UINT32 idx)
 		{
@@ -79,7 +92,8 @@ namespace BansheeEngine
 			BS_ADD_PLAINFIELD(mMultisampleHint, 8, TextureRTTI)
 			BS_ADD_PLAINFIELD(mTextureType, 9, TextureRTTI)
 			BS_ADD_PLAINFIELD(mFormat, 10, TextureRTTI)
-			BS_ADD_PLAINFIELD(mUsage, 11, TextureRTTI)
+
+			addPlainField("mUsage", 11, &TextureRTTI::getUsage, &TextureRTTI::setUsage);
 
 			addReflectablePtrArrayField("mPixelData", 12, &TextureRTTI::getPixelData, &TextureRTTI::getPixelDataArraySize, 
 				&TextureRTTI::setPixelData, &TextureRTTI::setPixelDataArraySize);

+ 1 - 1
BansheeD3D11RenderSystem/Source/BsD3D11Texture.cpp

@@ -181,7 +181,7 @@ namespace BansheeEngine
 			PixelUtil::bulkPixelConversion(src, myData);
 			unlock();
 		}
-		else if(mUsage == TU_STATIC)
+		else if(mUsage == TU_STATIC || mUsage == TU_RENDERTARGET || mUsage == TU_DEPTHSTENCIL)
 		{
 			mipLevel = Math::clamp(mipLevel, (UINT32)mipLevel, getNumMipmaps());
 			face = Math::clamp(face, (UINT32)0, mDepth - 1);

+ 10 - 0
BansheeD3D9RenderSystem/Include/BsD3D9PixelBuffer.h

@@ -83,6 +83,16 @@ namespace BansheeEngine
 		 */
 		static void unlockDeviceAccess();
 
+		/**
+		 * @brief	Initializes the provided pixel data buffer with information provided in the D3D9 locked rectangle.
+		 */
+		static void initPixelDataFromD3DLock(PixelData& pixelData, const D3DLOCKED_RECT& lrect);
+
+		/**
+		 * @brief	Initializes the provided pixel data buffer with information provided in the D3D9 locked box.
+		 */
+		static void initPixelDataFromD3DLock(PixelData& pixelData, const D3DLOCKED_BOX& lrect);
+
 	protected:
 		/**
 		 * @copydoc	PixelBuffer::lockImpl

+ 47 - 47
BansheeD3D9RenderSystem/Source/BsD3D9PixelBuffer.cpp

@@ -8,51 +8,6 @@
 
 namespace BansheeEngine 
 {
-	void fromD3DLock(PixelData& rval, const D3DLOCKED_RECT& lrect)
-	{
-		UINT32 bpp = PixelUtil::getNumElemBytes(rval.getFormat());
-		if (bpp != 0)
-		{
-			rval.setRowPitch(lrect.Pitch / bpp);
-			rval.setSlicePitch(rval.getRowPitch() * rval.getHeight());
-			assert((lrect.Pitch % bpp) == 0);
-		}
-		else if (PixelUtil::isCompressed(rval.getFormat()))
-		{
-			rval.setRowPitch(rval.getWidth());
-			rval.setSlicePitch(rval.getWidth() * rval.getHeight());
-		}
-		else
-		{
-			BS_EXCEPT(InvalidParametersException, "Invalid pixel format.");
-		}
-
-		rval.setExternalBuffer((UINT8*)lrect.pBits);
-	}
-
-	void fromD3DLock(PixelData& rval, const D3DLOCKED_BOX& lbox)
-	{
-		UINT32 bpp = PixelUtil::getNumElemBytes(rval.getFormat());
-		if (bpp != 0)
-		{
-			rval.setRowPitch(lbox.RowPitch / bpp);
-			rval.setSlicePitch(lbox.SlicePitch / bpp);
-			assert((lbox.RowPitch % bpp) == 0);
-			assert((lbox.SlicePitch % bpp) == 0);
-		}
-		else if (PixelUtil::isCompressed(rval.getFormat()))
-		{
-			rval.setRowPitch(rval.getWidth());
-			rval.setSlicePitch(rval.getWidth() * rval.getHeight());
-		}
-		else
-		{
-			BS_EXCEPT(InvalidParametersException, "Invalid pixel format.");
-		}
-
-		rval.setExternalBuffer((UINT8*)lbox.pBits);
-	}
-
 	RECT toD3DRECT(const PixelVolume &lockBox)
 	{
 		RECT prect;
@@ -278,7 +233,7 @@ namespace BansheeEngine
 			if (FAILED(hr))		
 				BS_EXCEPT(RenderingAPIException, "Surface locking failed");
 
-			fromD3DLock(rval, lrect);
+			initPixelDataFromD3DLock(rval, lrect);
 		} 
 		else if(bufferResources->volume) 
 		{
@@ -288,7 +243,7 @@ namespace BansheeEngine
 			if(bufferResources->volume->LockBox(&lbox, &pbox, flags) != D3D_OK)
 				BS_EXCEPT(RenderingAPIException, "Volume locking failed");
 
-			fromD3DLock(rval, lbox);
+			initPixelDataFromD3DLock(rval, lbox);
 		}
 
 		return rval;
@@ -366,4 +321,49 @@ namespace BansheeEngine
 
 		return bufferResources->surface;
 	}
+
+	void D3D9PixelBuffer::initPixelDataFromD3DLock(PixelData& rval, const D3DLOCKED_RECT& lrect)
+	{
+		UINT32 bpp = PixelUtil::getNumElemBytes(rval.getFormat());
+		if (bpp != 0)
+		{
+			rval.setRowPitch(lrect.Pitch / bpp);
+			rval.setSlicePitch(rval.getRowPitch() * rval.getHeight());
+			assert((lrect.Pitch % bpp) == 0);
+		}
+		else if (PixelUtil::isCompressed(rval.getFormat()))
+		{
+			rval.setRowPitch(rval.getWidth());
+			rval.setSlicePitch(rval.getWidth() * rval.getHeight());
+		}
+		else
+		{
+			BS_EXCEPT(InvalidParametersException, "Invalid pixel format.");
+		}
+
+		rval.setExternalBuffer((UINT8*)lrect.pBits);
+	}
+
+	void D3D9PixelBuffer::initPixelDataFromD3DLock(PixelData& rval, const D3DLOCKED_BOX& lbox)
+	{
+		UINT32 bpp = PixelUtil::getNumElemBytes(rval.getFormat());
+		if (bpp != 0)
+		{
+			rval.setRowPitch(lbox.RowPitch / bpp);
+			rval.setSlicePitch(lbox.SlicePitch / bpp);
+			assert((lbox.RowPitch % bpp) == 0);
+			assert((lbox.SlicePitch % bpp) == 0);
+		}
+		else if (PixelUtil::isCompressed(rval.getFormat()))
+		{
+			rval.setRowPitch(rval.getWidth());
+			rval.setSlicePitch(rval.getWidth() * rval.getHeight());
+		}
+		else
+		{
+			BS_EXCEPT(InvalidParametersException, "Invalid pixel format.");
+		}
+
+		rval.setExternalBuffer((UINT8*)lbox.pBits);
+	}
 };

+ 80 - 9
BansheeD3D9RenderSystem/Source/BsD3D9Texture.cpp

@@ -102,19 +102,90 @@ namespace BansheeEngine
 		if (mMultisampleCount > 0)
 			BS_EXCEPT(InvalidStateException, "Multisampled textures cannot be accessed from the CPU directly.");
 
-		PixelData myData = lock(GBL_READ_ONLY, mipLevel, face);
+		if (mUsage == TU_DEPTHSTENCIL || mUsage == TU_RENDERTARGET) // Render targets cannot be locked normally
+		{
+			IDirect3DDevice9* device = D3D9RenderSystem::getActiveD3D9Device();
+
+			D3D9PixelBuffer* sourceBuffer = static_cast<D3D9PixelBuffer*>(getBuffer(face, mipLevel).get());
+			
+			UINT32 mipWidth = mWidth >> mipLevel;
+			UINT32 mipHeight = mHeight >> mipLevel;
+			D3DFORMAT format = chooseD3DFormat(device);
+
+			// Note: I'm allocating and releasing a texture every time we read.
+			// I might consider adding a flag to keep this texture permanent which would make reading
+			// faster but at the cost of double the memory.
+
+			IDirect3DTexture9* stagingTexture = nullptr; 
+			HRESULT hr = D3DXCreateTexture(device, (UINT)mipWidth, (UINT)mipHeight, 1,
+				0, format, D3DPOOL_SYSTEMMEM, &stagingTexture);
+
+			if (FAILED(hr))
+			{
+				String msg = DXGetErrorDescription(hr);
+				BS_EXCEPT(RenderingAPIException, "Failed to create a texture: " + msg);
+			}
+
+			IDirect3DSurface9* stagingSurface = nullptr;
+			hr = stagingTexture->GetSurfaceLevel(0, &stagingSurface);
+			if (FAILED(hr))
+			{
+				String msg = DXGetErrorDescription(hr);
+				BS_EXCEPT(RenderingAPIException, "Failed to retrieve a texture surface: " + msg);
+			}
+
+			hr = device->GetRenderTargetData(sourceBuffer->getSurface(device), stagingSurface);
+			if (FAILED(hr))
+			{
+				String msg = DXGetErrorDescription(hr);
+				BS_EXCEPT(RenderingAPIException, "Failed to retrieve render target data: " + msg);
+			}
+
+			PixelData myData(mipWidth, mipHeight, 1, mFormat);
+
+			D3DLOCKED_RECT lrect;
+			hr = stagingSurface->LockRect(&lrect, nullptr, D3DLOCK_READONLY);
+			if (FAILED(hr))
+			{
+				String msg = DXGetErrorDescription(hr);
+				BS_EXCEPT(RenderingAPIException, "Failed to lock surface: " + msg);
+			}
+
+			D3D9PixelBuffer::initPixelDataFromD3DLock(myData, lrect);
 
 #if BS_DEBUG_MODE
-		if(dest.getConsecutiveSize() != myData.getConsecutiveSize())
-		{
-			unlock();
-			BS_EXCEPT(InternalErrorException, "Buffer sizes don't match.");
+			if (dest.getConsecutiveSize() != myData.getConsecutiveSize())
+				BS_EXCEPT(InternalErrorException, "Buffer sizes don't match.");
+#endif
+
+			PixelUtil::bulkPixelConversion(myData, dest);
+
+			hr = stagingSurface->UnlockRect();
+			if (FAILED(hr))
+			{
+				String msg = DXGetErrorDescription(hr);
+				BS_EXCEPT(RenderingAPIException, "Failed to unlock surface: " + msg);
+			}
+
+			SAFE_RELEASE(stagingSurface);
+			SAFE_RELEASE(stagingTexture);
 		}
+		else
+		{
+			PixelData myData = lock(GBL_READ_ONLY, mipLevel, face);
+
+#if BS_DEBUG_MODE
+			if (dest.getConsecutiveSize() != myData.getConsecutiveSize())
+			{
+				unlock();
+				BS_EXCEPT(InternalErrorException, "Buffer sizes don't match.");
+			}
 #endif
 
-		PixelUtil::bulkPixelConversion(myData, dest);
+			PixelUtil::bulkPixelConversion(myData, dest);
 
-		unlock();
+			unlock();
+		}
 	}
 
 	void D3D9Texture::writeData(const PixelData& src, UINT32 mipLevel, UINT32 face, bool discardWholeBuffer)
@@ -364,7 +435,7 @@ namespace BansheeEngine
 			// Create AA surface
 			HRESULT hr = d3d9Device->CreateRenderTarget(mWidth, mHeight, d3dPF, 
 				mMultisampleType, mMultisampleQuality,
-				TRUE, // TODO - Possible performance issues? Need to check. Other option is to use GetRenderTargetData when reading RT from CPU
+				FALSE,
 				&textureResources->pMultisampleSurface, NULL);
 
 			if (FAILED(hr))
@@ -387,7 +458,7 @@ namespace BansheeEngine
 			// Create AA depth stencil surface
 			HRESULT hr = d3d9Device->CreateDepthStencilSurface(mWidth, mHeight, d3dPF, 
 				mMultisampleType, mMultisampleQuality,
-				TRUE, // TODO - Possible performance issues? Need to check. Other option is to use GetRenderTargetData when reading RT from CPU
+				FALSE, 
 				&textureResources->pDepthStencilSurface, NULL);
 
 			if (FAILED(hr))

+ 21 - 0
BansheeEditor/Source/BsSceneEditorWidget.cpp

@@ -19,6 +19,14 @@
 #include "BsGUIWidget.h"
 #include "BsSceneGrid.h"
 
+// DEBUG ONLY
+#include "BsTime.h"
+#include "BsResources.h"
+#include "BsPath.h"
+#include "BsGUITexture.h"
+#include "BsSpriteTexture.h"
+#include "BsFileSystem.h"
+
 using namespace std::placeholders;
 
 namespace BansheeEngine
@@ -42,7 +50,20 @@ namespace BansheeEngine
 
 	void SceneEditorWidget::_update()
 	{
+		// DEBUG ONLY
+		if (gTime().getCurrentFrameNumber() == 100)
+		{
+			HTexture colorTex = mSceneRenderTarget->getBindableColorTexture();
+			gResources().save(colorTex, "C:\\SavedRenderTex.asset", true);
+
+			FileSystem::move("C:\\SavedRenderTex.asset", "C:\\SavedRenderTexNew.asset", true);
 
+			HTexture colorTexLoaded = gResources().load("C:\\SavedRenderTexNew.asset");
+			HSpriteTexture spriteTex = SpriteTexture::create(colorTexLoaded);
+
+			GUILayout& layout = mContent->getLayout();
+			layout.addElement(GUITexture::create(spriteTex));
+		}
 	}
 
 	void SceneEditorWidget::doOnResized(UINT32 width, UINT32 height)

+ 1 - 1
BansheeEditorExec/BsEditorExec.cpp

@@ -11,7 +11,7 @@ int CALLBACK WinMain(
 	_In_  int nCmdShow
 	)
 {
-	EditorApplication::startUp(RenderSystemPlugin::DX11);
+	EditorApplication::startUp(RenderSystemPlugin::DX9);
 	EditorApplication::instance().runMainLoop();
 	EditorApplication::shutDown();