Browse Source

Added initial texture read back support.

Branimir Karadžić 10 years ago
parent
commit
6aa6efda12

+ 1 - 1
examples/08-update/update.cpp

@@ -169,7 +169,7 @@ int _main_(int /*_argc*/, char** /*_argv*/)
 
 	const bgfx::Caps* caps = bgfx::getCaps();
 	const bool texture3DSupported = !!(caps->supported & BGFX_CAPS_TEXTURE_3D);
-	const bool blitSupported      = !!(caps->supported & BGFX_CAPS_BLIT);
+	const bool blitSupported      = !!(caps->supported & BGFX_CAPS_TEXTURE_BLIT);
 
 	uint32_t numTextures3d = 0;
 	bgfx::TextureHandle textures3d[3] = {};

+ 27 - 1
examples/09-hdr/hdr.cpp

@@ -215,6 +215,16 @@ class HDR : public entry::AppI
 		m_bright = bgfx::createFrameBuffer(bgfx::BackbufferRatio::Half,   bgfx::TextureFormat::BGRA8);
 		m_blur   = bgfx::createFrameBuffer(bgfx::BackbufferRatio::Eighth, bgfx::TextureFormat::BGRA8);
 
+		m_lumBgra8 = 0;
+		if ( (BGFX_CAPS_TEXTURE_BLIT|BGFX_CAPS_TEXTURE_READ_BACK) == (bgfx::getCaps()->supported & BGFX_CAPS_TEXTURE_BLIT|BGFX_CAPS_TEXTURE_READ_BACK) )
+		{
+			m_rb = bgfx::createTexture2D(1, 1, 1, bgfx::TextureFormat::BGRA8, BGFX_TEXTURE_READ_BACK);
+		}
+		else
+		{
+			m_rb.idx = bgfx::invalidHandle;
+		}
+
 		// Imgui.
 		imguiCreate();
 
@@ -306,7 +316,7 @@ class HDR : public entry::AppI
 					, m_height
 					);
 
-			imguiBeginScrollArea("Settings", m_width - m_width / 5 - 10, 10, m_width / 5, m_height / 3, &m_scrollArea);
+			imguiBeginScrollArea("Settings", m_width - m_width / 5 - 10, 10, m_width / 5, m_height / 2, &m_scrollArea);
 			imguiSeparatorLine();
 
 			imguiSlider("Speed", m_speed, 0.0f, 1.0f, 0.01f);
@@ -316,6 +326,14 @@ class HDR : public entry::AppI
 			imguiSlider("White point", m_white,      0.1f, 2.0f, 0.01f);
 			imguiSlider("Threshold",   m_threshold,  0.1f, 2.0f, 0.01f);
 
+			if (bgfx::isValid(m_rb) )
+			{
+				union { uint32_t color; uint8_t bgra[4]; } cast = { m_lumBgra8 };
+				float exponent = cast.bgra[3]/255.0f * 255.0f - 128.0f;
+				float lumAvg   = cast.bgra[2]/255.0f * exp2(exponent);
+				imguiSlider("Lum Avg", lumAvg, 0.0f, 1.0f, 0.01f, false);
+			}
+
 			imguiEndScrollArea();
 			imguiEndFrame();
 
@@ -473,6 +491,12 @@ class HDR : public entry::AppI
 			screenSpaceQuad( (float)m_width, (float)m_height, s_originBottomLeft);
 			bgfx::submit(9, m_tonemapProgram);
 
+			if (bgfx::isValid(m_rb) )
+			{
+				bgfx::blit(9, m_rb, 0, 0, m_lum[4]);
+				bgfx::readTexture(m_rb, &m_lumBgra8);
+			}
+
 			// Advance to next frame. Rendering thread will be kicked to
 			// process submitted rendering primitives.
 			bgfx::frame();
@@ -505,6 +529,7 @@ class HDR : public entry::AppI
 	Mesh* m_mesh;
 
 	bgfx::TextureHandle m_fbtextures[2];
+	bgfx::TextureHandle m_rb;
 	bgfx::FrameBufferHandle m_fbh;
 	bgfx::FrameBufferHandle m_lum[5];
 	bgfx::FrameBufferHandle m_bright;
