Browse Source

Added texture update batching. Added rect packing.

bkaradzic 12 years ago
parent
commit
435b83f1ac

+ 45 - 34
examples/08-update/update.cpp

@@ -11,9 +11,11 @@
 #include "../common/dbg.h"
 #include "../common/math.h"
 #include "../common/processevents.h"
+#include "../common/packrect.h"
 
 #include <stdio.h>
 #include <string.h>
+#include <list>
 
 struct PosColorVertex
 {
@@ -205,11 +207,7 @@ int _main_(int _argc, char** _argv)
 	bgfx::destroyFragmentShader(fsh);
 
 	uint32_t blockSide = 0;
-	uint32_t blockX = 0;
-	uint32_t blockY = 0;
-	const uint32_t blockWidth = 8;
-	const uint32_t blockHeight = 8;
-	const uint32_t textureSide = 256;
+	const uint32_t textureSide = 2048;
 
 	bgfx::TextureHandle textureCube = 
 		bgfx::createTextureCube(6
@@ -219,15 +217,18 @@ int _main_(int _argc, char** _argv)
 			, BGFX_TEXTURE_MIN_POINT|BGFX_TEXTURE_MAG_POINT|BGFX_TEXTURE_MIP_POINT
 			);
 
-	bgfx::TextureInfo ti;
-	bgfx::calcTextureSize(ti, blockWidth, blockHeight, 1, 1, bgfx::TextureFormat::BGRA8);
-
 	uint8_t rr = rand()%255;
 	uint8_t gg = rand()%255;
 	uint8_t bb = rand()%255;
 
 	int64_t updateTime = 0;
 
+	RectPackCubeT<256> cube(textureSide);
+
+	uint32_t hit = 0;
+	uint32_t miss = 0;
+	std::list<PackCube> quads;
+
 	while (!processEvents(width, height, debug, reset) )
 	{
 		// Set view 0 default viewport.
@@ -252,43 +253,53 @@ int _main_(int _argc, char** _argv)
 
 		if (now > updateTime)
 		{
-//			updateTime = now + freq/10;
-			const bgfx::Memory* mem = bgfx::alloc(ti.storageSize);
-			uint8_t* data = (uint8_t*)mem->data;
-			for (uint32_t ii = 0, num = ti.storageSize*8/ti.bitsPerPixel; ii < num; ++ii)
-			{
-				data[0] = bb;
-				data[1] = rr;
-				data[2] = gg;
-				data[3] = 0xff;
-				data += 4;
-			}
+			PackCube face;
 
-			bgfx::updateTextureCube(textureCube, blockSide, 0, blockX, blockY, blockWidth, blockHeight, mem);
+			uint32_t bw = bx::uint16_max(1, rand()%(textureSide/4) );
+			uint32_t bh = bx::uint16_max(1, rand()%(textureSide/4) );
 
-			blockX += 8;
-			if (blockX >= textureSide)
+			if (cube.find(bw, bh, face) )
 			{
-				blockX = 0;
-				blockY += 8;
+				quads.push_back(face);
+
+				++hit;
+				bgfx::TextureInfo ti;
+				const Pack2D& rect = face.m_rect;
+				bgfx::calcTextureSize(ti, rect.m_width, rect.m_height, 1, 1, bgfx::TextureFormat::BGRA8);
 
-				if (blockY >= textureSide)
+// 				updateTime = now + freq/10;
+				const bgfx::Memory* mem = bgfx::alloc(ti.storageSize);
+				uint8_t* data = (uint8_t*)mem->data;
+				for (uint32_t ii = 0, num = ti.storageSize*8/ti.bitsPerPixel; ii < num; ++ii)
 				{
-					rr = rand()%255;
-					gg = rand()%255;
-					bb = rand()%255;
+					data[0] = bb;
+					data[1] = rr;
+					data[2] = gg;
+					data[3] = 0xff;
+					data += 4;
+				}
+
+				bgfx::updateTextureCube(textureCube, face.m_side, 0, rect.m_x, rect.m_y, rect.m_width, rect.m_height, mem);
 
-					blockY = 0;
-					++blockSide;
+				rr = rand()%255;
+				gg = rand()%255;
+				bb = rand()%255;
+			}
+			else
+			{
+				++miss;
 
-					if (blockSide > 5)
-					{
-						blockSide = 0;
-					}
+				for (uint32_t ii = 0, num = bx::uint32_min(10, (uint32_t)quads.size() ); ii < num; ++ii)
+				{
+					const PackCube& face = quads.front();
+					cube.clear(face);
+					quads.pop_front();
 				}
 			}
 		}
 
+		bgfx::dbgTextPrintf(0, 4, 0x0f, "hit: %d, miss %d", hit, miss);
+
 		float at[3] = { 0.0f, 0.0f, 0.0f };
 		float eye[3] = { 0.0f, 0.0f, -5.0f };
 		

+ 177 - 0
examples/common/packrect.h

@@ -0,0 +1,177 @@
+/*
+ * Copyright 2011-2013 Branimir Karadzic. All rights reserved.
+ * License: http://www.opensource.org/licenses/BSD-2-Clause
+ */
+
+#ifndef __RECTPACK_H__
+#define __RECTPACK_H__
+
+#include <bx/uint32_t.h>
+
+struct Pack2D
+{
+	uint16_t m_x;
+	uint16_t m_y;
+	uint16_t m_width;
+	uint16_t m_height;
+};
+
+struct PackCube
+{
+	Pack2D m_rect;
+	uint8_t m_side;
+};
+
+template <uint16_t numBlocks>
+class RectPackCubeT;
+
+template <uint16_t numBlocks>
+class RectPack2DT
+{
+public:
+	RectPack2DT(uint16_t _width, uint16_t _height)
+	{
+		reset(_width, _height);
+	}
+
+	void reset(uint16_t _width, uint16_t _height)
+	{
+		m_bw = _width/64;
+		m_bh = _height/numBlocks;
+		memset(m_mem, 0xff, sizeof(m_mem) );
+	}
+
+	bool find(uint16_t _width, uint16_t _height, Pack2D& _pack)
+	{
+		uint16_t width  = bx::uint16_min(64, (_width  + m_bw - 1) / m_bw);
+		uint16_t height = bx::uint16_min(numBlocks, (_height + m_bh - 1) / m_bh);
+		uint16_t numx = 64-width;
+		uint16_t numy = numBlocks-height;
+
+		const uint64_t scan = width == 64 ? UINT64_MAX : (UINT64_C(1)<<width)-1;
+
+		for (uint16_t starty = 0; starty <= numy; ++starty)
+		{
+			uint64_t mem = m_mem[starty];
+			uint16_t ntz = (uint16_t)bx::uint64_cnttz(mem);
+			uint64_t mask = scan<<ntz;
+
+			for (uint16_t xx = ntz; xx <= numx; ++xx, mask <<= 1)
+			{
+				uint16_t yy = starty;
+				if ( (mem&mask) == mask)
+				{
+					uint16_t endy = starty + height;
+					while (yy < endy && (m_mem[yy]&mask) == mask)
+					{
+						++yy;
+					}
+
+					if (yy == endy)
+					{
+						uint64_t cmask = ~mask;
+						for (yy = starty; yy < endy; ++yy)
+						{
+							m_mem[yy] &= cmask;
+						}
+
+						_pack.m_x = xx * m_bw;
+						_pack.m_y = starty * m_bh;
+						_pack.m_width = width * m_bw;
+						_pack.m_height = height * m_bh;
+						return true;
+					}
+				}
+			}
+		}
+
+		return false;
+	}
+
+	void clear(const Pack2D& _pack)
+	{
+		uint16_t startx = bx::uint16_min(63, _pack.m_x / m_bw);
+		uint16_t starty = bx::uint16_min(numBlocks-1, _pack.m_y / m_bh);
+		uint16_t endx   = bx::uint16_min(64, (_pack.m_width + m_bw - 1) / m_bw + startx);
+		uint16_t endy   = bx::uint16_min(numBlocks, (_pack.m_height + m_bh - 1) / m_bh + starty);
+		uint16_t width  = endx - startx;
+
+		const uint64_t mask = (width == 64 ? UINT64_MAX : (UINT64_C(1)<<width)-1 )<<startx;
+
+		for (uint16_t yy = starty; yy < endy; ++yy)
+		{
+			m_mem[yy] |= mask;
+		}
+	}
+
+private:
+	friend class RectPackCubeT<numBlocks>;
+
+	RectPack2DT()
+	{
+	}
+
+	uint64_t m_mem[numBlocks];
+	uint16_t m_bw;
+	uint16_t m_bh;
+};
+
+template <uint16_t numBlocks>
+class RectPackCubeT
+{
+public:
+	RectPackCubeT(uint16_t _side)
+	{
+		reset(_side);
+	}
+
+	void reset(uint16_t _side)
+	{
+		for (uint32_t ii = 0; ii < 6; ++ii)
+		{
+			m_mru[ii] = ii;
+			m_ra[ii].reset(_side, _side);
+		}
+	}
+
+	bool find(uint16_t _width, uint16_t _height, PackCube& _pack)
+	{
+		bool found = false;
+		for (uint32_t ii = 0; ii < 6; ++ii)
+		{
+			uint8_t side = m_mru[ii];
+			found = m_ra[side].find(_width, _height, _pack.m_rect);
+
+			if (found)
+			{
+				_pack.m_side = side;
+				m_mru[ii] = m_mru[0];
+				m_mru[0] = side;
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	void clear(const PackCube& _pack)
+	{
+		uint8_t side = _pack.m_side;
+
+		uint32_t ii = 0;
+		for (; ii < 6 && m_mru[ii] != side; ++ii);
+
+		m_mru[ii] = m_mru[0];
+		m_mru[0] = side;
+
+		m_ra[side].clear(_pack.m_rect);
+	}
+
+private:
+	RectPackCubeT();
+
+	RectPack2DT<numBlocks> m_ra[6];
+	uint8_t m_mru[6];
+};
+
+#endif // __RECTPACK_H__

+ 131 - 24
src/bgfx_p.h

@@ -194,16 +194,6 @@ namespace bgfx
 	const char* getAttribName(Attrib::Enum _attr);
 	bool renderFrame();
 
-	inline uint16_t uint16_min(uint16_t _a, uint16_t _b)
-	{
-		return _a > _b ? _b : _a;
-	}
-
-	inline uint16_t uint16_max(uint16_t _a, uint16_t _b)
-	{
-		return _a < _b ? _b : _a;
-	}
-
 	inline uint32_t gcd(uint32_t _a, uint32_t _b)
 	{
 		do
@@ -374,6 +364,49 @@ namespace bgfx
 		bool m_init;
 	};
 
+	template <uint32_t maxKeys>
+	struct UpdateBatchT
+	{
+		UpdateBatchT()
+			: m_num(0)
+		{
+		}
+
+		void add(uint32_t _key, uint32_t _value)
+		{
+			uint32_t num = m_num++;
+			m_keys[num] = _key;
+			m_values[num] = _value;
+		}
+
+		bool sort()
+		{
+			if (0 < m_num)
+			{
+				uint32_t* tempKeys = (uint32_t*)alloca(sizeof(m_keys) );
+				uint32_t* tempValues = (uint32_t*)alloca(sizeof(m_values) );
+				bx::radixSort32(m_keys, tempKeys, m_values, tempValues, m_num);
+				return true;
+			}
+
+			return false;
+		}
+
+		bool isFull() const
+		{
+			return m_num >= maxKeys;
+		}
+
+		void reset()
+		{
+			m_num = 0;
+		}
+
+		uint32_t m_num;
+		uint32_t m_keys[maxKeys];
+		uint32_t m_values[maxKeys];
+	};
+
 	struct ClearQuad
 	{
 		void init();
@@ -480,6 +513,12 @@ namespace bgfx
 			read(reinterpret_cast<uint8_t*>(&_in), sizeof(Type) );
 		}
 
+		void skip(uint32_t _size)
+		{
+			BX_CHECK(m_pos < m_size, "");
+			m_pos += _size;
+		}
+
 		void reset()
 		{
 			m_pos = 0;
@@ -2369,7 +2408,9 @@ namespace bgfx
 		void rendererCreateProgram(ProgramHandle _handle, VertexShaderHandle _vsh, FragmentShaderHandle _fsh);
 		void rendererDestroyProgram(FragmentShaderHandle _handle);
 		void rendererCreateTexture(TextureHandle _handle, Memory* _mem, uint32_t _flags);
+		void rendererUpdateTextureBegin(TextureHandle _handle, uint8_t _side, uint8_t _mip);
 		void rendererUpdateTexture(TextureHandle _handle, uint8_t _side, uint8_t _mip, const Rect& _rect, uint16_t _z, uint16_t _depth, const Memory* _mem);
+		void rendererUpdateTextureEnd();
 		void rendererDestroyTexture(TextureHandle _handle);
 		void rendererCreateRenderTarget(RenderTargetHandle _handle, uint16_t _width, uint16_t _height, uint32_t _flags, uint32_t _textureFlags);
 		void rendererDestroyRenderTarget(RenderTargetHandle _handle);
@@ -2403,6 +2444,66 @@ namespace bgfx
 			}
 		}
 
+		void flushTextureUpdateBatch(CommandBuffer& _cmdbuf)
+		{
+			if (m_textureUpdateBatch.sort() )
+			{
+				const uint32_t pos = _cmdbuf.m_pos;
+
+				uint32_t currentKey = UINT32_MAX;
+
+				for (uint32_t ii = 0, num = m_textureUpdateBatch.m_num; ii < num; ++ii)
+				{
+					_cmdbuf.m_pos = m_textureUpdateBatch.m_values[ii];
+
+					TextureHandle handle;
+					_cmdbuf.read(handle);
+
+					uint8_t side;
+					_cmdbuf.read(side);
+
+					uint8_t mip;
+					_cmdbuf.read(mip);
+
+					Rect rect;
+					_cmdbuf.read(rect);
+
+					uint16_t zz;
+					_cmdbuf.read(zz);
+
+					uint16_t depth;
+					_cmdbuf.read(depth);
+
+					Memory* mem;
+					_cmdbuf.read(mem);
+
+					uint32_t key = m_textureUpdateBatch.m_keys[ii];
+					if (key != currentKey)
+					{
+						if (currentKey != UINT32_MAX)
+						{
+							rendererUpdateTextureEnd();
+						}
+						currentKey = key;
+						rendererUpdateTextureBegin(handle, side, mip);
+					}
+
+					rendererUpdateTexture(handle, side, mip, rect, zz, depth, mem);
+
+					release(mem);
+				}
+
+				if (currentKey != UINT32_MAX)
+				{
+					rendererUpdateTextureEnd();
+				}
+
+				m_textureUpdateBatch.reset();
+
+				_cmdbuf.m_pos = pos;
+			}
+		}
+
 		void rendererExecCommands(CommandBuffer& _cmdbuf)
 		{
 			_cmdbuf.reset();
@@ -2677,6 +2778,13 @@ namespace bgfx
 
 				case CommandBuffer::UpdateTexture:
 					{
+						if (m_textureUpdateBatch.isFull() )
+						{
+							flushTextureUpdateBatch(_cmdbuf);
+						}
+
+						uint32_t value = _cmdbuf.m_pos;
+
 						TextureHandle handle;
 						_cmdbuf.read(handle);
 
@@ -2686,21 +2794,14 @@ namespace bgfx
 						uint8_t mip;
 						_cmdbuf.read(mip);
 
-						Rect rect;
-						_cmdbuf.read(rect);
-
-						uint16_t zz;
-						_cmdbuf.read(zz);
+						_cmdbuf.skip(sizeof(Rect)+sizeof(uint16_t)+sizeof(uint16_t)+sizeof(Memory*) );
+ 
+ 						uint32_t key = (handle.idx<<16)
+ 									 | (side<<8)
+									 | mip
+ 									 ;
 
-						uint16_t depth;
-						_cmdbuf.read(depth);
-
-						Memory* mem;
-						_cmdbuf.read(mem);
-
-						rendererUpdateTexture(handle, side, mip, rect, zz, depth, mem);
-
-						release(mem);
+						m_textureUpdateBatch.add(key, value);
 					}
 					break;
 
@@ -2794,6 +2895,8 @@ namespace bgfx
 					break;
 				}
 			} while (!end);
+
+			flushTextureUpdateBatch(_cmdbuf);
 		}
 
 		void rendererSubmit();
@@ -2915,6 +3018,10 @@ namespace bgfx
 
 		bool m_rendererInitialized;
 		bool m_exit;
+
+		BX_CACHE_LINE_ALIGN_MARKER();
+		typedef UpdateBatchT<256> TextureUpdateBatch;
+		TextureUpdateBatch m_textureUpdateBatch;
 	};
 
 } // namespace bgfx

