Browse Source

Minimized allocations when updating TextSprite
Optimized number of allocations in GUIWidget::updateLayout
Fixed memory stack so it never frees memory to avoid constant alloc/dealloc cycles
Fixed frame alloc so it doesn't perform a block realloc if only one block remains
Fixed a crash in TextData

Marko Pintera 10 years ago
parent
commit
c1d0163465

+ 1 - 1
BansheeCore/Include/BsTextData.h

@@ -437,7 +437,7 @@ namespace BansheeEngine
 			UINT32 totalBufferSize = 0;
 			generatePersistentData(text, nullptr, totalBufferSize);
 
-			mData = (UINT8*)bs_alloc<GenAlloc>(totalBufferSize);
+			mData = (UINT8*)bs_alloc<Alloc>(totalBufferSize);
 			generatePersistentData(text, (UINT8*)mData, totalBufferSize);
 		}
 

+ 1 - 1
BansheeCore/Source/BsCoreRenderer.cpp

@@ -124,7 +124,7 @@ namespace BansheeEngine
 		std::shared_ptr<VertexData> vertexData = mesh->getVertexData();
 
 		rs.setVertexDeclaration(vertexData->vertexDeclaration);
-		auto vertexBuffers = vertexData->getBuffers();
+		auto& vertexBuffers = vertexData->getBuffers();
 
 		if (vertexBuffers.size() > 0)
 		{

+ 4 - 0
BansheeCore/Source/BsTextData.cpp

@@ -557,6 +557,10 @@ namespace BansheeEngine
 		LineBufferSize = 500;
 		PageBufferSize = 20;
 
+		NextFreeWord = 0;
+		NextFreeLine = 0;
+		NextFreePageInfo = 0;
+
 		WordBuffer = bs_newN<TextWord>(WordBufferSize);
 		LineBuffer = bs_newN<TextLine>(LineBufferSize);
 		PageBuffer = bs_newN<PageInfo>(PageBufferSize);

+ 5 - 5
BansheeEditor/Include/BsGUITextField.h

@@ -178,6 +178,11 @@ namespace BansheeEngine
 		 */
 		bool hasInputFocus() const { return mHasInputFocus; }
 
+		/**
+		 * @copydoc	GUIElement::setTint
+		 */
+		virtual void setTint(const Color& color) override;
+
 		Event<void(const WString&)> onValueChanged; /** Triggered when the value in the field changes. */
 	protected:
 		static const UINT32 DEFAULT_LABEL_WIDTH;
@@ -189,11 +194,6 @@ namespace BansheeEngine
 		 */
 		void styleUpdated() override;
 
-		/**
-		 * @copydoc	GUIElement::setTint
-		 */
-		virtual void setTint(const Color& color) override;
-
 		/**
 		 * @copydoc	GUIElement::_updateLayoutInternal
 		 */

+ 5 - 5
BansheeEngine/Include/BsGUIPanel.h

@@ -24,18 +24,18 @@ namespace BansheeEngine
 		/**
 		 * @brief	Calculate optimal sizes of all child layout elements.
 		 */
-		void _updateOptimalLayoutSizes();
+		void _updateOptimalLayoutSizes() override;
 
 		/**
 		 * @copydoc	GUIElementBase::_calculateLayoutSizeRange
 		 */
-		virtual LayoutSizeRange _calculateLayoutSizeRange() const;
+		virtual LayoutSizeRange _calculateLayoutSizeRange() const override;
 
 		/**
 		 * @copydoc	GUILayout::_getElementAreas
 		 */
 		void _getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
-			const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const;
+			const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const override;
 
 		/**
 		 * @brief	Calculates the size of the provided child within this layout with the provided dimensions.
@@ -62,7 +62,7 @@ namespace BansheeEngine
 		/**
 		 * @copydoc	GUILayout::_calcActualSize
 		 */
-		virtual Vector2I _calcActualSize(INT32 x, INT32 y, Rect2I* elementAreas, UINT32 numElements) const;
+		virtual Vector2I _calcActualSize(INT32 x, INT32 y, Rect2I* elementAreas, UINT32 numElements) const override;
 
 		/**
 		 * @brief	Changes values that control at which depth is GUI panel and its children rendered.
@@ -123,7 +123,7 @@ namespace BansheeEngine
 		/**
 		 * @copydoc	GUIElementBase::_updateLayoutInternal
 		 */
-		void _updateLayoutInternal(const GUILayoutData& data);
+		void _updateLayoutInternal(const GUILayoutData& data) override;
 
 		INT16 mDepthOffset;
 		UINT16 mDepthRangeMin;

+ 1 - 1
BansheeEngine/Include/BsGUIWidget.h

@@ -193,7 +193,7 @@ namespace BansheeEngine
 
 		HEvent mOwnerTargetResizedConn;
 
-		UnorderedSet<GUIElement*> mDirtyContents;
+		Set<GUIElement*> mDirtyContents;
 
 		mutable bool mWidgetIsDirty;
 		mutable Rect2I mBounds;

+ 7 - 0
BansheeEngine/Include/BsImageSprite.h

@@ -43,6 +43,7 @@ namespace BansheeEngine
 	{
 	public:
 		ImageSprite();
+		~ImageSprite();
 
 		/**
 		 * @brief	Recreates internal sprite data according the specified description structure.
@@ -53,5 +54,11 @@ namespace BansheeEngine
 		 *					share the same material if they use different world transform matrices)
 		 */
 		void update(const IMAGE_SPRITE_DESC& desc, UINT64 groupId);
+
+	private:
+		/**
+		 * @brief	Clears internal geometry buffers.
+		 */
+		void clearMesh();
 	};
 }