@@ -514,6 +539,7 @@ class HDR : public entry::AppI
 	uint32_t m_height;
 	uint32_t m_debug;
 	uint32_t m_reset;
+	uint32_t m_lumBgra8;
 
 	uint32_t m_oldWidth;
 	uint32_t m_oldHeight;

+ 1 - 1
examples/common/nanovg/nanovg_bgfx.cpp

@@ -251,7 +251,7 @@ namespace
 			vs_nanovg_fill = bgfx::makeRef(vs_nanovg_fill_mtl, sizeof(vs_nanovg_fill_mtl) );
 			fs_nanovg_fill = bgfx::makeRef(fs_nanovg_fill_mtl, sizeof(fs_nanovg_fill_mtl) );
 			break;
-				
+
 		default:
 			vs_nanovg_fill = bgfx::makeRef(vs_nanovg_fill_glsl, sizeof(vs_nanovg_fill_glsl) );
 			fs_nanovg_fill = bgfx::makeRef(fs_nanovg_fill_glsl, sizeof(fs_nanovg_fill_glsl) );

+ 12 - 0
include/bgfx/bgfx.h

@@ -1394,6 +1394,12 @@ namespace bgfx
 	///
 	void updateTextureCube(TextureHandle _handle, uint8_t _side, uint8_t _mip, uint16_t _x, uint16_t _y, uint16_t _width, uint16_t _height, const Memory* _mem, uint16_t _pitch = UINT16_MAX);
 
+	///
+	void readTexture(TextureHandle _handle, void* _data);
+
+	///
+	void readTexture(FrameBufferHandle _handle, uint8_t _attachment, void* _data);
+
 	/// Destroy texture.
 	///
 	/// @attention C99 equivalent is `bgfx_destroy_texture`.
@@ -2052,12 +2058,18 @@ namespace bgfx
 	///
 	void blit(uint8_t _id, TextureHandle _dst, uint16_t _dstX, uint16_t _dstY, TextureHandle _src, uint16_t _srcX = 0, uint16_t _srcY = 0, uint16_t _width = UINT16_MAX, uint16_t _height = UINT16_MAX);
 
+	///
+	void blit(uint8_t _id, TextureHandle _dst, uint16_t _dstX, uint16_t _dstY, FrameBufferHandle _src, uint8_t _attachment = 0, uint16_t _srcX = 0, uint16_t _srcY = 0, uint16_t _width = UINT16_MAX, uint16_t _height = UINT16_MAX);
+
 	/// Blit texture region between two textures.
 	///
 	/// @attention C99 equivalent is `bgfx_blit`.
 	///
 	void blit(uint8_t _id, TextureHandle _dst, uint8_t _dstMip, uint16_t _dstX, uint16_t _dstY, uint16_t _dstZ, TextureHandle _src, uint8_t _srcMip = 0, uint16_t _srcX = 0, uint16_t _srcY = 0, uint16_t _srcZ = 0, uint16_t _width = UINT16_MAX, uint16_t _height = UINT16_MAX, uint16_t _depth = UINT16_MAX);
 
+	///
+	void blit(uint8_t _id, TextureHandle _dst, uint8_t _dstMip, uint16_t _dstX, uint16_t _dstY, uint16_t _dstZ, FrameBufferHandle _src, uint8_t _attachment = 0, uint8_t _srcMip = 0, uint16_t _srcX = 0, uint16_t _srcY = 0, uint16_t _srcZ = 0, uint16_t _width = UINT16_MAX, uint16_t _height = UINT16_MAX, uint16_t _depth = UINT16_MAX);
+
 	/// Request screen shot.
 	///
 	/// @param[in] _filePath Will be passed to `bgfx::CallbackI::screenShot` callback.

+ 3 - 1
include/bgfx/bgfxdefines.h

@@ -311,6 +311,7 @@
 #define BGFX_TEXTURE_COMPUTE_WRITE       UINT32_C(0x00100000) //!<
 #define BGFX_TEXTURE_SRGB                UINT32_C(0x00200000) //!<
 #define BGFX_TEXTURE_BLIT_DST            UINT32_C(0x00400000) //!<
