From ad5a46779a30f901b262be3a7d775e2505921599 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= <branimirkaradzic@gmail.com>
Date: Thu, 15 Oct 2015 16:38:59 -0700
Subject: [PATCH] Added blit.

---
 include/bgfx/bgfx.h        | 12 ++++++++
 include/bgfx/bgfxdefines.h |  1 +
 include/bgfx/c99/bgfx.h    |  3 ++
 src/bgfx.cpp               | 51 +++++++++++++++++++++++++++++++-
 src/bgfx_p.h               | 60 ++++++++++++++++++++++++++++++++++++--
 src/config.h               |  4 +++
 src/glimports.h            |  4 +++
 src/renderer_d3d11.cpp     | 32 ++++++++++++++++++++
 src/renderer_d3d9.cpp      | 45 ++++++++++++++++++++++++----
 src/renderer_gl.cpp        | 44 ++++++++++++++++++++++++++--
 10 files changed, 245 insertions(+), 11 deletions(-)

diff --git a/include/bgfx/bgfx.h b/include/bgfx/bgfx.h
index 52495322..0f9a9811 100644
--- a/include/bgfx/bgfx.h
+++ b/include/bgfx/bgfx.h
@@ -2046,6 +2046,18 @@ namespace bgfx
 	///
 	void discard();
 
+	/// Blit texture region between two textures.
+	///
+	/// @attention C99 equivalent is `bgfx_blit`.
+	///
+	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);
+
+	/// 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);
+
 	/// Request screen shot.
 	///
 	/// @param[in] _filePath Will be passed to `bgfx::CallbackI::screenShot` callback.
diff --git a/include/bgfx/bgfxdefines.h b/include/bgfx/bgfxdefines.h
index b239be1c..05f4bad0 100644
--- a/include/bgfx/bgfxdefines.h
+++ b/include/bgfx/bgfxdefines.h
@@ -310,6 +310,7 @@
 #define BGFX_TEXTURE_COMPARE_MASK        UINT32_C(0x000f0000) //!<
 #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_BORDER_COLOR_SHIFT  24                   //!<
 #define BGFX_TEXTURE_BORDER_COLOR_MASK   UINT32_C(0x0f000000) //!<
 #define BGFX_TEXTURE_RESERVED_SHIFT      28                   //!<
diff --git a/include/bgfx/c99/bgfx.h b/include/bgfx/c99/bgfx.h
index b7aeecda..89f401ca 100644
--- a/include/bgfx/c99/bgfx.h
+++ b/include/bgfx/c99/bgfx.h
@@ -770,6 +770,9 @@ BGFX_C_API uint32_t bgfx_dispatch_indirect(uint8_t _id, bgfx_program_handle_t _h
 /**/
 BGFX_C_API void bgfx_discard();
 
+/**/
+BGFX_C_API void bgfx_blit(uint8_t _id, bgfx_texture_handle_t _dst, uint8_t _dstMip, uint16_t _dstX, uint16_t _dstY, uint16_t _dstZ, bgfx_texture_handle_t _src, uint8_t _srcMip, uint16_t _srcX, uint16_t _srcY, uint16_t _srcZ, uint16_t _width, uint16_t _height, uint16_t _depth);
+
 /**/
 BGFX_C_API void bgfx_save_screen_shot(const char* _filePath);
 
diff --git a/src/bgfx.cpp b/src/bgfx.cpp
index 34d56ddb..622e3d4e 100644
--- a/src/bgfx.cpp
+++ b/src/bgfx.cpp
@@ -852,6 +852,31 @@ namespace bgfx
 		return m_num;
 	}
 