+ 0 - 5
BansheeEngine/Include/BsSprite.h

@@ -136,11 +136,6 @@ namespace BansheeEngine
 		 */
 		void updateBounds() const;
 
-		/**
-		 * @brief	Clears internal geometry buffers.
-		 */
-		void clearMesh() const;
-
 		mutable Rect2I mBounds;
 		mutable Vector<SpriteRenderElement> mCachedRenderElements;
 	};

+ 17 - 2
BansheeEngine/Include/BsTextSprite.h

@@ -4,6 +4,7 @@
 #include "BsSprite.h"
 #include "BsTextData.h"
 #include "BsColor.h"
+#include "BsStaticAlloc.h"
 
 namespace BansheeEngine
 {
@@ -54,6 +55,7 @@ namespace BansheeEngine
 	{
 	public:
 		TextSprite();
+		~TextSprite();
 
 		/**
 		 * @brief	Recreates internal sprite data according the specified description structure.
@@ -74,9 +76,11 @@ namespace BansheeEngine
 		 * @param	height		Height of the text bounds into which to constrain the text, in pixels.
 		 * @param	horzAlign	Specifies how is text horizontally aligned within its bounds.
 		 * @param	vertAlign	Specifies how is text vertically aligned within its bounds.
+		 * @param	output		Pre-allocated buffer to output the results in. Buffer must have an element
+		 *						for every line in \p textData.
 		 */
-		static Vector<Vector2I> getAlignmentOffsets(const TextDataBase& textData, 
-			UINT32 width, UINT32 height, TextHorzAlign horzAlign, TextVertAlign vertAlign);
+		static void getAlignmentOffsets(const TextDataBase& textData, 
+			UINT32 width, UINT32 height, TextHorzAlign horzAlign, TextVertAlign vertAlign, Vector2I* output);
 
 		/**
 		 * @brief	Calculates text quads you may use for text rendering, based on the specified text
@@ -119,5 +123,16 @@ namespace BansheeEngine
 		static UINT32 genTextQuads(const TextDataBase& textData, UINT32 width, UINT32 height,
 			TextHorzAlign horzAlign, TextVertAlign vertAlign, SpriteAnchor anchor, Vector2* vertices, Vector2* uv, UINT32* indices, 
 			UINT32 bufferSizeQuads);
+
+	private:
+		static const int STATIC_CHARS_TO_BUFFER = 25;
+		static const int STATIC_BUFFER_SIZE = STATIC_CHARS_TO_BUFFER * (4 * (2 * sizeof(Vector2)) + (6 * sizeof(UINT32)));
+
+		/**
+		 * @brief	Clears internal geometry buffers.
+		 */
+		void clearMesh();
+
+		mutable StaticAlloc<STATIC_BUFFER_SIZE, STATIC_BUFFER_SIZE> mAlloc;
 	};
 }

+ 8 - 4
BansheeEngine/Source/BsGUIInputTool.cpp

@@ -33,9 +33,9 @@ namespace BansheeEngine
 				mNumQuads += textData.getNumQuadsForPage(i);
 
 			if (mQuads != nullptr)
-				bs_delete<ScratchAlloc>(mQuads);
+				bs_delete(mQuads);
 
-			mQuads = bs_newN<Vector2, ScratchAlloc>(mNumQuads * 4);
+			mQuads = bs_newN<Vector2>(mNumQuads * 4);
 
 			TextSprite::genTextQuads(textData, mTextDesc.width, mTextDesc.height, mTextDesc.horzAlign, mTextDesc.vertAlign, mTextDesc.anchor,
 				mQuads, nullptr, nullptr, mNumQuads);
@@ -45,8 +45,10 @@ namespace BansheeEngine
 			// Store cached line data
 			UINT32 curCharIdx = 0;
 			UINT32 curLineIdx = 0;
-			Vector<Vector2I> alignmentOffsets = TextSprite::getAlignmentOffsets(textData, mTextDesc.width,
-				mTextDesc.height, mTextDesc.horzAlign, mTextDesc.vertAlign);
+
+			Vector2I* alignmentOffsets = bs_frame_new<Vector2I>(numLines);
+			TextSprite::getAlignmentOffsets(textData, mTextDesc.width, mTextDesc.height, mTextDesc.horzAlign, 
+				mTextDesc.vertAlign, alignmentOffsets);
 
 			for (UINT32 i = 0; i < numLines; i++)
 			{
@@ -66,6 +68,8 @@ namespace BansheeEngine
 				curCharIdx = lineDesc.getEndChar();
 				curLineIdx++;
 			}
