Explorar o código

Made TextData thread safe

Marko Pintera %!s(int64=12) %!d(string=hai) anos
pai
achega
12a7eb5e8e

+ 4 - 4
BansheeEngine/Include/BsTextSprite.h

@@ -2,7 +2,7 @@
 
 #include "BsPrerequisites.h"
 #include "BsSprite.h"
-#include "CmTextUtility.h"
+#include "CmTextData.h"
 
 namespace BansheeEngine
 {
@@ -42,7 +42,7 @@ namespace BansheeEngine
 
 		void update(const TEXT_SPRITE_DESC& desc);
 
-		static CM::Vector<CM::Int2>::type getAlignmentOffsets(const CM::TextUtility::TextData& textData, 
+		static CM::Vector<CM::Int2>::type getAlignmentOffsets(const CM::TextData& textData, 
 			CM::UINT32 width, CM::UINT32 height, TextHorzAlign horzAlign, TextVertAlign vertAlign);
 
 		/**
@@ -56,7 +56,7 @@ namespace BansheeEngine
 		 *
 		 * @return	Number of generated quads.
 		 */
-		static CM::UINT32 genTextQuads(CM::UINT32 page, const CM::TextUtility::TextData& textData, CM::UINT32 width, CM::UINT32 height, 
+		static CM::UINT32 genTextQuads(CM::UINT32 page, const CM::TextData& textData, CM::UINT32 width, CM::UINT32 height, 
 			TextHorzAlign horzAlign, TextVertAlign vertAlign, SpriteAnchor anchor, CM::Vector2* vertices, CM::Vector2* uv, CM::UINT32* indices, 
 			CM::UINT32 bufferSizeQuads);
 
@@ -70,7 +70,7 @@ namespace BansheeEngine
 		 *
 		 * @return	Number of generated quads.
 		 */
-		static CM::UINT32 genTextQuads(const CM::TextUtility::TextData& textData, CM::UINT32 width, CM::UINT32 height, 
+		static CM::UINT32 genTextQuads(const CM::TextData& textData, CM::UINT32 width, CM::UINT32 height, 
 			TextHorzAlign horzAlign, TextVertAlign vertAlign, SpriteAnchor anchor, CM::Vector2* vertices, CM::Vector2* uv, CM::UINT32* indices, 
 			CM::UINT32 bufferSizeQuads);
 	};

+ 3 - 6
BansheeEngine/Source/BsGUIHelper.cpp

@@ -38,13 +38,10 @@ namespace BansheeEngine
 		UINT32 contentWidth = style.margins.left + style.margins.right + style.contentOffset.left + style.contentOffset.right;
 		UINT32 contentHeight = style.margins.top + style.margins.bottom + style.contentOffset.top + style.contentOffset.bottom;
 
-		std::shared_ptr<TextUtility::TextData> textData = TextUtility::getTextData(text, style.font, style.fontSize, wordWrapWidth, 0, style.wordWrap);
+		TextData textData(text, style.font, style.fontSize, wordWrapWidth, 0, style.wordWrap);
 
-		if(textData != nullptr)
-		{
-			contentWidth += textData->getWidth();
-			contentHeight += textData->getHeight();
-		}
+		contentWidth += textData.getWidth();
+		contentHeight += textData.getHeight();
 
 		return Int2(contentWidth, contentHeight);
 	}

+ 1 - 1
BansheeEngine/Source/BsGUIInputBox.cpp

@@ -10,7 +10,7 @@
 #include "BsGUIMouseEvent.h"
 #include "BsGUICommandEvent.h"
 #include "CmFont.h"
-#include "CmTextUtility.h"
+#include "CmTextData.h"
 #include "CmTexture.h"
 #include "CmPlatform.h"
 #include "BsGUIInputCaret.h"

+ 7 - 10
BansheeEngine/Source/BsGUIInputTool.cpp

@@ -22,25 +22,22 @@ namespace BansheeEngine
 
 		mLineDescs.clear();
 
-		std::shared_ptr<TextUtility::TextData> textData = TextUtility::getTextData(mTextDesc.text, mTextDesc.font, mTextDesc.fontSize, 
+		TextData textData(mTextDesc.text, mTextDesc.font, mTextDesc.fontSize, 
 			mTextDesc.width, mTextDesc.height, mTextDesc.wordWrap);
 
-		if(textData == nullptr)
-			return;
-
-		UINT32 numLines = textData->getNumLines();
-		UINT32 numPages = textData->getNumPages();
+		UINT32 numLines = textData.getNumLines();
+		UINT32 numPages = textData.getNumPages();
 
 		mNumQuads = 0;
 		for(UINT32 i = 0; i < numPages; i++)
-			mNumQuads += textData->getNumQuadsForPage(i);
+			mNumQuads += textData.getNumQuadsForPage(i);
 
 		if(mQuads != nullptr)
 			cm_delete<ScratchAlloc>(mQuads);
 
 		mQuads = cm_newN<Vector2, ScratchAlloc>(mNumQuads * 4);
 
-		TextSprite::genTextQuads(*textData, mTextDesc.width, mTextDesc.height, mTextDesc.horzAlign, mTextDesc.vertAlign, mTextDesc.anchor, 
+		TextSprite::genTextQuads(textData, mTextDesc.width, mTextDesc.height, mTextDesc.horzAlign, mTextDesc.vertAlign, mTextDesc.anchor, 
 			mQuads, nullptr, nullptr, mNumQuads);
 
 		UINT32 numVerts = mNumQuads * 4;
@@ -48,12 +45,12 @@ namespace BansheeEngine
 		// Store cached line data
 		UINT32 curCharIdx = 0;
 		UINT32 curLineIdx = 0;
-		Vector<Int2>::type alignmentOffsets = TextSprite::getAlignmentOffsets(*textData, mTextDesc.width, 
+		Vector<Int2>::type alignmentOffsets = TextSprite::getAlignmentOffsets(textData, mTextDesc.width, 
 			mTextDesc.height, mTextDesc.horzAlign, mTextDesc.vertAlign);
 
 		for(UINT32 i = 0; i < numLines; i++)
 		{
-			const TextUtility::TextLine& line = textData->getLine(i);
+			const TextData::TextLine& line = textData.getLine(i);
 
 			// Line has a newline char only if it wasn't created by word wrap and it isn't the last line
 			bool hasNewline = line.hasNewlineChar() && (curLineIdx != (numLines - 1));

+ 1 - 1
BansheeEngine/Source/BsGUILabel.cpp

@@ -5,7 +5,7 @@
 #include "BsGUIWidget.h"
 #include "BsGUILayoutOptions.h"
 #include "BsGUIHelper.h"
-#include "CmTextUtility.h"
+#include "CmTextData.h"
 
 using namespace CamelotFramework;
 

+ 14 - 17
BansheeEngine/Source/BsTextSprite.cpp

@@ -1,6 +1,6 @@
 #include "BsTextSprite.h"
 #include "BsGUIMaterialManager.h"
-#include "CmTextUtility.h"
+#include "CmTextData.h"
 #include "CmFont.h"
 #include "CmVector2.h"
 
@@ -18,16 +18,13 @@ namespace BansheeEngine
 	void TextSprite::update(const TEXT_SPRITE_DESC& desc)
 	{
 		gProfiler().beginSample("textUpdateA");
-		std::shared_ptr<TextUtility::TextData> textData = TextUtility::getTextData(desc.text, desc.font, desc.fontSize, desc.width, desc.height, desc.wordWrap);
+		TextData textData(desc.text, desc.font, desc.fontSize, desc.width, desc.height, desc.wordWrap);
 		gProfiler().endSample("textUpdateA");
 
-		if(textData == nullptr)
-			return;
-
 		gProfiler().beginSample("textUpdateB");
 
-		UINT32 numLines = textData->getNumLines();
-		UINT32 numPages = textData->getNumPages();
+		UINT32 numLines = textData.getNumLines();
+		UINT32 numPages = textData.getNumPages();
 
 		// Resize cached mesh array to needed size
 		if(mCachedRenderElements.size() > numPages)
@@ -65,7 +62,7 @@ namespace BansheeEngine
 		UINT32 texPage = 0;
 		for(auto& cachedElem : mCachedRenderElements)
 		{
-			UINT32 newNumQuads = textData->getNumQuadsForPage(texPage);
+			UINT32 newNumQuads = textData.getNumQuadsForPage(texPage);
 			if(newNumQuads != cachedElem.numQuads)
 			{
 				UINT32 oldVertexCount = cachedElem.numQuads * 4;
@@ -81,7 +78,7 @@ namespace BansheeEngine
 				cachedElem.numQuads = newNumQuads;
 			}
 
-			HMaterial newMaterial = GUIMaterialManager::instance().requestTextMaterial(textData->getTextureForPage(texPage));
+			HMaterial newMaterial = GUIMaterialManager::instance().requestTextMaterial(textData.getTextureForPage(texPage));
 			if(cachedElem.material != nullptr)
 				GUIMaterialManager::instance().releaseMaterial(cachedElem.material);
 
@@ -98,7 +95,7 @@ namespace BansheeEngine
 		{
 			SpriteRenderElement& renderElem = mCachedRenderElements[j];
 
-			genTextQuads(j, *textData, desc.width, desc.height, desc.horzAlign, desc.vertAlign, desc.anchor, 
+			genTextQuads(j, textData, desc.width, desc.height, desc.horzAlign, desc.vertAlign, desc.anchor, 
 				renderElem.vertices, renderElem.uvs, renderElem.indexes, renderElem.numQuads);
 		}
 
@@ -110,7 +107,7 @@ namespace BansheeEngine
 		gProfiler().instance().endSample("textUpdateE");
 	}
 
-	UINT32 TextSprite::genTextQuads(UINT32 page, const TextUtility::TextData& textData, UINT32 width, UINT32 height, 
+	UINT32 TextSprite::genTextQuads(UINT32 page, const TextData& textData, UINT32 width, UINT32 height, 
 		TextHorzAlign horzAlign, TextVertAlign vertAlign, SpriteAnchor anchor, Vector2* vertices, Vector2* uv, UINT32* indices, UINT32 bufferSizeQuads)
 	{
 		UINT32 numLines = textData.getNumLines();
@@ -122,7 +119,7 @@ namespace BansheeEngine
 		UINT32 quadOffset = 0;
 		for(UINT32 i = 0; i < numLines; i++)
 		{
-			const TextUtility::TextLine& line = textData.getLine(i);
+			const TextData::TextLine& line = textData.getLine(i);
 			UINT32 writtenQuads = line.fillBuffer(page, vertices, uv, indices, quadOffset, bufferSizeQuads);
 
 			Int2 position = offset + alignmentOffsets[i];
@@ -140,7 +137,7 @@ namespace BansheeEngine
 	}
 
 
-	UINT32 TextSprite::genTextQuads(const TextUtility::TextData& textData, UINT32 width, UINT32 height, 
+	UINT32 TextSprite::genTextQuads(const TextData& textData, UINT32 width, UINT32 height, 
 		TextHorzAlign horzAlign, TextVertAlign vertAlign, SpriteAnchor anchor, Vector2* vertices, Vector2* uv, UINT32* indices, UINT32 bufferSizeQuads)
 	{
 		UINT32 numLines = textData.getNumLines();
@@ -153,7 +150,7 @@ namespace BansheeEngine
 		
 		for(UINT32 i = 0; i < numLines; i++)
 		{
-			const TextUtility::TextLine& line = textData.getLine(i);
+			const TextData::TextLine& line = textData.getLine(i);
 			for(UINT32 j = 0; j < numPages; j++)
 			{
 				UINT32 writtenQuads = line.fillBuffer(j, vertices, uv, indices, quadOffset, bufferSizeQuads);
@@ -174,14 +171,14 @@ namespace BansheeEngine
 		return quadOffset;
 	}
 
-	Vector<Int2>::type TextSprite::getAlignmentOffsets(const TextUtility::TextData& textData, 
+	Vector<Int2>::type TextSprite::getAlignmentOffsets(const TextData& textData, 
 		UINT32 width, UINT32 height, TextHorzAlign horzAlign, TextVertAlign vertAlign)
 	{
 		UINT32 numLines = textData.getNumLines();
 		UINT32 curHeight = 0;
 		for(UINT32 i = 0; i < numLines; i++)
 		{
-			const TextUtility::TextLine& line = textData.getLine(i);
+			const TextData::TextLine& line = textData.getLine(i);
 			curHeight += line.getYOffset();
 		}
 
@@ -206,7 +203,7 @@ namespace BansheeEngine
 		Vector<Int2>::type lineOffsets;
 		for(UINT32 i = 0; i < numLines; i++)
 		{
-			const TextUtility::TextLine& line = textData.getLine(i);
+			const TextData::TextLine& line = textData.getLine(i);
 
 			UINT32 horzOffset = 0;
 			switch(horzAlign)

+ 2 - 2
CamelotCore/CamelotCore.vcxproj

@@ -339,7 +339,7 @@
     <ClInclude Include="Include\CmPixelBuffer.h" />
     <ClInclude Include="Include\CmGpuProgIncludeImporter.h" />
     <ClInclude Include="Include\CmTextureView.h" />
-    <ClInclude Include="Include\CmTextUtility.h" />
+    <ClInclude Include="Include\CmTextData.h" />
     <ClInclude Include="Include\CmVertexBuffer.h" />
     <ClInclude Include="Include\CmHighLevelGpuProgram.h" />
     <ClInclude Include="Include\CmHighLevelGpuProgramManager.h" />
@@ -452,7 +452,7 @@
     <ClCompile Include="Source\CmRenderer.cpp" />
     <ClCompile Include="Source\CmRenderQueue.cpp" />
     <ClCompile Include="Source\CmTextureView.cpp" />
-    <ClCompile Include="Source\CmTextUtility.cpp" />
+    <ClCompile Include="Source\CmTextData.cpp" />
     <ClCompile Include="Source\CmVertexBuffer.cpp" />
     <ClCompile Include="Source\CmHighLevelGpuProgram.cpp" />
     <ClCompile Include="Source\CmHighLevelGpuProgramManager.cpp" />

+ 6 - 6
CamelotCore/CamelotCore.vcxproj.filters

@@ -420,9 +420,6 @@
     <ClInclude Include="Include\CmGpuResourceDataRTTI.h">
       <Filter>Header Files\RTTI</Filter>
     </ClInclude>
-    <ClInclude Include="Include\CmTextUtility.h">
-      <Filter>Header Files\Text</Filter>
-    </ClInclude>
     <ClInclude Include="Include\CmOSInputHandler.h">
       <Filter>Header Files\Input</Filter>
     </ClInclude>
@@ -483,6 +480,9 @@
     <ClInclude Include="Include\CmDrawOps.h">
       <Filter>Header Files\RenderSystem</Filter>
     </ClInclude>
+    <ClInclude Include="Include\CmTextData.h">
+      <Filter>Header Files\Text</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\CmApplication.cpp">
@@ -701,9 +701,6 @@
     <ClCompile Include="Source\CmPixelData.cpp">
       <Filter>Source Files\Resources</Filter>
     </ClCompile>
-    <ClCompile Include="Source\CmTextUtility.cpp">
-      <Filter>Source Files\Text</Filter>
-    </ClCompile>
     <ClCompile Include="Source\CmOSInputHandler.cpp">
       <Filter>Source Files\Input</Filter>
     </ClCompile>
@@ -749,5 +746,8 @@
     <ClCompile Include="Source\CmPlatform.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="Source\CmTextData.cpp">
+      <Filter>Source Files\Text</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 175 - 0
CamelotCore/Include/CmTextData.h

@@ -0,0 +1,175 @@
+#pragma once
+
+#include "CmPrerequisites.h"
+#include "CmFontDesc.h"
+#include "CmInt2.h"
+
+namespace CamelotFramework
+{
+	class TextData
+	{
+	private:
+		class TextWord
+		{
+		public:
+			void init(bool spacer);
+
+			UINT32 addChar(UINT32 charIdx, const CHAR_DESC& desc);
+			void addSpace(UINT32 spaceWidth);
+
+			UINT32 getWidth() const { return mWidth; }
+			UINT32 getHeight() const { return mHeight; }
+			bool isSpacer() const { return mSpacer; }
+
+			UINT32 getNumChars() const { return mLastChar == nullptr ? 0 : (mCharsEnd - mCharsStart + 1); }
+			UINT32 getCharsStart() const { return mCharsStart; }
+			UINT32 getCharsEnd() const { return mCharsEnd; }
+
+		private:
+			UINT32 mCharsStart, mCharsEnd;
+			UINT32 mWidth;
+			UINT32 mHeight;
+
+			const CHAR_DESC* mLastChar;
+
+			bool mSpacer;
+			UINT32 mSpaceWidth;
+		};
+
+		struct PageInfo
+		{
+			UINT32 numQuads;
+			HTexture texture;
+		};
+
+	public:
+		class CM_EXPORT TextLine
+		{
+		public:
+			UINT32 getWidth() const { return mWidth; }
+			UINT32 getHeight() const { return mHeight; }
+
+			/**
+				* @brief	Returns an offset used to separate two lines.
+				*/
+			UINT32 getYOffset() const { return mTextData->getLineHeight(); }
+
+			/**
+				* @brief	Fills the vertex/uv/index buffers for the specified page, with all the character data
+				* 			needed for rendering.
+				*
+				* @param	page			The page.
+				* @param [out]	vertices	Pre-allocated array where character vertices will be written.
+				* @param [out]	uvs			Pre-allocated array where character uv coordinates will be written.
+				* @param [out]	indexes 	Pre-allocated array where character indices will be written.
+				* @param	offset			Offsets the location at which the method writes to the buffers.
+				* 							Counted as number of quads.
+				* @param	size			Total number of quads that can fit into the specified buffers.
+				*
+				* @return	Number of quads that were written.
+				*/
+			UINT32 fillBuffer(UINT32 page, Vector2* vertices, Vector2* uvs, UINT32* indexes, UINT32 offset, UINT32 size) const;
+
+			/**
+				* @brief	Returns the total number of characters on this line.
+				*/
+			UINT32 getNumChars() const;
+
+			/**
+				* @brief	Query if this line was created explicitly due to a newline character.
+				* 			As opposed to a line that was created because a word couldn't fit on the previous line.
+				*/
+			bool hasNewlineChar() const { return mHasNewline; }
+		private:
+			friend class TextData;
+
+			TextData* mTextData;
+			UINT32 mWordsStart, mWordsEnd;
+
+			UINT32 mWidth;
+			UINT32 mHeight;
+
+			bool mIsEmpty;
+			bool mHasNewline;
+
+			void add(UINT32 charIdx, const CHAR_DESC& charDesc);
+			void addSpace();
+			void addWord(UINT32 wordIdx, const TextWord& word);
+
+			void init(TextData* textData);
+			void finalize(bool hasNewlineChar);
+
+			bool isEmpty() const { return mIsEmpty; }
+			UINT32 removeLastWord();
+
+			void calculateBounds();
+		};
+
+	public:
+		CM_EXPORT TextData(const WString& text, const HFont& font, UINT32 fontSize, UINT32 width = 0, UINT32 height = 0, bool wordWrap = false);
+		CM_EXPORT ~TextData();
+
+		CM_EXPORT UINT32 getNumLines() const { return mNumLines; }
+		CM_EXPORT UINT32 getNumPages() const { return mNumPageInfos; }
+
+		CM_EXPORT const TextLine& getLine(UINT32 idx) const { return mLines[idx]; }
+		CM_EXPORT const HTexture& getTextureForPage(UINT32 page) const { return mPageInfos[page].texture; }
+		CM_EXPORT UINT32 getNumQuadsForPage(UINT32 page) const { return mPageInfos[page].numQuads; }
+
+		CM_EXPORT UINT32 getWidth() const;
+		CM_EXPORT UINT32 getHeight() const;
+
+	private:
+		friend class TextLine;
+
+		INT32 getBaselineOffset() const { return mBaselineOffset; }
+		UINT32 getLineHeight() const { return mLineHeight; }
+		UINT32 getSpaceWidth() const { return mSpaceWidth; }
+
+		const CHAR_DESC& getChar(UINT32 idx) const { return *mChars[idx]; }
+		const TextWord& getWord(UINT32 idx) const { return mWords[idx]; }
+
+	private:
+		const CHAR_DESC** mChars;
+		UINT32 mNumChars;
+
+		TextWord* mWords;
+		UINT32 mNumWords;
+
+		TextLine* mLines;
+		UINT32 mNumLines;
+
+		PageInfo* mPageInfos;
+		UINT32 mNumPageInfos;
+
+		void* mData;
+
+		HFont mFont;
+		INT32 mBaselineOffset;
+		UINT32 mLineHeight;
+		UINT32 mSpaceWidth;
+
+		// Static buffers
+	private:
+		static CM_THREADLOCAL bool BuffersInitialized;
+
+		static CM_THREADLOCAL TextWord* WordBuffer;
+		static CM_THREADLOCAL UINT32 WordBufferSize;
+		static CM_THREADLOCAL UINT32 NextFreeWord;
+
+		static CM_THREADLOCAL TextLine* LineBuffer;
+		static CM_THREADLOCAL UINT32 LineBufferSize;
+		static CM_THREADLOCAL UINT32 NextFreeLine;
+
+		static CM_THREADLOCAL PageInfo* PageBuffer;
+		static CM_THREADLOCAL UINT32 PageBufferSize;
+		static CM_THREADLOCAL UINT32 NextFreePageInfo;
+
+		static void initAlloc();
+		static UINT32 allocWord(bool spacer);
+		static UINT32 allocLine(TextData* textData);
+		static void deallocAll();
+
+		static void addCharToPage(UINT32 page, const FontData& fontData);
+	};
+}

+ 0 - 180
CamelotCore/Include/CmTextUtility.h

@@ -1,180 +0,0 @@
-#pragma once
-
-#include "CmPrerequisites.h"
-#include "CmFontDesc.h"
-#include "CmInt2.h"
-
-namespace CamelotFramework
-{
-	class CM_EXPORT TextUtility
-	{
-	private:
-		class TextWord
-		{
-		public:
-			void init(bool spacer);
-
-			UINT32 addChar(UINT32 charIdx, const CHAR_DESC& desc);
-			void addSpace(UINT32 spaceWidth);
-
-			UINT32 getWidth() const { return mWidth; }
-			UINT32 getHeight() const { return mHeight; }
-			bool isSpacer() const { return mSpacer; }
-
-			UINT32 getNumChars() const { return mLastChar == nullptr ? 0 : (mCharsEnd - mCharsStart + 1); }
-			UINT32 getCharsStart() const { return mCharsStart; }
-			UINT32 getCharsEnd() const { return mCharsEnd; }
-
-		private:
-			UINT32 mCharsStart, mCharsEnd;
-			UINT32 mWidth;
-			UINT32 mHeight;
-
-			const CHAR_DESC* mLastChar;
-
-			bool mSpacer;
-			UINT32 mSpaceWidth;
-		};
-		
-		struct PageInfo
-		{
-			UINT32 numQuads;
-			HTexture texture;
-		};
-	public:
-		class TextData;
-
-		class CM_EXPORT TextLine
-		{
-		public:
-			UINT32 getWidth() const { return mWidth; }
-			UINT32 getHeight() const { return mHeight; }
-
-			/**
-			 * @brief	Returns an offset used to separate two lines.
-			 */
-			UINT32 getYOffset() const { return mTextData->getLineHeight(); }
-
-			/**
-			 * @brief	Fills the vertex/uv/index buffers for the specified page, with all the character data
-			 * 			needed for rendering.
-			 *
-			 * @param	page			The page.
-			 * @param [out]	vertices	Pre-allocated array where character vertices will be written.
-			 * @param [out]	uvs			Pre-allocated array where character uv coordinates will be written.
-			 * @param [out]	indexes 	Pre-allocated array where character indices will be written.
-			 * @param	offset			Offsets the location at which the method writes to the buffers.
-			 * 							Counted as number of quads.
-			 * @param	size			Total number of quads that can fit into the specified buffers.
-			 *
-			 * @return	Number of quads that were written.
-			 */
-			UINT32 fillBuffer(UINT32 page, Vector2* vertices, Vector2* uvs, UINT32* indexes, UINT32 offset, UINT32 size) const;
-
-			/**
-			 * @brief	Returns the total number of characters on this line.
-			 */
-			UINT32 getNumChars() const;
-
-			/**
-			 * @brief	Query if this line was created explicitly due to a newline character.
-			 * 			As opposed to a line that was created because a word couldn't fit on the previous line.
-			 */
-			bool hasNewlineChar() const { return mHasNewline; }
-		private:
-			friend class TextUtility;
-
-			TextData* mTextData;
-			UINT32 mWordsStart, mWordsEnd;
-
-			UINT32 mWidth;
-			UINT32 mHeight;
-
-			bool mIsEmpty;
-			bool mHasNewline;
-
-			void add(UINT32 charIdx, const CHAR_DESC& charDesc);
-			void addSpace();
-			void addWord(UINT32 wordIdx, const TextWord& word);
-
-			void init(TextData* textData);
-			void finalize(bool hasNewlineChar);
-
-			bool isEmpty() const { return mIsEmpty; }
-			UINT32 removeLastWord();
-
-			void calculateBounds();
-		};
-
-		class CM_EXPORT TextData
-		{
-		public:
-			TextData(const HFont& font, INT32 baselineOffset, UINT32 lineHeight, UINT32 spaceWidth);
-			~TextData();
-
-			UINT32 getNumLines() const { return mNumLines; }
-			UINT32 getNumPages() const { return mNumPageInfos; }
-
-			const TextLine& getLine(UINT32 idx) const { return mLines[idx]; }
-			const HTexture& getTextureForPage(UINT32 page) const { return mPageInfos[page].texture; }
-			UINT32 getNumQuadsForPage(UINT32 page) const { return mPageInfos[page].numQuads; }
-
-			UINT32 getWidth() const;
-			UINT32 getHeight() const;
-
-		private:
-			friend class TextUtility;
-			friend class TextLine;
-
-			INT32 getBaselineOffset() const { return mBaselineOffset; }
-			UINT32 getLineHeight() const { return mLineHeight; }
-			UINT32 getSpaceWidth() const { return mSpaceWidth; }
-
-			const CHAR_DESC& getChar(UINT32 idx) const { return *mChars[idx]; }
-			const TextWord& getWord(UINT32 idx) const { return mWords[idx]; }
-
-		private:
-			const CHAR_DESC** mChars;
-			UINT32 mNumChars;
-
-			TextWord* mWords;
-			UINT32 mNumWords;
-
-			TextLine* mLines;
-			UINT32 mNumLines;
-
-			PageInfo* mPageInfos;
-			UINT32 mNumPageInfos;
-
-			void* mData;
-
-			HFont mFont;
-			INT32 mBaselineOffset;
-			UINT32 mLineHeight;
-			UINT32 mSpaceWidth;
-		};
-
-		static std::shared_ptr<TextUtility::TextData> getTextData(const WString& text, const HFont& font, UINT32 fontSize, UINT32 width = 0, UINT32 height = 0, bool wordWrap = false);
-
-	private:
-		friend class TextLine;
-		friend class TextWord;
-
-		static Vector<TextWord>::type WordBuffer;
-		static UINT32 NextFreeWord;
-
-		static Vector<TextLine>::type LineBuffer;
-		static UINT32 NextFreeLine;
-
-		static Vector<PageInfo>::type PageBuffer;
-		static UINT32 NextFreePageInfo;
-
-		static UINT32 allocWord(bool spacer);
-		static UINT32 allocLine(TextData* textData);
-		static void deallocAll();
-
-		static void addCharToPage(UINT32 page, const FontData& fontData);
-
-
-	};
-}

+ 142 - 97
CamelotCore/Source/CmTextUtility.cpp → CamelotCore/Source/CmTextData.cpp

@@ -1,4 +1,4 @@
-#include "CmTextUtility.h"
+#include "CmTextData.h"
 #include "CmFont.h"
 #include "CmVector2.h"
 #include "CmDebug.h"
@@ -7,7 +7,7 @@ namespace CamelotFramework
 {
 	const int SPACE_CHAR = 32;
 
-	void TextUtility::TextWord::init(bool spacer)
+	void TextData::TextWord::init(bool spacer)
 	{
 		mWidth = mHeight = 0;
 		mSpacer = spacer;
@@ -18,7 +18,7 @@ namespace CamelotFramework
 	}
 
 	// Assumes charIdx is an index right after last char in the list (if any). All chars need to be sequential.
-	UINT32 TextUtility::TextWord::addChar(UINT32 charIdx, const CHAR_DESC& desc)
+	UINT32 TextData::TextWord::addChar(UINT32 charIdx, const CHAR_DESC& desc)
 	{
 		UINT32 charWidth = desc.xAdvance;
 		if(mLastChar != nullptr)
@@ -49,14 +49,14 @@ namespace CamelotFramework
 		return charWidth;
 	}
 
-	void TextUtility::TextWord::addSpace(UINT32 spaceWidth)
+	void TextData::TextWord::addSpace(UINT32 spaceWidth)
 	{
 		mSpaceWidth += spaceWidth;
 		mWidth = mSpaceWidth;
 		mHeight = 0;
 	}
 
-	void TextUtility::TextLine::init(TextData* textData)
+	void TextData::TextLine::init(TextData* textData)
 	{
 		mWidth = 0;
 		mHeight = 0;
@@ -65,12 +65,12 @@ namespace CamelotFramework
 		mWordsStart = mWordsEnd = 0;
 	}
 
-	void TextUtility::TextLine::finalize(bool hasNewlineChar)
+	void TextData::TextLine::finalize(bool hasNewlineChar)
 	{
 		mHasNewline = hasNewlineChar;
 	}
 
-	void TextUtility::TextLine::add(UINT32 charIdx, const CHAR_DESC& charDesc)
+	void TextData::TextLine::add(UINT32 charIdx, const CHAR_DESC& charDesc)
 	{
 		UINT32 charWidth = 0;
 		if(mIsEmpty)
@@ -80,18 +80,18 @@ namespace CamelotFramework
 		}
 		else
 		{
-			if(TextUtility::WordBuffer[mWordsEnd].isSpacer())
+			if(TextData::WordBuffer[mWordsEnd].isSpacer())
 				mWordsEnd = allocWord(false);
 		}
 
-		TextWord& lastWord = TextUtility::WordBuffer[mWordsEnd];
+		TextWord& lastWord = TextData::WordBuffer[mWordsEnd];
 		charWidth = lastWord.addChar(charIdx, charDesc);
 
 		mWidth += charWidth;
 		mHeight = std::max(mHeight, lastWord.getHeight());
 	}
 
-	void TextUtility::TextLine::addSpace()
+	void TextData::TextLine::addSpace()
 	{
 		if(mIsEmpty)
 		{
@@ -101,14 +101,14 @@ namespace CamelotFramework
 		else
 			mWordsEnd = allocWord(true); // Each space is counted as its own word, to make certain operations easier
 
-		TextWord& lastWord = TextUtility::WordBuffer[mWordsEnd];
+		TextWord& lastWord = TextData::WordBuffer[mWordsEnd];
 		lastWord.addSpace(mTextData->getSpaceWidth());
 
 		mWidth += mTextData->getSpaceWidth();
 	}
 
 	// Assumes wordIdx is an index right after last word in the list (if any). All words need to be sequential.
-	void TextUtility::TextLine::addWord(UINT32 wordIdx, const TextWord& word)
+	void TextData::TextLine::addWord(UINT32 wordIdx, const TextWord& word)
 	{
 		if(mIsEmpty)
 		{
@@ -122,7 +122,7 @@ namespace CamelotFramework
 		mHeight = std::max(mHeight, word.getHeight());
 	}
 
-	UINT32 TextUtility::TextLine::removeLastWord()
+	UINT32 TextData::TextLine::removeLastWord()
 	{
 		if(mIsEmpty)
 		{
@@ -142,7 +142,7 @@ namespace CamelotFramework
 		return lastWord;
 	}
 
-	UINT32 TextUtility::TextLine::fillBuffer(UINT32 page, Vector2* vertices, Vector2* uvs, UINT32* indexes, UINT32 offset, UINT32 size) const
+	UINT32 TextData::TextLine::fillBuffer(UINT32 page, Vector2* vertices, Vector2* uvs, UINT32* indexes, UINT32 offset, UINT32 size) const
 	{
 		UINT32 numQuads = 0;
 
@@ -267,7 +267,7 @@ namespace CamelotFramework
 		return numQuads;
 	}
 
-	UINT32 TextUtility::TextLine::getNumChars() const
+	UINT32 TextData::TextLine::getNumChars() const
 	{
 		if(mIsEmpty)
 			return 0;
@@ -275,7 +275,7 @@ namespace CamelotFramework
 		UINT32 numChars = 0;
 		for(UINT32 i = mWordsStart; i <= mWordsEnd; i++)
 		{
-			TextWord& word = TextUtility::WordBuffer[i];
+			TextWord& word = TextData::WordBuffer[i];
 
 			if(word.isSpacer())
 				numChars++;
@@ -286,7 +286,7 @@ namespace CamelotFramework
 		return numChars;
 	}
 
-	void TextUtility::TextLine::calculateBounds()
+	void TextData::TextLine::calculateBounds()
 	{
 		mWidth = 0;
 		mHeight = 0;
@@ -296,65 +296,20 @@ namespace CamelotFramework
 
 		for(UINT32 i = mWordsStart; i <= mWordsEnd; i++)
 		{
-			TextWord& word = TextUtility::WordBuffer[i];
+			TextWord& word = TextData::WordBuffer[i];
 
 			mWidth += word.getWidth();
 			mHeight = std::max(mHeight, word.getHeight());
 		}
 	}
 
-	TextUtility::TextData::TextData(const HFont& font, INT32 baselineOffset, UINT32 lineHeight, UINT32 spaceWidth)
-		:mFont(font), mBaselineOffset(baselineOffset), mLineHeight(lineHeight), mSpaceWidth(spaceWidth), mChars(nullptr),
+	TextData::TextData(const WString& text, const HFont& font, UINT32 fontSize, UINT32 width, UINT32 height, bool wordWrap)
+		:mFont(font), mBaselineOffset(0), mLineHeight(0), mSpaceWidth(0), mChars(nullptr),
 		mNumChars(0), mWords(nullptr), mNumWords(0), mLines(nullptr), mNumLines(0), mPageInfos(nullptr), mNumPageInfos(0), mData(nullptr)
-	{
-	}
-
-	TextUtility::TextData::~TextData()
-	{
-		if(mData != nullptr)
-			cm_free(mData);
-	}
-
-	Vector<TextUtility::TextWord>::type TextUtility::WordBuffer = Vector<TextUtility::TextWord>::type(2000);
-	UINT32 TextUtility::NextFreeWord = 0;
-
-	Vector<TextUtility::TextLine>::type TextUtility::LineBuffer = Vector<TextUtility::TextLine>::type(500);
-	UINT32 TextUtility::NextFreeLine = 0;
-
-	Vector<TextUtility::PageInfo>::type TextUtility::PageBuffer = Vector<TextUtility::PageInfo>::type(20);
-	UINT32 TextUtility::NextFreePageInfo = 0;
-
-	UINT32 TextUtility::allocWord(bool spacer)
-	{
-		if(NextFreeWord >= WordBuffer.size())
-			WordBuffer.resize(WordBuffer.size() * 2);
-
-		WordBuffer[NextFreeWord].init(spacer);
-
-		return NextFreeWord++;
-	}
-
-	UINT32 TextUtility::allocLine(TextData* textData)
-	{
-		if(NextFreeLine >= LineBuffer.size())
-			LineBuffer.resize(LineBuffer.size() * 2);
-
-		LineBuffer[NextFreeLine].init(textData);
-
-		return NextFreeLine++;
-	}
-
-	void TextUtility::deallocAll()
-	{
-		NextFreeWord = 0;
-		NextFreeLine = 0;
-		NextFreePageInfo = 0;
-	}
-
-	std::shared_ptr<TextUtility::TextData> TextUtility::getTextData(const WString& text, const HFont& font, UINT32 fontSize, UINT32 width, UINT32 height, bool wordWrap)
 	{
 		// In order to reduce number of memory allocations algorithm first calculates data into temporary buffers and then copies the results
-		
+		initAlloc();
+
 		const FontData* fontData = nullptr;
 		if(font != nullptr)
 		{
@@ -363,7 +318,7 @@ namespace CamelotFramework
 		}
 
 		if(fontData == nullptr || fontData->texturePages.size() == 0)
-			return nullptr;
+			return;
 
 		if(fontData->size != fontSize)
 		{
@@ -371,10 +326,12 @@ namespace CamelotFramework
 		}
 
 		bool widthIsLimited = width > 0;
+		mFont = font;
+		mBaselineOffset = fontData->fontDesc.baselineOffset;
+		mLineHeight = fontData->fontDesc.lineHeight;
+		mSpaceWidth = fontData->fontDesc.spaceWidth;
 
-		std::shared_ptr<TextUtility::TextData> textData = cm_shared_ptr<TextData, PoolAlloc>(font, fontData->fontDesc.baselineOffset, fontData->fontDesc.lineHeight, fontData->fontDesc.spaceWidth);
-
-		UINT32 curLineIdx = allocLine(textData.get());
+		UINT32 curLineIdx = allocLine(this);
 		UINT32 curHeight = fontData->fontDesc.lineHeight;
 		UINT32 charIdx = 0;
 
@@ -392,7 +349,7 @@ namespace CamelotFramework
 			{
 				curLine->finalize(true);
 
-				curLineIdx = allocLine(textData.get());
+				curLineIdx = allocLine(this);
 				curLine = &LineBuffer[curLineIdx];
 
 				curHeight += fontData->fontDesc.lineHeight;
@@ -425,7 +382,7 @@ namespace CamelotFramework
 					{
 						curLine->finalize(false);
 
-						curLineIdx = allocLine(textData.get());
+						curLineIdx = allocLine(this);
 						curLine = &LineBuffer[curLineIdx];
 
 						curHeight += fontData->fontDesc.lineHeight;
@@ -441,49 +398,137 @@ namespace CamelotFramework
 		LineBuffer[curLineIdx].finalize(true);
 
 		// Now that we have all the data we need, allocate the permanent buffers and copy the data
-		textData->mNumChars = (UINT32)text.size();
-		textData->mNumWords = NextFreeWord;
-		textData->mNumLines = NextFreeLine;
-		textData->mNumPageInfos = NextFreePageInfo;
+		mNumChars = (UINT32)text.size();
+		mNumWords = NextFreeWord;
+		mNumLines = NextFreeLine;
+		mNumPageInfos = NextFreePageInfo;
 
-		UINT32 charArraySize = textData->mNumChars * sizeof(const CHAR_DESC*);
-		UINT32 wordArraySize = textData->mNumWords * sizeof(TextWord);
-		UINT32 lineArraySize = textData->mNumLines * sizeof(TextLine);
-		UINT32 pageInfoArraySize = textData->mNumPageInfos * sizeof(PageInfo);
+		UINT32 charArraySize = mNumChars * sizeof(const CHAR_DESC*);
+		UINT32 wordArraySize = mNumWords * sizeof(TextWord);
+		UINT32 lineArraySize = mNumLines * sizeof(TextLine);
+		UINT32 pageInfoArraySize = mNumPageInfos * sizeof(PageInfo);
 
 		UINT32 totalBufferSize = charArraySize + wordArraySize + lineArraySize + pageInfoArraySize;
-		textData->mData = cm_alloc(totalBufferSize);
+		mData = cm_alloc(totalBufferSize);
 
-		UINT8* dataPtr = (UINT8*)textData->mData;
-		textData->mChars = (const CHAR_DESC**)dataPtr;
+		UINT8* dataPtr = (UINT8*)mData;
+		mChars = (const CHAR_DESC**)dataPtr;
 
-		for(UINT32 i = 0; i < textData->mNumChars; i++)
+		for(UINT32 i = 0; i < mNumChars; i++)
 		{
 			UINT32 charId = text[i];
 			const CHAR_DESC& charDesc = fontData->getCharDesc(charId);
 
-			textData->mChars[i] = &charDesc;
+			mChars[i] = &charDesc;
 		}
 
 		dataPtr += charArraySize;
-		textData->mWords = (TextWord*)dataPtr;
-		memcpy(textData->mWords, &WordBuffer[0], wordArraySize);
+		mWords = (TextWord*)dataPtr;
+		memcpy(mWords, &WordBuffer[0], wordArraySize);
 
 		dataPtr += wordArraySize;
-		textData->mLines = (TextLine*)dataPtr;
-		memcpy(textData->mLines, &LineBuffer[0], lineArraySize);
+		mLines = (TextLine*)dataPtr;
+		memcpy(mLines, &LineBuffer[0], lineArraySize);
 
 		dataPtr += lineArraySize;
-		textData->mPageInfos = (PageInfo*)dataPtr;
-		memcpy(textData->mPageInfos, &PageBuffer[0], pageInfoArraySize);
+		mPageInfos = (PageInfo*)dataPtr;
+		memcpy(mPageInfos, &PageBuffer[0], pageInfoArraySize);
 
-		TextUtility::deallocAll();
+		TextData::deallocAll();
+	}
 
-		return textData;
+	TextData::~TextData()
+	{
+		if(mData != nullptr)
+			cm_free(mData);
 	}
 
-	void TextUtility::addCharToPage(UINT32 page, const FontData& fontData)
+	bool TextData::BuffersInitialized = false;
+
+	TextData::TextWord* TextData::WordBuffer = nullptr;
+	UINT32 TextData::NextFreeWord = 0;
+	UINT32 TextData::WordBufferSize = 0;
+
+	TextData::TextLine* TextData::LineBuffer = nullptr;
+	UINT32 TextData::NextFreeLine = 0;
+	UINT32 TextData::LineBufferSize = 0;
+
+	TextData::PageInfo* TextData::PageBuffer = nullptr;
+	UINT32 TextData::NextFreePageInfo = 0;
+	UINT32 TextData::PageBufferSize = 0;
+
+	void TextData::initAlloc()
+	{
+		if(!BuffersInitialized)
+		{
+			WordBufferSize = 2000;
+			LineBufferSize = 500;
+			PageBufferSize = 20;
+
+			WordBuffer = cm_newN<TextWord>(WordBufferSize);
+			LineBuffer = cm_newN<TextLine>(LineBufferSize);
+			PageBuffer = cm_newN<PageInfo>(PageBufferSize);
+
+			BuffersInitialized = true;
+		}
+	}
+
+	UINT32 TextData::allocWord(bool spacer)
+	{
+		if(NextFreeWord >= WordBufferSize)
+		{
+			UINT32 newBufferSize = WordBufferSize * 2;
+			TextWord* newBuffer = cm_newN<TextWord>(newBufferSize);
+			memcpy(WordBuffer, newBuffer, WordBufferSize);
+
+			cm_deleteN(WordBuffer, WordBufferSize);
+			WordBuffer = newBuffer;
+			WordBufferSize = newBufferSize;
+		}
+
+		WordBuffer[NextFreeWord].init(spacer);
+
+		return NextFreeWord++;
+	}
+
+	UINT32 TextData::allocLine(TextData* textData)
 	{
+		if(NextFreeLine >= LineBufferSize)
+		{
+			UINT32 newBufferSize = LineBufferSize * 2;
+			TextLine* newBuffer = cm_newN<TextLine>(newBufferSize);
+			memcpy(LineBuffer, newBuffer, LineBufferSize);
+
+			cm_deleteN(LineBuffer, LineBufferSize);
+			LineBuffer = newBuffer;
+			LineBufferSize = newBufferSize;
+		}
+
+		LineBuffer[NextFreeLine].init(textData);
+
+		return NextFreeLine++;
+	}
+
+	void TextData::deallocAll()
+	{
+		NextFreeWord = 0;
+		NextFreeLine = 0;
+		NextFreePageInfo = 0;
+	}
+
+	void TextData::addCharToPage(UINT32 page, const FontData& fontData)
+	{
+		if(NextFreePageInfo >= PageBufferSize)
+		{
+			UINT32 newBufferSize = PageBufferSize * 2;
+			PageInfo* newBuffer = cm_newN<PageInfo>(newBufferSize);
+			memcpy(PageBuffer, newBuffer, PageBufferSize);
+
+			cm_deleteN(PageBuffer, PageBufferSize);
+			PageBuffer = newBuffer;
+			PageBufferSize = newBufferSize;
+		}
+
 		while(page >= NextFreePageInfo)
 		{
 			PageBuffer[NextFreePageInfo].numQuads = 0;
@@ -498,7 +543,7 @@ namespace CamelotFramework
 			PageBuffer[page].texture = fontData.texturePages[page];
 	}
 
-	UINT32 TextUtility::TextData::getWidth() const
+	UINT32 TextData::getWidth() const
 	{
 		UINT32 width = 0;
 
@@ -508,7 +553,7 @@ namespace CamelotFramework
 		return width;
 	}
 
-	UINT32 TextUtility::TextData::getHeight() const
+	UINT32 TextData::getHeight() const
 	{
 		UINT32 height = 0;
 

+ 0 - 1
Notes.txt

@@ -68,7 +68,6 @@ Potential optimizations:
 More detailed thought out system descriptions:
 
  <<<<Memory allocation critical areas>>>>
- - TextUtility a lot of allocs
  - Binding gpu params. It gets copied in DeferredRenderContext
  - GameObjectHandle often allocates its internal data
  - ResourceHandle often allocates its internal data

+ 3 - 3
TextOpts.txt

@@ -1,8 +1,8 @@
 Make sure to also update TextSprite and ImageSprite and anything else in UpdateMesh, then don't forget to find the issue that causes elements to get marked as dirty every single frame
 
-TODO: Fix allocation counter as it doesn't work properly between multiple threads
-TODO: Get rid of TextUtility and make all text generation happen in TextData constructor. Get rid of TextData shared_ptr as it just causes an unnecessary allocation.
-TODO: Ensure that buffers in TextData actually handle multiple threads properly (THREADLOCAL?)
+Profiler does a lot of allocations of its own which can seriously skew the profiling results. Try to eliminate them all or add alloc/free overhead fields
+
+TODO: TextData does an extra mem alloc in this bit of code: PageBuffer[NextFreePageInfo].texture = HTexture();, try to avoid it
 
 Performance stats:
  Pre-opt: 1500 allocs, 600 frees, 0.5ms