+#define BGFX_TEXTURE_READ_BACK           UINT32_C(0x00800000) //!<
 #define BGFX_TEXTURE_BORDER_COLOR_SHIFT  24                   //!<
 #define BGFX_TEXTURE_BORDER_COLOR_MASK   UINT32_C(0x0f000000) //!<
 #define BGFX_TEXTURE_RESERVED_SHIFT      28                   //!<
@@ -367,7 +368,8 @@
 #define BGFX_CAPS_INDEX32                UINT64_C(0x0000000000002000) //!< 32-bit indices are supported.
 #define BGFX_CAPS_DRAW_INDIRECT          UINT64_C(0x0000000000004000) //!< Draw indirect is supported.
 #define BGFX_CAPS_HIDPI                  UINT64_C(0x0000000000008000) //!< HiDPI rendering is supported.
-#define BGFX_CAPS_BLIT                   UINT64_C(0x0000000000010000) //!< Blit is supported.
+#define BGFX_CAPS_TEXTURE_BLIT           UINT64_C(0x0000000000010000) //!< Texture blit is supported.
+#define BGFX_CAPS_TEXTURE_READ_BACK      UINT64_C(0x0000000000020000) //!< Read-back texture is supported.
 
 ///
 #define BGFX_CAPS_FORMAT_TEXTURE_NONE             UINT8_C(0x00) //!< Texture format is not supported.

+ 38 - 1
src/bgfx.cpp

@@ -977,7 +977,7 @@ namespace bgfx
 		CAPS_FLAGS(BGFX_CAPS_INDEX32),
 		CAPS_FLAGS(BGFX_CAPS_DRAW_INDIRECT),
 		CAPS_FLAGS(BGFX_CAPS_HIDPI),
-		CAPS_FLAGS(BGFX_CAPS_BLIT),
+		CAPS_FLAGS(BGFX_CAPS_TEXTURE_BLIT),
 #undef CAPS_FLAGS
 	};
 
@@ -2058,6 +2058,18 @@ again:
 				}
 				break;
 
+			case CommandBuffer::ReadTexture:
+				{
+					TextureHandle handle;
+					_cmdbuf.read(handle);
+
+					void* data;
+					_cmdbuf.read(data);
+
+					m_renderCtx->readTexture(handle, data);
+				}
+				break;
+
 			case CommandBuffer::ResizeTexture:
 				{
 					TextureHandle handle;
@@ -2903,6 +2915,20 @@ again:
 		}
 	}
 
+	void readTexture(TextureHandle _handle, void* _data)
+	{
+		BGFX_CHECK_MAIN_THREAD();
+		BX_CHECK(NULL != _data, "_data can't be NULL");
+		s_ctx->readTexture(_handle, _data);
+	}
+
+	void readTexture(FrameBufferHandle _handle, uint8_t _attachment, void* _data)
+	{
+		BGFX_CHECK_MAIN_THREAD();
+		BX_CHECK(NULL != _data, "_data can't be NULL");
+		s_ctx->readTexture(_handle, _attachment, _data);
+	}
+
 	FrameBufferHandle createFrameBuffer(uint16_t _width, uint16_t _height, TextureFormat::Enum _format, uint32_t _textureFlags)
 	{
 		_textureFlags |= _textureFlags&BGFX_TEXTURE_RT_MSAA_MASK ? 0 : BGFX_TEXTURE_RT;
@@ -3278,12 +3304,23 @@ again:
 		blit(_id, _dst, 0, _dstX, _dstY, 0, _src, 0, _srcX, _srcY, 0, _width, _height, 0);
 	}
 
+	void blit(uint8_t _id, TextureHandle _dst, uint16_t _dstX, uint16_t _dstY, FrameBufferHandle _src, uint8_t _attachment, uint16_t _srcX, uint16_t _srcY, uint16_t _width, uint16_t _height)
+	{
+		blit(_id, _dst, 0, _dstX, _dstY, 0, _src, _attachment, 0, _srcX, _srcY, 0, _width, _height, 0);
+	}
+
 	void blit(uint8_t _id, TextureHandle _dst, uint8_t _dstMip, uint16_t _dstX, uint16_t _dstY, uint16_t _dstZ, TextureHandle _src, uint8_t _srcMip, uint16_t _srcX, uint16_t _srcY, uint16_t _srcZ, uint16_t _width, uint16_t _height, uint16_t _depth)
 	{
 		BGFX_CHECK_MAIN_THREAD();
 		s_ctx->blit(_id, _dst, _dstMip, _dstX, _dstY, _dstZ, _src, _srcMip, _srcX, _srcY, _srcZ, _width, _height, _depth);
 	}
 