+ 9 - 1
src/renderer_d3d11.cpp

@@ -1799,7 +1799,7 @@ namespace bgfx
 			, ...
 			);
 #else
-		deviceCtx->UpdateSubresource(m_ptr, subres, &box, _mem->data, _rect.m_width*4, 0); 
+		deviceCtx->UpdateSubresource(m_ptr, subres, &box, _mem->data, _rect.m_width*4, 0);
 #endif // 0
 	}
 
@@ -2011,11 +2011,19 @@ namespace bgfx
 		s_renderCtx.m_textures[_handle.idx].create(_mem, _flags);
 	}
 
+	void Context::rendererUpdateTextureBegin(TextureHandle /*_handle*/, uint8_t /*_side*/, uint8_t /*_mip*/)
+	{
+	}
+
 	void Context::rendererUpdateTexture(TextureHandle _handle, uint8_t _side, uint8_t _mip, const Rect& _rect, uint16_t _z, uint16_t _depth, const Memory* _mem)
 	{
 		s_renderCtx.m_textures[_handle.idx].update(_side, _mip, _rect, _z, _depth, _mem);
 	}
 
+	void Context::rendererUpdateTextureEnd()
+	{
+	}
+
 	void Context::rendererDestroyTexture(TextureHandle _handle)
 	{
 		s_renderCtx.m_textures[_handle.idx].destroy();

+ 84 - 11
src/renderer_d3d9.cpp

@@ -881,6 +881,12 @@ namespace bgfx
 		UniformRegistry m_uniformReg;
 		void* m_uniforms[BGFX_CONFIG_MAX_UNIFORMS];
 
+		Texture* m_updateTexture;
+		uint8_t* m_updateTextureBits;
+		uint32_t m_updateTexturePitch;
+		uint8_t m_updateTextureSide;
+		uint8_t m_updateTextureMip;
+
 		TextVideoMem m_textVideoMem;
 		RenderTargetHandle m_rt;
 		bool m_rtMsaa;
@@ -1346,6 +1352,42 @@ namespace bgfx
 		BX_CHECK(false, "You should not be here.");
 	}
 