+
+			bs_frame_delete(alignmentOffsets);
 		}
 		bs_frame_clear();
 	}

+ 0 - 22
BansheeEngine/Source/BsGUIManager.cpp

@@ -359,12 +359,10 @@ namespace BansheeEngine
 
 			for(auto& widget : renderData.widgets)
 			{
-				gProfilerCPU().beginSample("Widget::isDirty");
 				if (widget->isDirty(true))
 				{
 					isDirty = true;
 				}
-				gProfilerCPU().endSample("Widget::isDirty");
 			}
 
 			if(!isDirty)
@@ -372,8 +370,6 @@ namespace BansheeEngine
 
 			bs_frame_mark();
 			{
-				gProfilerCPU().beginSample("Sort elements");
-
 				// Make a list of all GUI elements, sorted from farthest to nearest (highest depth to lowest)
 				auto elemComp = [](const GUIGroupElement& a, const GUIGroupElement& b)
 				{
@@ -389,7 +385,6 @@ namespace BansheeEngine
 
 				FrameSet<GUIGroupElement, std::function<bool(const GUIGroupElement&, const GUIGroupElement&)>> allElements(elemComp);
 
-				gProfilerCPU().beginSample("InsertAll");
 				for (auto& widget : renderData.widgets)
 				{
 					const Vector<GUIElement*>& elements = widget->getElements();
@@ -406,7 +401,6 @@ namespace BansheeEngine
 						}
 					}
 				}
-				gProfilerCPU().endSample("InsertAll");
 
 				// Group the elements in such a way so that we end up with a smallest amount of
 				// meshes, without breaking back to front rendering order
@@ -417,13 +411,9 @@ namespace BansheeEngine
 					UINT32 renderElemIdx = elem.renderElement;
 					UINT32 elemDepth = guiElem->_getRenderElementDepth(renderElemIdx);
 
-					gProfilerCPU().beginSample("Tfrm");
-
 					Rect2I tfrmedBounds = guiElem->_getClippedBounds();
 					tfrmedBounds.transform(guiElem->_getParentWidget()->SO()->getWorldTfrm());
 
-					gProfilerCPU().endSample("Tfrm");
-
 					const GUIMaterialInfo& matInfo = guiElem->_getMaterial(renderElemIdx);
 
 					UINT64 materialId = matInfo.material->getInternalID(); // TODO - I group based on material ID. So if two widgets used exact copies of the same material
@@ -495,8 +485,6 @@ namespace BansheeEngine
 						}
 					}
 
-					gProfilerCPU().beginSample("AddToGroup");
-
 					if (foundGroup == nullptr)
 					{
 						allGroups.push_back(GUIMaterialGroup());
@@ -515,8 +503,6 @@ namespace BansheeEngine
 						foundGroup->depth = std::min(foundGroup->depth, elemDepth);
 						foundGroup->numQuads += guiElem->_getNumQuads(renderElemIdx);
 					}
-
-					gProfilerCPU().endSample("AddToGroup");
 				}
 
 				// Make a list of all GUI elements, sorted from farthest to nearest (highest depth to lowest)
@@ -536,10 +522,6 @@ namespace BansheeEngine
 					}
 				}
 
-				gProfilerCPU().endSample("Sort elements");
-
-				gProfilerCPU().beginSample("Mesh data");
-
 				UINT32 numMeshes = (UINT32)sortedGroups.size();
 				UINT32 oldNumMeshes = (UINT32)renderData.cachedMeshes.size();
 
@@ -597,7 +579,6 @@ namespace BansheeEngine
 						quadOffset += numQuads;
 					}
 
-					gProfilerCPU().beginSample("alloc/dealloc mesh data");
 					if(groupIdx < (UINT32)renderData.cachedMeshes.size())
 					{
 						mMeshHeap->dealloc(renderData.cachedMeshes[groupIdx]);
@@ -607,12 +588,9 @@ namespace BansheeEngine
 					{
 						renderData.cachedMeshes.push_back(mMeshHeap->alloc(meshData));
 					}
-					gProfilerCPU().endSample("alloc/dealloc mesh data");
 
 					groupIdx++;
 				}
-
-				gProfilerCPU().endSample("Mesh data");
 			}
 
 			bs_frame_clear();			

+ 22 - 16
BansheeEngine/Source/BsGUIWidget.cpp

@@ -112,8 +112,10 @@ namespace BansheeEngine
 
 	void GUIWidget::_updateLayout()
 	{
+		bs_frame_mark();
+
 		// Determine dirty contents and layouts
-		Stack<GUIElementBase*> todo;
+		FrameStack<GUIElementBase*> todo;
 		todo.push(mPanel);
 
 		while (!todo.empty())
@@ -138,6 +140,8 @@ namespace BansheeEngine
 					todo.push(currentElem->_getChild(i));
 			}
 		}