+	void Frame::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)
+	{
+		uint16_t item = m_numBlitItems++;
+
+		BlitItem& blit = m_blitItem[item];
+		blit.m_srcX   = _srcX;
+		blit.m_srcY	  =	_srcY;
+		blit.m_srcZ	  =	_srcZ;
+		blit.m_dstX	  =	_dstX;
+		blit.m_dstY	  =	_dstY;
+		blit.m_dstZ	  =	_dstZ;
+		blit.m_width  =	_width;
+		blit.m_height =	_height;
+		blit.m_depth  = _depth;
+		blit.m_srcMip = _srcMip;
+		blit.m_dstMip = _dstMip;
+		blit.m_src	  =	_src;
+		blit.m_dst	  =	_dst;
+
+		BlitKey key;
+		key.m_view = _id;
+		key.m_item = item;
+		m_blitKeys[item] = key.encode();
+	}
+
 	void Frame::sort()
 	{
 		for (uint32_t ii = 0, num = m_num; ii < num; ++ii)
@@ -859,6 +884,12 @@ namespace bgfx
 			m_sortKeys[ii] = SortKey::remapView(m_sortKeys[ii], m_viewRemap);
 		}
 		bx::radixSort64(m_sortKeys, s_ctx->m_tempKeys, m_sortValues, s_ctx->m_tempValues, m_num);
+
+		for (uint32_t ii = 0, num = m_num; ii < num; ++ii)
+		{
+			m_blitKeys[ii] = BlitKey::remapView(m_blitKeys[ii], m_viewRemap);
+		}
+		bx::radixSort32(m_blitKeys, (uint32_t*)&s_ctx->m_tempKeys, m_numBlitItems);
 	}
 
 	RenderFrame::Enum renderFrame()