+	void Texture::dirty(uint8_t _side, const Rect& _rect)
+	{
+		switch (m_type)
+		{
+		case Texture2D:
+			{
+				RECT rect;
+				rect.left = _rect.m_x;
+				rect.top = _rect.m_y;
+				rect.right = rect.left + _rect.m_width;
+				rect.bottom = rect.top + _rect.m_height;
+				DX_CHECK(m_texture2d->AddDirtyRect(&rect) );
+			}
+			return;
+
+		case Texture3D:
+			{
+//				DX_CHECK(m_texture3d->AddDirtyRect(_box) );
+			}
+			return;
+
+		case TextureCube:
+			{
+				RECT rect;
+				rect.left = _rect.m_x;
+				rect.top = _rect.m_y;
+				rect.right = rect.left + _rect.m_width;
+				rect.bottom = rect.top + _rect.m_height;
+				DX_CHECK(m_textureCube->AddDirtyRect(D3DCUBEMAP_FACES(_side), &rect) );
+			}
+			return;
+		}
+
+		BX_CHECK(false, "You should not be here.");
+	}
+
 	void Texture::create(const Memory* _mem, uint32_t _flags)
 	{
 		m_tau = s_textureAddress[(_flags&BGFX_TEXTURE_U_MASK)>>BGFX_TEXTURE_U_SHIFT];
@@ -1548,22 +1590,41 @@ namespace bgfx
 		}
 	}
 