+
+		bs_frame_clear();
 	}
 
 	void GUIWidget::_updateLayout(GUIElementBase* elem)
@@ -177,23 +181,27 @@ namespace BansheeEngine
 		}
 
 		// Mark dirty contents
-		Stack<GUIElementBase*> todo;
-		todo.push(elem);
-
-		while (!todo.empty())
+		bs_frame_mark();
 		{
-			GUIElementBase* currentElem = todo.top();
-			todo.pop();
+			FrameStack<GUIElementBase*> todo;
+			todo.push(elem);
+
+			while (!todo.empty())
+			{
+				GUIElementBase* currentElem = todo.top();
+				todo.pop();
 
-			if (currentElem->_getType() == GUIElementBase::Type::Element)
-				mDirtyContents.insert(static_cast<GUIElement*>(currentElem));
+				if (currentElem->_getType() == GUIElementBase::Type::Element)
+					mDirtyContents.insert(static_cast<GUIElement*>(currentElem));
 
-			currentElem->_markAsClean();
+				currentElem->_markAsClean();
 
-			UINT32 numChildren = currentElem->_getNumChildren();
-			for (UINT32 i = 0; i < numChildren; i++)
-				todo.push(currentElem->_getChild(i));
+				UINT32 numChildren = currentElem->_getNumChildren();
+				for (UINT32 i = 0; i < numChildren; i++)
+					todo.push(currentElem->_getChild(i));
+			}
 		}
+		bs_frame_clear();
 	}
 
 	bool GUIWidget::_mouseEvent(GUIElement* element, const GUIMouseEvent& ev)
@@ -275,9 +283,7 @@ namespace BansheeEngine
 			mWidgetIsDirty = false;
 
 			for (auto& dirtyElement : mDirtyContents)
-			{
-				PROFILE_CALL(dirtyElement->_updateRenderElements(), "UpdateDirty");
-			}
+				dirtyElement->_updateRenderElements();
 
 			mDirtyContents.clear();
 			updateBounds();

+ 39 - 3
BansheeEngine/Source/BsImageSprite.cpp

@@ -13,6 +13,11 @@ namespace BansheeEngine
 
 	}
 
+	ImageSprite::~ImageSprite()
+	{
+		clearMesh();
+	}
+
 	void ImageSprite::update(const IMAGE_SPRITE_DESC& desc, UINT64 groupId)
 	{
 		const FontData* fontData = nullptr;
@@ -22,8 +27,6 @@ namespace BansheeEngine
 			return;
 		}
 
-		gProfilerCPU().beginSample("UpdateImageSprite");
-
 		// Actually generate a mesh
 		if(mCachedRenderElements.size() < 1)
 			mCachedRenderElements.resize(1);
@@ -262,7 +265,40 @@ namespace BansheeEngine
 		}
 
 		updateBounds();
+	}
+
+	void ImageSprite::clearMesh()
+	{
+		for (auto& renderElem : mCachedRenderElements)
+		{
+			UINT32 vertexCount = renderElem.numQuads * 4;
+			UINT32 indexCount = renderElem.numQuads * 6;
+
+			if (renderElem.vertices != nullptr)
+			{
+				bs_deleteN<ScratchAlloc>(renderElem.vertices, vertexCount);
+				renderElem.vertices = nullptr;
+			}
 
-		gProfilerCPU().endSample("UpdateImageSprite");
+			if (renderElem.uvs != nullptr)
+			{
+				bs_deleteN<ScratchAlloc>(renderElem.uvs, vertexCount);
+				renderElem.uvs = nullptr;
+			}
+
+			if (renderElem.indexes != nullptr)
+			{
+				bs_deleteN<ScratchAlloc>(renderElem.indexes, indexCount);
+				renderElem.indexes = nullptr;
+			}
+
+			if (renderElem.matInfo.material != nullptr)
+			{
+				GUIMaterialManager::instance().releaseMaterial(renderElem.matInfo);
+			}
+		}
+
+		mCachedRenderElements.clear();
+		updateBounds();
 	}
 }

+ 2 - 41
BansheeEngine/Source/BsSprite.cpp