@@ -1631,7 +1662,7 @@ again:
 			}
 			else
 			{
-#if 0
+#if 1
 				if (s_rendererCreator[RendererType::Metal].supported)
 				{
 					_type = RendererType::Metal;
@@ -3241,6 +3272,17 @@ again:
 		s_ctx->discard();
 	}
 
+	void blit(uint8_t _id, TextureHandle _dst, uint16_t _dstX, uint16_t _dstY, TextureHandle _src, uint16_t _srcX, uint16_t _srcY, uint16_t _width, uint16_t _height)
+	{
+		blit(_id, _dst, 0, _dstX, _dstY, 0, _src, 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 saveScreenShot(const char* _filePath)
 	{
 		BGFX_CHECK_MAIN_THREAD();
@@ -4059,6 +4101,13 @@ BGFX_C_API void bgfx_discard()
 	bgfx::discard();
 }
 
+BGFX_C_API void bgfx_blit(uint8_t _id, bgfx_texture_handle_t _dst, uint8_t _dstMip, uint16_t _dstX, uint16_t _dstY, uint16_t _dstZ, bgfx_texture_handle_t _src, uint8_t _srcMip, uint16_t _srcX, uint16_t _srcY, uint16_t _srcZ, uint16_t _width, uint16_t _height, uint16_t _depth)
+{
+	union { bgfx_texture_handle_t c; bgfx::TextureHandle cpp; } dst = { _dst };
+	union { bgfx_texture_handle_t c; bgfx::TextureHandle cpp; } src = { _src };
+	bgfx::blit(_id, dst.cpp, _dstMip, _dstX, _dstY, _dstZ, src.cpp, _srcMip, _srcX, _srcY, _srcZ, _width, _height, _depth);
+}
+
 BGFX_C_API void bgfx_save_screen_shot(const char* _filePath)
 {
 	bgfx::saveScreenShot(_filePath);
diff --git a/src/bgfx_p.h b/src/bgfx_p.h
index b7bbcf67..9913ed63 100644
--- a/src/bgfx_p.h
+++ b/src/bgfx_p.h
@@ -759,6 +759,34 @@ namespace bgfx
 	};
 #undef SORT_KEY_RENDER_DRAW
 
+	struct BlitKey
+	{
+		uint32_t encode()
+		{
+			return 0
+				| (uint32_t(m_view) << 24)
+				|  uint32_t(m_item)
+				;
+		}
+
+		void decode(uint32_t _key)
+		{
+			m_item = uint16_t(_key & UINT16_MAX);
+			m_view =  uint8_t(_key >> 24);
+		}
+
+		static uint32_t remapView(uint32_t _key, uint8_t _viewRemap[BGFX_CONFIG_MAX_VIEWS])
+		{
+			const uint8_t  oldView  = uint8_t(_key >> 24);
+			const uint32_t view     = uint32_t(_viewRemap[oldView]) << 24;
+			const uint32_t key      = (_key & ~UINT32_C(0xff000000) ) | view;
+			return key;
+		}
+
+		uint16_t m_item;
+		uint8_t  m_view;
+	};
+
 	BX_ALIGN_DECL_16(struct) Matrix4
 	{
 		union
@@ -1184,6 +1212,23 @@ namespace bgfx
 		RenderCompute compute;
 	};
 
+	struct BlitItem
+	{
+		uint16_t m_srcX;
+		uint16_t m_srcY;
+		uint16_t m_srcZ;
+		uint16_t m_dstX;
+		uint16_t m_dstY;
+		uint16_t m_dstZ;
+		uint16_t m_width;
+		uint16_t m_height;
+		uint16_t m_depth;
+		uint8_t  m_srcMip;
+		uint8_t  m_dstMip;
+		TextureHandle m_src;
+		TextureHandle m_dst;
+	};
+
 	struct Resolution
 	{
 		Resolution()
@@ -1274,9 +1319,10 @@ namespace bgfx
 			m_matrixCache.reset();
 			m_rectCache.reset();
 			m_key.reset();
-			m_num = 0;
+			m_num            = 0;
 			m_numRenderItems = 0;
-			m_numDropped = 0;
+			m_numDropped     = 0;
+			m_numBlitItems   = 0;
 			m_iboffset = 0;
 			m_vboffset = 0;
 			m_cmdPre.start();
@@ -1501,6 +1547,8 @@ namespace bgfx
 			return dispatch(_id, _handle, 0, 0, 0, _flags);
 		}
 
+		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);
+
 		void sort();
 
 		bool checkAvailTransientIndexBuffer(uint32_t _num)
@@ -1622,6 +1670,8 @@ namespace bgfx
 		RenderItem m_renderItem[BGFX_CONFIG_MAX_DRAW_CALLS+1];
 		RenderDraw m_draw;
 		RenderCompute m_compute;
+		uint32_t m_blitKeys[BGFX_CONFIG_MAX_BLIT_ITEMS+1];
+		BlitItem m_blitItem[BGFX_CONFIG_MAX_BLIT_ITEMS+1];
 		uint64_t m_flags;
 		uint32_t m_uniformBegin;
 		uint32_t m_uniformEnd;
@@ -1632,6 +1682,7 @@ namespace bgfx
 		RenderItemCount m_num;
 		RenderItemCount m_numRenderItems;
 		RenderItemCount m_numDropped;
+		uint16_t m_numBlitItems;
 
 		MatrixCache m_matrixCache;
 		RectCache m_rectCache;
@@ -3559,6 +3610,11 @@ namespace bgfx
 			m_submit->discard();
 		}
 
+		BGFX_API_FUNC(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) )
+		{
+			m_submit->blit(_id, _dst, _dstMip, _dstX, _dstY, _dstZ, _src, _srcMip, _srcX, _srcY, _srcZ, _width, _height, _depth);
+		}
+
 		BGFX_API_FUNC(uint32_t frame() );
 
 		void dumpViewStats();
diff --git a/src/config.h b/src/config.h
index 7aecc9b0..a150861a 100644
--- a/src/config.h
+++ b/src/config.h
@@ -189,6 +189,10 @@
 #	define BGFX_CONFIG_MAX_DRAW_CALLS ( (64<<10)-1)
 #endif // BGFX_CONFIG_MAX_DRAW_CALLS
 