-	void Texture::update(uint8_t _side, uint8_t _mip, const Rect& _rect, uint16_t _z, uint16_t _depth, const Memory* _mem)
+	void Texture::updateBegin(uint8_t _side, uint8_t _mip)
 	{
-		uint32_t pitch;
 		uint32_t slicePitch;
-		uint8_t* bits = lock(_side, _mip, pitch, slicePitch, &_rect);
+		s_renderCtx.m_updateTextureSide = _side;
+		s_renderCtx.m_updateTextureMip = _mip;
+		s_renderCtx.m_updateTextureBits = lock(_side, _mip, s_renderCtx.m_updateTexturePitch, slicePitch);
+	}
 
-		uint32_t srcpitch = _rect.m_width*s_textureFormat[m_format].m_bpp/8;
-		uint32_t dstpitch = pitch;
-		for (uint32_t yy = 0, height = _rect.m_height; yy < height; ++yy)
+	void Texture::update(uint8_t _side, uint8_t _mip, const Rect& _rect, uint16_t _z, uint16_t _depth, const Memory* _mem)
+	{
+		uint32_t bpp = s_textureFormat[m_format].m_bpp;
+		uint32_t srcpitch = _rect.m_width*bpp/8;
+		uint32_t dstpitch = s_renderCtx.m_updateTexturePitch;
+		uint8_t* bits = s_renderCtx.m_updateTextureBits + _rect.m_y*dstpitch + _rect.m_x*bpp/8;
+
+		if (srcpitch == dstpitch)
 		{
-			uint8_t* src = &_mem->data[yy*srcpitch];
-			uint8_t* dst = &bits[yy*dstpitch];
-			memcpy(dst, src, srcpitch);
+			memcpy(bits, _mem->data, srcpitch*_rect.m_height);
+		}
+		else
+		{
+			for (uint32_t yy = 0, height = _rect.m_height; yy < height; ++yy)
+			{
+				uint8_t* src = &_mem->data[yy*srcpitch];
+				uint8_t* dst = &bits[yy*dstpitch];
+				memcpy(dst, src, srcpitch);
+			}
 		}
 
-		unlock(_side, _mip);
+		dirty(_side, _rect);
+	}
+
+	void Texture::updateEnd()
+	{
+		unlock(s_renderCtx.m_updateTextureSide, s_renderCtx.m_updateTextureMip);
 	}
 
 	void Texture::commit(uint8_t _stage)