@@ -6,14 +6,10 @@
 namespace BansheeEngine
 {
 	Sprite::Sprite()
-	{
-
-	}
+	{ }
 
 	Sprite::~Sprite()
-	{
-		clearMesh();
-	}
+	{ }
 
 	Rect2I Sprite::getBounds(const Vector2I& offset, const Rect2I& clipRect) const 
 	{
@@ -242,41 +238,6 @@ namespace BansheeEngine
 		mBounds = Rect2I((int)min.x, (int)min.y, (int)(max.x - min.x), (int)(max.y - min.y));
 	}
 
-	void Sprite::clearMesh() const
-	{
-		for(auto& renderElem : mCachedRenderElements)
-		{
-			UINT32 vertexCount = renderElem.numQuads * 4;
-			UINT32 indexCount = renderElem.numQuads * 6;
-
-			if (renderElem.vertices != nullptr)
-			{
-				bs_deleteN<ScratchAlloc>(renderElem.vertices, vertexCount);
-				renderElem.vertices = nullptr;
-			}
-
-			if (renderElem.uvs != nullptr)
-			{
-				bs_deleteN<ScratchAlloc>(renderElem.uvs, vertexCount);
-				renderElem.uvs = nullptr;
-			}
-
-			if (renderElem.indexes != nullptr)
-			{
-				bs_deleteN<ScratchAlloc>(renderElem.indexes, indexCount);
-				renderElem.indexes = nullptr;
-			}
-
-			if(renderElem.matInfo.material != nullptr)
-			{
-				GUIMaterialManager::instance().releaseMaterial(renderElem.matInfo);
-			}
-		}
-
-		mCachedRenderElements.clear();
-		updateBounds();
-	}
-
 	// This will only properly clip an array of quads
 	// Vertices in the quad must be in a specific order: top left, top right, bottom left, bottom right
 	// (0, 0) represents top left of the screen

+ 67 - 47
BansheeEngine/Source/BsTextSprite.cpp

@@ -13,10 +13,13 @@ namespace BansheeEngine
 
 	}
 
-	void TextSprite::update(const TEXT_SPRITE_DESC& desc, UINT64 groupId)
+	TextSprite::~TextSprite()
 	{
-		gProfilerCPU().beginSample("UpdateTextSprite");
+		clearMesh();
+	}
 
+	void TextSprite::update(const TEXT_SPRITE_DESC& desc, UINT64 groupId)
+	{
 		bs_frame_mark();
 		{
 			TextData<FrameAlloc> textData(desc.text, desc.font, desc.fontSize, desc.width, desc.height, desc.wordWrap, desc.wordBreak);
@@ -24,30 +27,23 @@ namespace BansheeEngine
 			UINT32 numLines = textData.getNumLines();
 			UINT32 numPages = textData.getNumPages();
 
-			// Resize cached mesh array to needed size
-			if (mCachedRenderElements.size() > numPages)
+			// Free all previous memory
+			for (auto& cachedElem : mCachedRenderElements)
 			{
-				for (UINT32 i = numPages; i < (UINT32)mCachedRenderElements.size(); i++)
-				{
-					auto& renderElem = mCachedRenderElements[i];
-
-					UINT32 vertexCount = renderElem.numQuads * 4;
-					UINT32 indexCount = renderElem.numQuads * 6;
-
-					if (renderElem.vertices != nullptr)
-						bs_deleteN<ScratchAlloc>(renderElem.vertices, vertexCount);
+				if (cachedElem.vertices != nullptr) mAlloc.free(cachedElem.vertices);
+				if (cachedElem.uvs != nullptr) mAlloc.free(cachedElem.uvs);
+				if (cachedElem.indexes != nullptr) mAlloc.free(cachedElem.indexes);
+			}
 
-					if (renderElem.uvs != nullptr)
-						bs_deleteN<ScratchAlloc>(renderElem.uvs, vertexCount);
+			mAlloc.clear();
 
-					if (renderElem.indexes != nullptr)
-						bs_deleteN<ScratchAlloc>(renderElem.indexes, indexCount);
+			// Resize cached mesh array to needed size
+			for (UINT32 i = numPages; i < (UINT32)mCachedRenderElements.size(); i++)
+			{
+				auto& renderElem = mCachedRenderElements[i];
 
-					if (renderElem.matInfo.material != nullptr)
-					{
-						GUIMaterialManager::instance().releaseMaterial(renderElem.matInfo);
-					}
-				}
+				if (renderElem.matInfo.material != nullptr)
+					GUIMaterialManager::instance().releaseMaterial(renderElem.matInfo);
 			}
 
 			if (mCachedRenderElements.size() != numPages)
@@ -58,20 +54,11 @@ namespace BansheeEngine
 			for (auto& cachedElem : mCachedRenderElements)
 			{
 				UINT32 newNumQuads = textData.getNumQuadsForPage(texPage);
-				if (newNumQuads != cachedElem.numQuads)
-				{
-					UINT32 oldVertexCount = cachedElem.numQuads * 4;
-					UINT32 oldIndexCount = cachedElem.numQuads * 6;
-
-					if (cachedElem.vertices != nullptr) bs_deleteN<ScratchAlloc>(cachedElem.vertices, oldVertexCount);
-					if (cachedElem.uvs != nullptr) bs_deleteN<ScratchAlloc>(cachedElem.uvs, oldVertexCount);
-					if (cachedElem.indexes != nullptr) bs_deleteN<ScratchAlloc>(cachedElem.indexes, oldIndexCount);
 
-					cachedElem.vertices = bs_newN<Vector2, ScratchAlloc>(newNumQuads * 4);
-					cachedElem.uvs = bs_newN<Vector2, ScratchAlloc>(newNumQuads * 4);
-					cachedElem.indexes = bs_newN<UINT32, ScratchAlloc>(newNumQuads * 6);
-					cachedElem.numQuads = newNumQuads;
-				}
+				cachedElem.vertices = (Vector2*)mAlloc.alloc(sizeof(Vector2) * newNumQuads * 4);
+				cachedElem.uvs = (Vector2*)mAlloc.alloc(sizeof(Vector2) * newNumQuads * 4);
+				cachedElem.indexes = (UINT32*)mAlloc.alloc(sizeof(UINT32) * newNumQuads * 6);
+				cachedElem.numQuads = newNumQuads;
 
 				const HTexture& tex = textData.getTextureForPage(texPage);
 
@@ -114,8 +101,6 @@ namespace BansheeEngine
 		bs_frame_clear();
 
 		updateBounds();
-
-		gProfilerCPU().endSample("UpdateTextSprite");
 	}
 
 	UINT32 TextSprite::genTextQuads(UINT32 page, const TextDataBase& textData, UINT32 width, UINT32 height,
@@ -124,7 +109,8 @@ namespace BansheeEngine
 		UINT32 numLines = textData.getNumLines();
 		UINT32 newNumQuads = textData.getNumQuadsForPage(page);
 
-		Vector<Vector2I> alignmentOffsets = getAlignmentOffsets(textData, width, height, horzAlign, vertAlign);
+		Vector2I* alignmentOffsets = bs_stack_new<Vector2I>(numLines);
+		getAlignmentOffsets(textData, width, height, horzAlign, vertAlign, alignmentOffsets);
 		Vector2I offset = getAnchorOffset(anchor, width, height);
 
 		UINT32 quadOffset = 0;
@@ -144,6 +130,7 @@ namespace BansheeEngine
 			quadOffset += writtenQuads;
 		}
 
+		bs_stack_delete(alignmentOffsets, numLines);
 		return newNumQuads;
 	}
 
@@ -154,7 +141,8 @@ namespace BansheeEngine
 		UINT32 numLines = textData.getNumLines();
 		UINT32 numPages = textData.getNumPages();
 
-		Vector<Vector2I> alignmentOffsets = getAlignmentOffsets(textData, width, height, horzAlign, vertAlign);
+		Vector2I* alignmentOffsets = bs_stack_new<Vector2I>(numLines);
+		getAlignmentOffsets(textData, width, height, horzAlign, vertAlign, alignmentOffsets);
 		Vector2I offset = getAnchorOffset(anchor, width, height);
 
 		UINT32 quadOffset = 0;
@@ -169,21 +157,22 @@ namespace BansheeEngine
 				Vector2I position = offset + alignmentOffsets[i];
 
 				UINT32 numVertices = writtenQuads * 4;
-				for(UINT32 i = 0; i < numVertices; i++)
+				for(UINT32 k = 0; k < numVertices; k++)
 				{
-					vertices[quadOffset * 4 + i].x += (float)position.x;
-					vertices[quadOffset * 4 + i].y += (float)position.y;
+					vertices[quadOffset * 4 + k].x += (float)position.x;
+					vertices[quadOffset * 4 + k].y += (float)position.y;
 				}
 
 				quadOffset += writtenQuads;
 			}
 		}
 