+	void blit(uint8_t _id, TextureHandle _dst, uint8_t _dstMip, uint16_t _dstX, uint16_t _dstY, uint16_t _dstZ, FrameBufferHandle _src, uint8_t _attachment, uint8_t _srcMip, uint16_t _srcX, uint16_t _srcY, uint16_t _srcZ, uint16_t _width, uint16_t _height, uint16_t _depth)
+	{
+		BGFX_CHECK_MAIN_THREAD();
+		s_ctx->blit(_id, _dst, _dstMip, _dstX, _dstY, _dstZ, _src, _attachment, _srcMip, _srcX, _srcY, _srcZ, _width, _height, _depth);
+	}
+
 	void saveScreenShot(const char* _filePath)
 	{
 		BGFX_CHECK_MAIN_THREAD();

+ 27 - 0
src/bgfx_p.h

@@ -583,6 +583,7 @@ namespace bgfx
 			DestroyTexture,
 			DestroyFrameBuffer,
 			DestroyUniform,
+			ReadTexture,
 			SaveScreenShot,
 		};
 
@@ -1941,6 +1942,7 @@ namespace bgfx
 		virtual void updateTextureBegin(TextureHandle _handle, uint8_t _side, uint8_t _mip) = 0;
 		virtual void updateTexture(TextureHandle _handle, uint8_t _side, uint8_t _mip, const Rect& _rect, uint16_t _z, uint16_t _depth, uint16_t _pitch, const Memory* _mem) = 0;
 		virtual void updateTextureEnd() = 0;
+		virtual void readTexture(TextureHandle _handle, void* _data) = 0;
 		virtual void resizeTexture(TextureHandle _handle, uint16_t _width, uint16_t _height) = 0;
 		virtual void destroyTexture(TextureHandle _handle) = 0;
 		virtual void createFrameBuffer(FrameBufferHandle _handle, uint8_t _num, const TextureHandle* _textureHandles) = 0;
@@ -2990,6 +2992,22 @@ namespace bgfx
 			textureDecRef(_handle);
 		}
 
+		BGFX_API_FUNC(void readTexture(TextureHandle _handle, void* _data) )
+		{
+			CommandBuffer& cmdbuf = getCommandBuffer(CommandBuffer::ReadTexture);
+			cmdbuf.write(_handle);
+			cmdbuf.write(_data);
+		}
+
+		BGFX_API_FUNC(void readTexture(FrameBufferHandle _handle, uint8_t _attachment, void* _data) )
+		{
+			const FrameBufferRef& ref = m_frameBufferRef[_handle.idx];
+			BX_CHECK(!ref.m_window, "Can't sample window frame buffer.");
+			TextureHandle textureHandle = ref.un.m_th[_attachment];
+			BX_CHECK(isValid(textureHandle), "Frame buffer texture %d is invalid.", _attachment);
+			readTexture(textureHandle, _data);
+		}
+
 		void resizeTexture(TextureHandle _handle, uint16_t _width, uint16_t _height)
 		{
 			const TextureRef& textureRef = m_textureRef[_handle.idx];
@@ -3623,6 +3641,15 @@ namespace bgfx
 			m_submit->blit(_id, _dst, _dstMip, _dstX, _dstY, _dstZ, _src, _srcMip, _srcX, _srcY, _srcZ, _width, _height, _depth);
 		}
 