@@ -1990,9 +2051,21 @@ namespace bgfx
 		s_renderCtx.m_textures[_handle.idx].create(_mem, _flags);
 	}
 
+	void Context::rendererUpdateTextureBegin(TextureHandle _handle, uint8_t _side, uint8_t _mip)
+	{
+		s_renderCtx.m_updateTexture = &s_renderCtx.m_textures[_handle.idx];
+		s_renderCtx.m_updateTexture->updateBegin(_side, _mip);
+	}
+
 	void Context::rendererUpdateTexture(TextureHandle _handle, uint8_t _side, uint8_t _mip, const Rect& _rect, uint16_t _z, uint16_t _depth, const Memory* _mem)
 	{
-		s_renderCtx.m_textures[_handle.idx].update(_side, _mip, _rect, _z, _depth, _mem);
+		s_renderCtx.m_updateTexture->update(_side, _mip, _rect, _z, _depth, _mem);
+	}
+
+	void Context::rendererUpdateTextureEnd()
+	{
+		s_renderCtx.m_updateTexture->updateEnd();
+		s_renderCtx.m_updateTexture = NULL;
 	}
 
 	void Context::rendererDestroyTexture(TextureHandle _handle)

+ 3 - 0
src/renderer_d3d9.h

@@ -320,6 +320,7 @@ namespace bgfx
 
 		uint8_t* lock(uint8_t _side, uint8_t _lod, uint32_t& _pitch, uint32_t& _slicePitch, const Rect* _rect = NULL);
 		void unlock(uint8_t _side, uint8_t _lod);