+		bs_stack_delete(alignmentOffsets, numLines);
 		return quadOffset;
 	}
 
-	Vector<Vector2I> TextSprite::getAlignmentOffsets(const TextDataBase& textData,
-		UINT32 width, UINT32 height, TextHorzAlign horzAlign, TextVertAlign vertAlign)
+	void TextSprite::getAlignmentOffsets(const TextDataBase& textData,
+		UINT32 width, UINT32 height, TextHorzAlign horzAlign, TextVertAlign vertAlign, Vector2I* output)
 	{
 		UINT32 numLines = textData.getNumLines();
 		UINT32 curHeight = 0;
@@ -211,7 +200,6 @@ namespace BansheeEngine
 
 		// Calc horizontal alignment offset
 		UINT32 curY = 0;
-		Vector<Vector2I> lineOffsets;
 		for(UINT32 i = 0; i < numLines; i++)
 		{
 			const TextDataBase::TextLine& line = textData.getLine(i);
@@ -230,10 +218,42 @@ namespace BansheeEngine
 				break;
 			}
 
-			lineOffsets.push_back(Vector2I(horzOffset, vertOffset + curY));
+			output[i] = Vector2I(horzOffset, vertOffset + curY);
 			curY += line.getYOffset();
 		}
+	}
+
+	void TextSprite::clearMesh()
+	{
+		for (auto& renderElem : mCachedRenderElements)
+		{
+			if (renderElem.vertices != nullptr)
+			{
+				mAlloc.free(renderElem.vertices);
+				renderElem.vertices = nullptr;
+			}
+
+			if (renderElem.uvs != nullptr)
+			{
+				mAlloc.free(renderElem.uvs);
+				renderElem.uvs = nullptr;
+			}
 
-		return lineOffsets;
+			if (renderElem.indexes != nullptr)
+			{
+				mAlloc.free(renderElem.indexes);
+				renderElem.indexes = nullptr;
+			}
+
+			if (renderElem.matInfo.material != nullptr)
+			{
+				GUIMaterialManager::instance().releaseMaterial(renderElem.matInfo);
+			}
+		}
+
+		mCachedRenderElements.clear();
+		mAlloc.clear();
+
+		updateBounds();
 	}
 }