+#ifndef BGFX_CONFIG_MAX_BLIT_ITEMS
+#	define BGFX_CONFIG_MAX_BLIT_ITEMS 256
+#endif // BGFX_CONFIG_MAX_BLIT_ITEMS
+
 #ifndef BGFX_CONFIG_MAX_MATRIX_CACHE
 #	define BGFX_CONFIG_MAX_MATRIX_CACHE (BGFX_CONFIG_MAX_DRAW_CALLS+1)
 #endif // BGFX_CONFIG_MAX_MATRIX_CACHE
diff --git a/src/glimports.h b/src/glimports.h
index 42f966ec..3467cc50 100644
--- a/src/glimports.h
+++ b/src/glimports.h
@@ -71,6 +71,7 @@ typedef void           (GL_APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum targ
 typedef void           (GL_APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data);
 typedef void           (GL_APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data);
 typedef void           (GL_APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data);
+typedef void           (GL_APIENTRYP PFNGLCOPYIMAGESUBDATAPROC) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
 typedef GLuint         (GL_APIENTRYP PFNGLCREATEPROGRAMPROC) (void);
 typedef GLuint         (GL_APIENTRYP PFNGLCREATESHADERPROC) (GLenum type);
 typedef void           (GL_APIENTRYP PFNGLCULLFACEPROC) (GLenum mode);
@@ -251,6 +252,7 @@ GL_IMPORT______(false, PFNGLCOMPRESSEDTEXIMAGE2DPROC,              glCompressedT
 GL_IMPORT______(false, PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC,           glCompressedTexSubImage2D);
 GL_IMPORT______(true , PFNGLCOMPRESSEDTEXIMAGE3DPROC,              glCompressedTexImage3D);
 GL_IMPORT______(true , PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC,           glCompressedTexSubImage3D);
+GL_IMPORT______(true , PFNGLCOPYIMAGESUBDATAPROC,                  glCopyImageSubData);
 GL_IMPORT______(false, PFNGLCREATEPROGRAMPROC,                     glCreateProgram);
 GL_IMPORT______(false, PFNGLCREATESHADERPROC,                      glCreateShader);
 GL_IMPORT______(false, PFNGLCULLFACEPROC,                          glCullFace);
@@ -451,6 +453,8 @@ GL_IMPORT______(true,  PFNGLGETTRANSLATEDSHADERSOURCEANGLEPROC,    glGetTranslat
 GL_IMPORT_ANGLE(true,  PFNGLBLITFRAMEBUFFERPROC,                   glBlitFramebuffer);
 GL_IMPORT_ANGLE(true,  PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC,    glRenderbufferStorageMultisample);
 
+GL_IMPORT_EXT__(true , PFNGLCOPYIMAGESUBDATAPROC,                  glCopyImageSubData);
+
 GL_IMPORT_KHR__(true,  PFNGLDEBUGMESSAGECONTROLPROC,               glDebugMessageControl);
 GL_IMPORT_KHR__(true,  PFNGLDEBUGMESSAGEINSERTPROC,                glDebugMessageInsert);
 GL_IMPORT_KHR__(true,  PFNGLDEBUGMESSAGECALLBACKPROC,              glDebugMessageCallback);
diff --git a/src/renderer_d3d11.cpp b/src/renderer_d3d11.cpp
index a7a383d3..5d437902 100644
--- a/src/renderer_d3d11.cpp
+++ b/src/renderer_d3d11.cpp
@@ -4450,6 +4450,11 @@ BX_PRAGMA_DIAGNOSTIC_POP();
 		uint16_t view = UINT16_MAX;
 		FrameBufferHandle fbh = BGFX_INVALID_HANDLE;
 
+		BlitKey blitKey;
+		blitKey.decode(_render->m_blitKeys[0]);
+		uint16_t numBlitItems = _render->m_numBlitItems;
+		uint16_t blitItem = 0;
+
 		const uint64_t primType = _render->m_debug&BGFX_DEBUG_WIREFRAME ? BGFX_STATE_PT_LINES : 0;
 		uint8_t primIndex = uint8_t(primType >> BGFX_STATE_PT_SHIFT);
 		PrimInfo prim = s_primInfo[primIndex];
@@ -4583,6 +4588,33 @@ BX_PRAGMA_DIAGNOSTIC_POP();
 						clearQuad(_clearQuad, viewState.m_rect, clr, _render->m_colorPalette);
 						prim = s_primInfo[BX_COUNTOF(s_primName)]; // Force primitive type update after clear quad.
 					}
+
+					for (; blitItem < numBlitItems && blitKey.m_view == view; blitItem++)
+					{
+						const BlitItem& blit = _render->m_blitItem[blitItem];
+						blitKey.decode(_render->m_blitKeys[blitItem+1]);
+
+						const TextureD3D11 src = m_textures[blit.m_src.idx];
+						const TextureD3D11 dst = m_textures[blit.m_dst.idx];
+
+						D3D11_BOX box;
+						box.left   = blit.m_srcX;
+						box.top    = blit.m_srcY;
+						box.front  = blit.m_srcZ;
+						box.right  = blit.m_srcX + blit.m_width;
+						box.bottom = blit.m_srcY + blit.m_height;
+						box.back   = blit.m_srcZ + bx::uint16_max(blit.m_depth, 1);
+
+						deviceCtx->CopySubresourceRegion(dst.m_ptr
+							, blit.m_dstMip
+							, blit.m_dstX
+							, blit.m_dstY
+							, blit.m_dstZ
+							, src.m_ptr
+							, blit.m_srcMip
+							, &box
+							);
+					}
 				}
 
 				if (isCompute)
diff --git a/src/renderer_d3d9.cpp b/src/renderer_d3d9.cpp
index d14956ab..b573bae0 100644
--- a/src/renderer_d3d9.cpp
+++ b/src/renderer_d3d9.cpp
@@ -2290,22 +2290,23 @@ namespace bgfx { namespace d3d9
 
 	void TextureD3D9::createTexture(uint32_t _width, uint32_t _height, uint8_t _numMips)
 	{
-		m_width = (uint16_t)_width;
-		m_height = (uint16_t)_height;
+		m_width   = (uint16_t)_width;
+		m_height  = (uint16_t)_height;
 		m_numMips = _numMips;
-		m_type = Texture2D;
+		m_type    = Texture2D;
 		const TextureFormat::Enum fmt = (TextureFormat::Enum)m_textureFormat;
 
 		DWORD usage = 0;
 		D3DPOOL pool = s_renderD3D9->m_pool;
 
 		const bool renderTarget = 0 != (m_flags&BGFX_TEXTURE_RT_MASK);
+		const bool blit         = 0 != (m_flags&BGFX_TEXTURE_BLIT_DST);
 		if (isDepth(fmt) )
 		{
 			usage = D3DUSAGE_DEPTHSTENCIL;
 			pool  = D3DPOOL_DEFAULT;
 		}
-		else if (renderTarget)
+		else if (renderTarget || blit)
 		{
 			usage = D3DUSAGE_RENDERTARGET;
 			pool  = D3DPOOL_DEFAULT;
@@ -2793,7 +2794,7 @@ namespace bgfx { namespace d3d9
 	{
 		TextureFormat::Enum fmt = (TextureFormat::Enum)m_textureFormat;
 		if (TextureFormat::Unknown != fmt
-		&& (isDepth(fmt) || !!(m_flags&BGFX_TEXTURE_RT_MASK) ) )
+		&& (isDepth(fmt) || !!(m_flags&(BGFX_TEXTURE_RT_MASK|BGFX_TEXTURE_BLIT_DST) ) ) )
 		{
 			DX_RELEASE(m_ptr, 0);
 			DX_RELEASE(m_surface, 0);
@@ -2804,7 +2805,7 @@ namespace bgfx { namespace d3d9
 	{
 		TextureFormat::Enum fmt = (TextureFormat::Enum)m_textureFormat;
 		if (TextureFormat::Unknown != fmt
-		&& (isDepth(fmt) || !!(m_flags&BGFX_TEXTURE_RT_MASK) ) )
+		&& (isDepth(fmt) || !!(m_flags&(BGFX_TEXTURE_RT_MASK|BGFX_TEXTURE_BLIT_DST)) ) )
 		{
 			createTexture(m_width, m_height, m_numMips);
 		}
@@ -3202,6 +3203,11 @@ namespace bgfx { namespace d3d9
 		FrameBufferHandle fbh = BGFX_INVALID_HANDLE;
 		uint32_t blendFactor = 0;
 
+		BlitKey blitKey;
+		blitKey.decode(_render->m_blitKeys[0]);
+		uint16_t numBlitItems = _render->m_numBlitItems;
+		uint16_t blitItem = 0;
+
 		uint8_t primIndex;
 		{
 			const uint64_t pt = _render->m_debug&BGFX_DEBUG_WIREFRAME ? BGFX_STATE_PT_LINES : 0;
@@ -3293,6 +3299,33 @@ namespace bgfx { namespace d3d9
 					DX_CHECK(device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE) );
 					DX_CHECK(device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE) );
 					DX_CHECK(device->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER) );
+
+					for (; blitItem < numBlitItems && blitKey.m_view == view; blitItem++)
+					{
+						const BlitItem& blit = _render->m_blitItem[blitItem];
+						blitKey.decode(_render->m_blitKeys[blitItem+1]);
+
+						const TextureD3D9 src = m_textures[blit.m_src.idx];
+						const TextureD3D9 dst = m_textures[blit.m_dst.idx];
+
+						RECT srcRect = { blit.m_srcX, blit.m_srcY, blit.m_srcX + blit.m_width, blit.m_srcY + blit.m_height };
+						RECT dstRect = { blit.m_dstX, blit.m_dstY, blit.m_dstX + blit.m_width, blit.m_dstY + blit.m_height };
+
+						IDirect3DSurface9* srcSurface;
+						DX_CHECK(src.m_texture2d->GetSurfaceLevel(blit.m_srcMip, &srcSurface) );
+
+						IDirect3DSurface9* dstSurface;
+						DX_CHECK(dst.m_texture2d->GetSurfaceLevel(blit.m_dstMip, &dstSurface) );
+
+						DX_CHECK(m_device->StretchRect(srcSurface
+							, &srcRect
+							, dstSurface
+							, &dstRect
+							, D3DTEXF_NONE
+							) );
+						srcSurface->Release();
+						dstSurface->Release();
+					}
 				}
 
 				uint16_t scissor = draw.m_scissor;
diff --git a/src/renderer_gl.cpp b/src/renderer_gl.cpp
index 557614ef..147e1302 100644
--- a/src/renderer_gl.cpp
+++ b/src/renderer_gl.cpp
@@ -441,6 +441,7 @@ namespace bgfx { namespace gl
 
 			ARB_compute_shader,
 			ARB_conservative_depth,
+			ARB_copy_image,
 			ARB_debug_label,
 			ARB_debug_output,
 			ARB_depth_buffer_float,
@@ -499,6 +500,7 @@ namespace bgfx { namespace gl
 			EXT_blend_subtract,
 			EXT_color_buffer_half_float,
 			EXT_color_buffer_float,
+			EXT_copy_image,
 			EXT_compressed_ETC1_RGB8_sub_texture,
 			EXT_debug_label,
 			EXT_debug_marker,
@@ -554,10 +556,12 @@ namespace bgfx { namespace gl
 			MOZ_WEBGL_compressed_texture_s3tc,
 			MOZ_WEBGL_depth_texture,
 
-			NV_texture_border_clamp,
+			NV_copy_image,
 			NV_draw_buffers,
+			NV_texture_border_clamp,
 			NVX_gpu_memory_info,
 
+			OES_copy_image,
 			OES_compressed_ETC1_RGB8_texture,
 			OES_depth24,
 			OES_depth32,
@@ -640,6 +644,7 @@ namespace bgfx { namespace gl
 
 		{ "ARB_compute_shader",                    BGFX_CONFIG_RENDERER_OPENGL >= 43, true  },
 		{ "ARB_conservative_depth",                BGFX_CONFIG_RENDERER_OPENGL >= 42, true  },
+		{ "ARB_copy_image",                        BGFX_CONFIG_RENDERER_OPENGL >= 42, true  },
 		{ "ARB_debug_label",                       false,                             true  },
 		{ "ARB_debug_output",                      BGFX_CONFIG_RENDERER_OPENGL >= 43, true  },
 		{ "ARB_depth_buffer_float",                BGFX_CONFIG_RENDERER_OPENGL >= 33, true  },
@@ -698,6 +703,7 @@ namespace bgfx { namespace gl
 		{ "EXT_blend_subtract",                    BGFX_CONFIG_RENDERER_OPENGL >= 14, true  },
 		{ "EXT_color_buffer_half_float",           false,                             true  }, // GLES2 extension.
 		{ "EXT_color_buffer_float",                false,                             true  }, // GLES2 extension.
+		{ "EXT_copy_image",                        false,                             true  }, // GLES2 extension.
 		{ "EXT_compressed_ETC1_RGB8_sub_texture",  false,                             true  }, // GLES2 extension.
 		{ "EXT_debug_label",                       false,                             true  },
 		{ "EXT_debug_marker",                      false,                             true  },
@@ -753,10 +759,12 @@ namespace bgfx { namespace gl
 		{ "MOZ_WEBGL_compressed_texture_s3tc",     false,                             true  },
 		{ "MOZ_WEBGL_depth_texture",               false,                             true  },
 
-		{ "NV_texture_border_clamp",               false,                             true  }, // GLES2 extension.
+		{ "NV_copy_image",                         false,                             true  },
 		{ "NV_draw_buffers",                       false,                             true  }, // GLES2 extension.
+		{ "NV_texture_border_clamp",               false,                             true  }, // GLES2 extension.
 		{ "NVX_gpu_memory_info",                   false,                             true  },
 
+		{ "OES_copy_image",                        false,                             true  },
 		{ "OES_compressed_ETC1_RGB8_texture",      false,                             true  },
 		{ "OES_depth24",                           false,                             true  },
 		{ "OES_depth32",                           false,                             true  },
@@ -5105,6 +5113,12 @@ namespace bgfx { namespace gl
 		SortKey key;
 		uint16_t view = UINT16_MAX;
 		FrameBufferHandle fbh = BGFX_INVALID_HANDLE;
+
+		BlitKey blitKey;
+		blitKey.decode(_render->m_blitKeys[0]);
+		uint16_t numBlitItems = _render->m_numBlitItems;
+		uint16_t blitItem = 0;
+
 		int32_t height = hmdEnabled
 					? _render->m_hmd.height
 					: _render->m_resolution.m_height
@@ -5257,6 +5271,32 @@ namespace bgfx { namespace gl
 					GL_CHECK(glDepthFunc(GL_LESS) );
 					GL_CHECK(glEnable(GL_CULL_FACE) );
 					GL_CHECK(glDisable(GL_BLEND) );
+
+					for (; blitItem < numBlitItems && blitKey.m_view == view; blitItem++)
+					{
+						const BlitItem& blit = _render->m_blitItem[blitItem];
+						blitKey.decode(_render->m_blitKeys[blitItem+1]);
+
+						const TextureGL src = m_textures[blit.m_src.idx];
+						const TextureGL dst = m_textures[blit.m_dst.idx];
+
+						GL_CHECK(glCopyImageSubData(src.m_id
+							, src.m_target
+							, blit.m_srcMip
+							, blit.m_srcX
+							, blit.m_srcY
+							, blit.m_srcZ
+							, dst.m_id
+							, dst.m_target
+							, blit.m_dstMip
+							, blit.m_dstX
+							, blit.m_dstY
+							, blit.m_dstZ
+							, blit.m_width
+							, blit.m_height
+							, bx::uint32_max(blit.m_depth, 1)
+							) );
+					}
 				}
 
 				if (isCompute)