+		BGFX_API_FUNC(void blit(uint8_t _id, TextureHandle _dst, uint8_t _dstMip, uint16_t _dstX, uint16_t _dstY, uint16_t _dstZ, FrameBufferHandle _src, uint8_t _attachment, uint8_t _srcMip, uint16_t _srcX, uint16_t _srcY, uint16_t _srcZ, uint16_t _width, uint16_t _height, uint16_t _depth) )
+		{
+			const FrameBufferRef& ref = m_frameBufferRef[_src.idx];
+			BX_CHECK(!ref.m_window, "Can't sample window frame buffer.");
+			TextureHandle textureHandle = ref.un.m_th[_attachment];
+			BX_CHECK(isValid(textureHandle), "Frame buffer texture %d is invalid.", _attachment);
+			blit(_id, _dst, _dstMip, _dstX, _dstY, _dstZ, textureHandle, _srcMip, _srcX, _srcY, _srcZ, _width, _height, _depth);
+		}
+
 		BGFX_API_FUNC(uint32_t frame() );
 
 		void dumpViewStats();

+ 7 - 0
src/glimports.h

@@ -121,6 +121,7 @@ typedef void           (GL_APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuin
 typedef void           (GL_APIENTRYP PFNGLGETACTIVEATTRIBPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name);
 typedef void           (GL_APIENTRYP PFNGLGETACTIVEUNIFORMPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name);
 typedef GLint          (GL_APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name);
+typedef void           (GL_APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint level, GLvoid *img);
 typedef GLuint         (GL_APIENTRYP PFNGLGETDEBUGMESSAGELOGPROC) (GLuint count, GLsizei bufsize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog);
 typedef GLenum         (GL_APIENTRYP PFNGLGETERRORPROC) (void);
 typedef void           (GL_APIENTRYP PFNGLGETFLOATVPROC) (GLenum pname, GLfloat *data);
@@ -139,6 +140,7 @@ typedef void           (GL_APIENTRYP PFNGLGETPROGRAMRESOURCEIVPROC) (GLuint prog
 typedef void           (GL_APIENTRYP PFNGLGETPROGRAMRESOURCENAMEPROC) (GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name);
 typedef GLint          (GL_APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONPROC) (GLuint program, GLenum programInterface, const GLchar *name);
 typedef GLint          (GL_APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONINDEXPROC) (GLuint program, GLenum programInterface, const GLchar *name);
+typedef void           (GL_APIENTRYP PFNGLGETTEXIMAGEPROC) (GLenum target, GLint level, GLenum format, GLenum type, void *pixels);
 typedef void           (GL_APIENTRYP PFNGLGETQUERYIVPROC) (GLenum target, GLenum pname, GLint *params);
 typedef void           (GL_APIENTRYP PFNGLGETQUERYOBJECTIVPROC) (GLuint id, GLenum pname, GLint *params);
 typedef void           (GL_APIENTRYP PFNGLGETQUERYOBJECTI64VPROC) (GLuint id, GLenum pname, GLint64 *params);
@@ -302,6 +304,7 @@ GL_IMPORT______(true,  PFNGLGENVERTEXARRAYSPROC,                   glGenVertexAr
 GL_IMPORT______(false, PFNGLGETACTIVEATTRIBPROC,                   glGetActiveAttrib);
 GL_IMPORT______(false, PFNGLGETATTRIBLOCATIONPROC,                 glGetAttribLocation);
 GL_IMPORT______(false, PFNGLGETACTIVEUNIFORMPROC,                  glGetActiveUniform);
+GL_IMPORT______(true,  PFNGLGETCOMPRESSEDTEXIMAGEPROC,             glGetCompressedTexImage);
 GL_IMPORT______(true,  PFNGLGETDEBUGMESSAGELOGPROC,                glGetDebugMessageLog);
 GL_IMPORT______(false, PFNGLGETERRORPROC,                          glGetError);
 GL_IMPORT______(false, PFNGLGETFLOATVPROC,                         glGetFloatv);
@@ -320,6 +323,7 @@ GL_IMPORT______(true,  PFNGLGETPROGRAMRESOURCEIVPROC,              glGetProgramR
 GL_IMPORT______(true,  PFNGLGETPROGRAMRESOURCENAMEPROC,            glGetProgramResourceName);
 GL_IMPORT______(true,  PFNGLGETPROGRAMRESOURCELOCATIONPROC,        glGetProgramResourceLocation);
 GL_IMPORT______(true,  PFNGLGETPROGRAMRESOURCELOCATIONINDEXPROC,   glGetProgramResourceLocationIndex);
+GL_IMPORT______(true,  PFNGLGETTEXIMAGEPROC,                       glGetTexImage);
 GL_IMPORT______(true,  PFNGLGETQUERYIVPROC,                        glGetQueryiv);
 GL_IMPORT______(true,  PFNGLGETQUERYOBJECTIVPROC,                  glGetQueryObjectiv);
 GL_IMPORT______(true,  PFNGLGETQUERYOBJECTI64VPROC,                glGetQueryObjecti64v);
@@ -519,6 +523,9 @@ GL_IMPORT_____x(true,  PFNGLMEMORYBARRIERPROC,                     glMemoryBarri
 GL_IMPORT_____x(true,  PFNGLDISPATCHCOMPUTEPROC,                   glDispatchCompute);
 GL_IMPORT_____x(true,  PFNGLDISPATCHCOMPUTEINDIRECTPROC,           glDispatchComputeIndirect);
 
+GL_IMPORT_____x(true,  PFNGLGETCOMPRESSEDTEXIMAGEPROC,             glGetCompressedTexImage);
+GL_IMPORT_____x(true,  PFNGLGETTEXIMAGEPROC,                       glGetTexImage);
+
 GL_IMPORT_NV___(true,  PFNGLDRAWBUFFERSPROC,                       glDrawBuffers);
 GL_IMPORT_NV___(true,  PFNGLGENQUERIESPROC,                        glGenQueries);
 GL_IMPORT_NV___(true,  PFNGLDELETEQUERIESPROC,                     glDeleteQueries);

+ 37 - 2
src/renderer_d3d11.cpp

@@ -1156,7 +1156,8 @@ BX_PRAGMA_DIAGNOSTIC_POP();
 					| BGFX_CAPS_SWAP_CHAIN
 					| (m_ovr.isInitialized() ? BGFX_CAPS_HMD : 0)
 					| BGFX_CAPS_DRAW_INDIRECT
-					| BGFX_CAPS_BLIT
+					| BGFX_CAPS_TEXTURE_BLIT
+					| BGFX_CAPS_TEXTURE_READ_BACK
 					);
 
 				if (m_featureLevel <= D3D_FEATURE_LEVEL_9_2)
@@ -1652,6 +1653,32 @@ BX_PRAGMA_DIAGNOSTIC_POP();
 		{
 		}
 
+		void readTexture(TextureHandle _handle, void* _data) BX_OVERRIDE
+		{
+			const TextureD3D11& texture = m_textures[_handle.idx];
+			D3D11_MAPPED_SUBRESOURCE mapped;
+			DX_CHECK(m_deviceCtx->Map(texture.m_ptr, 0, D3D11_MAP_READ, 0, &mapped) );
+
+			uint8_t* src      = (uint8_t*)mapped.pData;
+			uint32_t srcPitch = mapped.RowPitch;
+
+			const uint8_t bpp = getBitsPerPixel(TextureFormat::Enum(texture.m_textureFormat) );
+			uint8_t* dst      = (uint8_t*)_data;
+			uint32_t dstPitch = texture.m_width*bpp/8;
+
+			uint32_t pitch = bx::uint32_min(srcPitch, dstPitch);
+
+			for (uint32_t yy = 0, height = texture.m_height; yy < height; ++yy)
+			{
+				memcpy(dst, src, pitch);
+
+				src += srcPitch;
+				dst += dstPitch;
+			}
+
+			m_deviceCtx->Unmap(texture.m_ptr, 0);
+		}
+
 		void resizeTexture(TextureHandle _handle, uint16_t _width, uint16_t _height) BX_OVERRIDE
 		{
 			TextureD3D11& texture = m_textures[_handle.idx];
@@ -3903,11 +3930,12 @@ BX_PRAGMA_DIAGNOSTIC_POP();
 				}
 			}
 
-			const bool bufferOnly   = 0 != (m_flags&BGFX_TEXTURE_RT_BUFFER_ONLY);
+			const bool bufferOnly   = 0 != (m_flags&(BGFX_TEXTURE_RT_BUFFER_ONLY|BGFX_TEXTURE_READ_BACK) );
 			const bool computeWrite = 0 != (m_flags&BGFX_TEXTURE_COMPUTE_WRITE);
 			const bool renderTarget = 0 != (m_flags&BGFX_TEXTURE_RT_MASK);
 			const bool srgb         = 0 != (m_flags&BGFX_TEXTURE_SRGB) || imageContainer.m_srgb;
 			const bool blit         = 0 != (m_flags&BGFX_TEXTURE_BLIT_DST);
+			const bool readBack     = 0 != (m_flags&BGFX_TEXTURE_READ_BACK);
 			const uint32_t msaaQuality = bx::uint32_satsub( (m_flags&BGFX_TEXTURE_RT_MSAA_MASK)>>BGFX_TEXTURE_RT_MSAA_SHIFT, 1);
 			const DXGI_SAMPLE_DESC& msaa = s_msaa[msaaQuality];
 
@@ -3966,6 +3994,13 @@ BX_PRAGMA_DIAGNOSTIC_POP();
 						desc.Usage = D3D11_USAGE_DEFAULT;
 					}
 
+					if (readBack)
+					{
+						desc.BindFlags      = 0;
+						desc.Usage          = D3D11_USAGE_STAGING;
+						desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+					}
+
 					if (imageContainer.m_cubeMap)
 					{
 						desc.ArraySize = 6;

+ 5 - 1
src/renderer_d3d12.cpp

@@ -884,7 +884,7 @@ namespace bgfx { namespace d3d12
 									| BGFX_CAPS_COMPUTE
 									| (m_options.ROVsSupported ? BGFX_CAPS_FRAGMENT_ORDERING : 0)
 //									| BGFX_CAPS_SWAP_CHAIN
-									| BGFX_CAPS_BLIT
+									| BGFX_CAPS_TEXTURE_BLIT
 									);
 				g_caps.maxTextureSize   = 16384;
 				g_caps.maxFBAttachments = uint8_t(bx::uint32_min(16, BGFX_CONFIG_MAX_FRAME_BUFFER_ATTACHMENTS) );
@@ -1250,6 +1250,10 @@ namespace bgfx { namespace d3d12
 		{
 		}
 
+		void readTexture(TextureHandle /*_handle*/, void* /*_data*/) BX_OVERRIDE
+		{
+		}
+
 		void resizeTexture(TextureHandle _handle, uint16_t _width, uint16_t _height) BX_OVERRIDE
 		{
 			TextureD3D12& texture = m_textures[_handle.idx];

+ 33 - 1
src/renderer_d3d9.cpp

@@ -535,7 +535,8 @@ namespace bgfx { namespace d3d9
 				| BGFX_CAPS_FRAGMENT_DEPTH
 				| BGFX_CAPS_SWAP_CHAIN
 				| ( (UINT16_MAX < m_caps.MaxVertexIndex) ? BGFX_CAPS_INDEX32 : 0)
-				| ( (m_caps.DevCaps2 & D3DDEVCAPS2_CAN_STRETCHRECT_FROM_TEXTURES) ? BGFX_CAPS_BLIT : 0)
+//				| ( (m_caps.DevCaps2 & D3DDEVCAPS2_CAN_STRETCHRECT_FROM_TEXTURES) ? BGFX_CAPS_TEXTURE_BLIT : 0)
+//				| BGFX_CAPS_TEXTURE_READ_BACK
 				);
 			g_caps.maxTextureSize = uint16_t(bx::uint32_min(m_caps.MaxTextureWidth, m_caps.MaxTextureHeight) );
 //			g_caps.maxVertexIndex = m_caps.MaxVertexIndex;
@@ -902,6 +903,37 @@ namespace bgfx { namespace d3d9
 			m_updateTexture = NULL;
 		}
 
+		void readTexture(TextureHandle _handle, void* _data) BX_OVERRIDE
+		{
+			TextureD3D9& texture = m_textures[_handle.idx];
+
+			D3DLOCKED_RECT lockedRect;
+			DX_CHECK(texture.m_texture2d->LockRect(0
+				, &lockedRect
+				, NULL
+				, D3DLOCK_NO_DIRTY_UPDATE|D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY
+				) );
+
+			uint32_t srcPitch = lockedRect.Pitch;
+			uint8_t* src      = (uint8_t*)lockedRect.pBits;
+
+			const uint8_t bpp = getBitsPerPixel(TextureFormat::Enum(texture.m_textureFormat) );
+			uint8_t* dst      = (uint8_t*)_data;
+			uint32_t dstPitch = texture.m_width*bpp/8;
+
+			uint32_t pitch = bx::uint32_min(srcPitch, dstPitch);
+
+			for (uint32_t yy = 0, height = texture.m_height; yy < height; ++yy)
+			{
+				memcpy(dst, src, pitch);
+
+				src += srcPitch;
+				dst += dstPitch;
+			}
+
+			DX_CHECK(texture.m_texture2d->UnlockRect(0) );
+		}
+
 		void resizeTexture(TextureHandle _handle, uint16_t _width, uint16_t _height) BX_OVERRIDE
 		{
 			TextureD3D9& texture = m_textures[_handle.idx];

+ 33 - 1
src/renderer_gl.cpp

@@ -1204,6 +1204,7 @@ namespace bgfx { namespace gl
 			, m_maxMsaa(0)
 			, m_vao(0)
 			, m_blitSupported(false)
+			, m_readBackSupported(false)
 			, m_vaoSupport(false)
 			, m_samplerObjectSupport(false)
 			, m_shadowSamplersSupport(false)
@@ -1713,7 +1714,7 @@ namespace bgfx { namespace gl
 			{
 				m_blitSupported   = NULL != glCopyImageSubData;
 				g_caps.supported |= m_blitSupported
-					? BGFX_CAPS_BLIT
+					? BGFX_CAPS_TEXTURE_BLIT
 					: 0
 					;
 			}
@@ -2093,6 +2094,36 @@ namespace bgfx { namespace gl
 		{
 		}
 
+		void readTexture(TextureHandle _handle, void* _data) BX_OVERRIDE
+		{
+			if (m_readBackSupported)
+			{
+				const TextureGL& texture = m_textures[_handle.idx];
+				const bool compressed    = isCompressed(TextureFormat::Enum(texture.m_textureFormat) );
+
+				GL_CHECK(glBindTexture(texture.m_target, texture.m_id) );
+
+				if (compressed)
+				{
+					GL_CHECK(glGetCompressedTexImage(texture.m_target
+						, 0
+						, _data
+						) );
+				}
+				else
+				{
+					GL_CHECK(glGetTexImage(texture.m_target
+						, 0
+						, texture.m_fmt
+						, texture.m_type
+						, _data
+						) );
+				}
+
+				GL_CHECK(glBindTexture(texture.m_target, 0) );
+			}
+		}
+
 		void resizeTexture(TextureHandle _handle, uint16_t _width, uint16_t _height) BX_OVERRIDE
 		{
 			TextureGL& texture = m_textures[_handle.idx];
@@ -3106,6 +3137,7 @@ namespace bgfx { namespace gl
 		int32_t m_maxMsaa;
 		GLuint m_vao;
 		bool m_blitSupported;
+		bool m_readBackSupported;
 		bool m_vaoSupport;
 		bool m_samplerObjectSupport;
 		bool m_shadowSamplersSupport;

+ 4 - 0
src/renderer_mtl.mm

@@ -612,6 +612,10 @@ namespace bgfx { namespace mtl
 	{
 	}
 
+	void readTexture(TextureHandle /*_handle*/, void* /*_data*/) BX_OVERRIDE
+	{
+	}
+
 	void resizeTexture(TextureHandle _handle, uint16_t _width, uint16_t _height) BX_OVERRIDE
 	{
 		TextureMtl& texture = m_textures[_handle.idx];

+ 4 - 0
src/renderer_null.cpp

@@ -113,6 +113,10 @@ namespace bgfx { namespace noop
 		{
 		}
 
+		void readTexture(TextureHandle /*_handle*/, void* /*_data*/) BX_OVERRIDE
+		{
+		}
+
 		void resizeTexture(TextureHandle /*_handle*/, uint16_t /*_width*/, uint16_t /*_height*/) BX_OVERRIDE
 		{
 		}