+		void dirty(uint8_t _side, const Rect& _rect);
 
 		void create(const Memory* _mem, uint32_t _flags);
 
@@ -328,7 +329,9 @@ namespace bgfx
 			DX_RELEASE(m_ptr, 0);
 		}
 
+		void updateBegin(uint8_t _side, uint8_t _mip);
 		void update(uint8_t _side, uint8_t _mip, const Rect& _rect, uint16_t _z, uint16_t _depth, const Memory* _mem);
+		void updateEnd();
 		void commit(uint8_t _stage);
 	
 		union

+ 8 - 0
src/renderer_gl.cpp

@@ -2187,11 +2187,19 @@ namespace bgfx
 		s_renderCtx.m_textures[_handle.idx].create(_mem, _flags);
 	}
 
+	void Context::rendererUpdateTextureBegin(TextureHandle _handle, uint8_t _side, uint8_t _mip)
+	{
+	}
+
 	void Context::rendererUpdateTexture(TextureHandle _handle, uint8_t _side, uint8_t _mip, const Rect& _rect, uint16_t _z, uint16_t _depth, const Memory* _mem)
 	{
 		s_renderCtx.m_textures[_handle.idx].update(_side, _mip, _rect, _z, _depth, _mem);
 	}
 
+	void Context::rendererUpdateTextureEnd()
+	{
+	}
+
 	void Context::rendererDestroyTexture(TextureHandle _handle)
 	{
 		s_renderCtx.m_textures[_handle.idx].destroy();

+ 8 - 0
src/renderer_null.cpp

@@ -124,10 +124,18 @@ namespace bgfx
 		}
 	}
 
+	void Context::rendererUpdateTextureBegin(TextureHandle /*_handle*/, uint8_t /*_side*/, uint8_t /*_mip*/)
+	{
+	}
+
 	void Context::rendererUpdateTexture(TextureHandle /*_handle*/, uint8_t /*_side*/, uint8_t /*_mip*/, const Rect& /*_rect*/, uint16_t /*_z*/, uint16_t /*_depth*/, const Memory* /*_mem*/)
 	{
 	}
 
+	void Context::rendererUpdateTextureEnd()
+	{
+	}
+
 	void Context::rendererDestroyTexture(TextureHandle /*_handle*/)
 	{
 	}