Browse Source

Added blit.

Branimir Karadžić 10 years ago
parent
commit
ad5a46779a
10 changed files with 245 additions and 11 deletions
  1. 12 0
      include/bgfx/bgfx.h
  2. 1 0
      include/bgfx/bgfxdefines.h
  3. 3 0
      include/bgfx/c99/bgfx.h
  4. 50 1
      src/bgfx.cpp
  5. 58 2
      src/bgfx_p.h
  6. 4 0
      src/config.h
  7. 4 0
      src/glimports.h
  8. 32 0
      src/renderer_d3d11.cpp
  9. 39 6
      src/renderer_d3d9.cpp
  10. 42 2
      src/renderer_gl.cpp

+ 12 - 0
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.

+ 1 - 0
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                   //!<

+ 3 - 0
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);
 

+ 50 - 1
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);

+ 58 - 2
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();

+ 4 - 0
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

+ 4 - 0
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);

+ 32 - 0
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)

+ 39 - 6
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;

+ 42 - 2
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)