+ 68 - 32
BansheeUtility/Include/BsMemStack.h

@@ -27,7 +27,8 @@ namespace BansheeEngine
 		{
 		public:
 			MemBlock(UINT32 size)
-				:mData(nullptr), mFreePtr(0), mSize(size)
+				:mData(nullptr), mFreePtr(0), mSize(size), 
+				mNextBlock(nullptr), mPrevBlock(nullptr)
 			{ }
 
 			~MemBlock()
@@ -62,22 +63,28 @@ namespace BansheeEngine
 			UINT8* mData;
 			UINT32 mFreePtr;
 			UINT32 mSize;
+			MemBlock* mNextBlock;
+			MemBlock* mPrevBlock;
 		};
 
 	public:
 		MemStackInternal()
-		{ }
+			:mFreeBlock(nullptr)
+		{
+			mFreeBlock = allocBlock(BlockCapacity);
+		}
 
 		~MemStackInternal()
 		{
-			assert(mBlocks.size() == 0 && "Not all blocks were released before shutting down the stack allocator.");
+			assert(mFreeBlock->mFreePtr == 0 && "Not all blocks were released before shutting down the stack allocator.");
 
-			while(!mBlocks.empty())
+			MemBlock* curBlock = mFreeBlock;
+			while (curBlock != nullptr)
 			{
-				MemBlock* curPtr = mBlocks.top();
-				mBlocks.pop();
+				MemBlock* nextBlock = curBlock->mNextBlock;
+				deallocBlock(curBlock);
 
-				deallocBlock(curPtr);
+				curBlock = nextBlock;
 			}
 		}
 
@@ -97,20 +104,11 @@ namespace BansheeEngine
 		{
 			amount += sizeof(UINT32);
 
-			MemBlock* topBlock;
-			if(mBlocks.size() == 0)
-				topBlock = allocBlock(amount);
-			else
-				topBlock = mBlocks.top();
+			UINT32 freeMem = mFreeBlock->mSize - mFreeBlock->mFreePtr;
+			if(amount > freeMem)
+				allocBlock(amount);
 
-			MemBlock* memBlock = nullptr;
-			UINT32 freeMem = topBlock->mSize - topBlock->mFreePtr;
-			if(amount <= freeMem)
-				memBlock = topBlock;
-			else
-				memBlock = allocBlock(amount);
-
-			UINT8* data = memBlock->alloc(amount);
+			UINT8* data = mFreeBlock->alloc(amount);
 
 			UINT32* storedSize = reinterpret_cast<UINT32*>(data);
 			*storedSize = amount;
@@ -127,19 +125,35 @@ namespace BansheeEngine
 			data -= sizeof(UINT32);
 
 			UINT32* storedSize = reinterpret_cast<UINT32*>(data);
+			mFreeBlock->dealloc(data, *storedSize);
 
-			MemBlock* topBlock = mBlocks.top();
-			topBlock->dealloc(data, *storedSize);
-
-			if(topBlock->mFreePtr == 0)
+			if (mFreeBlock->mFreePtr == 0)
 			{
-				deallocBlock(topBlock);
-				mBlocks.pop();
+				MemBlock* emptyBlock = mFreeBlock;
+
+				if (emptyBlock->mPrevBlock != nullptr)
+					mFreeBlock = emptyBlock->mPrevBlock;
+
+				// Merge with next block
+				if (emptyBlock->mNextBlock != nullptr)
+				{
+					UINT32 totalSize = emptyBlock->mSize + emptyBlock->mNextBlock->mSize;
+
+					if (emptyBlock->mPrevBlock != nullptr)
+						emptyBlock->mPrevBlock->mNextBlock = nullptr;
+					else
+						mFreeBlock = nullptr;
+
+					deallocBlock(emptyBlock->mNextBlock);
+					deallocBlock(emptyBlock);
+
+					allocBlock(totalSize);
+				}
 			}
 		}
 
 	private:
-		std::stack<MemBlock*> mBlocks;
+		MemBlock* mFreeBlock;
 
 		/**
 		 * @brief	Allocates a new block of memory using a heap allocator. Block will never be 
@@ -151,13 +165,35 @@ namespace BansheeEngine
 			if(wantedSize > blockSize)
 				blockSize = wantedSize;
 
-			UINT8* data = (UINT8*)reinterpret_cast<UINT8*>(bs_alloc(blockSize + sizeof(MemBlock)));
-			MemBlock* newBlock = new (data) MemBlock(blockSize);
-			data += sizeof(MemBlock);
-			newBlock->mData = data;
+			MemBlock* newBlock = nullptr;
+			MemBlock* curBlock = mFreeBlock;
+
+			while (curBlock != nullptr)
+			{
+				MemBlock* nextBlock = curBlock->mNextBlock;
+				if (nextBlock->mSize >= blockSize)
+				{
+					newBlock = nextBlock;
+					break;
+				}
+
+				curBlock = nextBlock;
+			}
+
+			if (newBlock == nullptr)
+			{
+				UINT8* data = (UINT8*)reinterpret_cast<UINT8*>(bs_alloc(blockSize + sizeof(MemBlock)));
+				newBlock = new (data)MemBlock(blockSize);
+				data += sizeof(MemBlock);
+
+				newBlock->mData = data;
+				newBlock->mPrevBlock = mFreeBlock;
 
-			mBlocks.push(newBlock);
+				if (mFreeBlock != nullptr)
+					mFreeBlock->mNextBlock = newBlock;
+			}
 
+			mFreeBlock = newBlock;
 			return newBlock;
 		}
 

+ 13 - 13
BansheeUtility/Include/BsStaticAlloc.h

@@ -1,7 +1,5 @@
 #pragma once
 
-#include "BsPrerequisitesUtil.h"
-
 namespace BansheeEngine
 {
 	/**
@@ -18,7 +16,7 @@ namespace BansheeEngine
 	 *									if you stay within the bounds of the statically allocated memory.
 	 */
 	template<int BlockSize = 512, int MaxDynamicMemory = 512>
-	class BS_UTILITY_EXPORT StaticAlloc
+	class StaticAlloc
 	{
 	private:
 		/**
@@ -71,7 +69,7 @@ namespace BansheeEngine
 		{
 			assert(mFreeBlock == &mStaticBlock && mStaticBlock.mFreePtr == 0);
 
-			freeEmptyDynamicBlocks(mFreeBlock);
+			freeBlocks(mFreeBlock);
 		}
 
 		/**
@@ -106,14 +104,16 @@ namespace BansheeEngine
 		/**
 		 * @brief	Deallocates a previously allocated piece of memory.
 		 */
-		void dealloc(UINT8* data)
+		void free(void* data)
 		{
 			// Dealloc is only used for debug and can be removed if needed. All the actual deallocation
 			// happens in ::clear
 
 #if BS_DEBUG_MODE
-			data -= sizeof(UINT32);
-			UINT32* storedSize = reinterpret_cast<UINT32*>(data);
+			UINT8* dataPtr = (UINT8*)data;
+			dataPtr -= sizeof(UINT32);
+
+			UINT32* storedSize = (UINT32*)(dataPtr);
 			mTotalAllocBytes -= *storedSize;
 #endif
 		}
@@ -121,9 +121,8 @@ namespace BansheeEngine
 		void clear()
 		{
 			assert(mTotalAllocBytes == 0);
-			assert(mFreeBlock == &mStaticBlock);
 
-			MemBlock* dynamicBlock = mFreeBlock->mNextBlock;
+			MemBlock* dynamicBlock = mStaticBlock.mNextBlock;
 			INT32 totalDynamicMemAmount = 0;
 			UINT32 numDynamicBlocks = 0;
 
@@ -136,18 +135,19 @@ namespace BansheeEngine
 				numDynamicBlocks++;
 			}
 
+			mFreeBlock = &mStaticBlock;
+			mStaticBlock.clear();
+
 			if (numDynamicBlocks > 1)
 			{
-				freeBlocks(mFreeBlock);
+				freeBlocks(&mStaticBlock);
 				allocBlock(std::min(totalDynamicMemAmount, MaxDynamicMemory));
 				mFreeBlock = &mStaticBlock;
 			}
 			else if (numDynamicBlocks == 1 && MaxDynamicMemory == 0)
 			{
-				freeBlocks(mFreeBlock);
+				freeBlocks(&mStaticBlock);
 			}
-
-			mFreeBlock->clear();
 		}
 
 	private:

+ 12 - 9
BansheeUtility/Source/BsFrameAlloc.cpp

@@ -161,18 +161,21 @@ namespace BansheeEngine
 				BS_EXCEPT(InvalidStateException, "Not all frame allocated bytes were properly released.");
 #endif
 
-			// Merge all blocks into one
-			UINT32 totalBytes = 0;
-			for (auto& block : mBlocks)
+			if (mBlocks.size() > 1)
 			{
-				totalBytes += block->mSize;
-				deallocBlock(block);
-			}
+				// Merge all blocks into one
+				UINT32 totalBytes = 0;
+				for (auto& block : mBlocks)
+				{
+					totalBytes += block->mSize;
+					deallocBlock(block);
+				}
 
-			mBlocks.clear();
-			mNextBlockIdx = 0;
+				mBlocks.clear();
+				mNextBlockIdx = 0;
 
-			allocBlock(totalBytes);
+				allocBlock(totalBytes);
+			}
 		}
 	}