IF NOT IMPLEMENTED #6

Open
opened 2025-11-28 14:40:33 +00:00 by shadow · 0 comments

Implement Nicu's patch for text rendering, it adds 10-30 fps in client in certain instances BUT NOTE:

MOVE EMOJI RENDERING after implementing it , to be initialized at the end of the render, otherwise you will not have emojis anymore in client

Subject: [PATCH] optimizing client text rendering
---
Index: src/EterLib/GrpBase.h
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/EterLib/GrpBase.h b/src/EterLib/GrpBase.h
--- a/src/EterLib/GrpBase.h	(revision bb19e9abda71c4545d35a3f9bf8cfedf3ce3c7b7)
+++ b/src/EterLib/GrpBase.h	(revision fc1c64cfd2cace42c872955604f5e9aef67a3ae3)
@@ -134,6 +134,7 @@
 		static DWORD GetAvailableTextureMemory();
 		static const D3DXMATRIX& GetViewMatrix();
 		static const D3DXMATRIX & GetIdentityMatrix();
+		static LPDIRECT3DDEVICE9 GetDevice() { return ms_lpd3dDevice; }
 
 		enum
 		{			
@@ -213,6 +214,7 @@
 		static void SetDefaultIndexBuffer(UINT eDefIB);
 		static bool SetPDTStream(SPDTVertexRaw* pVertices, UINT uVtxCount);
 		static bool SetPDTStream(SPDTVertex* pVertices, UINT uVtxCount);
+		static UINT GetPDTVertexNum() { return PDT_VERTEX_NUM; }
 		
 	protected:
 		static D3DXMATRIX				ms_matIdentity;
Index: src/EterLib/GrpTextInstance.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/EterLib/GrpTextInstance.cpp b/src/EterLib/GrpTextInstance.cpp
--- a/src/EterLib/GrpTextInstance.cpp	(revision bb19e9abda71c4545d35a3f9bf8cfedf3ce3c7b7)
+++ b/src/EterLib/GrpTextInstance.cpp	(revision fc1c64cfd2cace42c872955604f5e9aef67a3ae3)
@@ -9,6 +9,29 @@
 
 extern DWORD GetDefaultCodePage();
 
+// Static memory pools for optimized batched rendering
+std::vector<CGraphicTextInstance::BatchedCharacter> CGraphicTextInstance::s_outlineBatch;
+std::vector<CGraphicTextInstance::BatchedCharacter> CGraphicTextInstance::s_mainBatch;
+std::map<DWORD, std::vector<const CGraphicTextInstance::BatchedCharacter*>> CGraphicTextInstance::s_textureGroups;
+
+// True vertex buffer batching (massive performance boost)
+std::vector<SPDTVertex> CGraphicTextInstance::s_batchVertices;
+std::vector<WORD> CGraphicTextInstance::s_batchIndices;
+
+// Fixed-size texture grouping (eliminates std::map allocations)
+CGraphicTextInstance::TextureGroup CGraphicTextInstance::s_textureGroupsArray[CGraphicTextInstance::MAX_FONT_TEXTURES];
+int CGraphicTextInstance::s_activeTextureGroupCount = 0;
+
+// Safe render state caching (per-frame validation)
+CGraphicTextInstance::FrameRenderStateCache CGraphicTextInstance::s_renderStateCache = {0, NULL, false, false, 0, 0};
+
+void CGraphicTextInstance::InvalidateRenderStateCache()
+{
+	s_renderStateCache.isInitialized = false;
+	s_renderStateCache.frameId = 0;
+	s_renderStateCache.devicePtr = NULL;
+}
+
 const float c_fFontFeather = 0.5f;
 
 CDynamicPool<CGraphicTextInstance>		CGraphicTextInstance::ms_kPool;
@@ -486,6 +509,10 @@
 	CGraphicFontTexture* pFontTexture = pkText->GetFontTexturePointer();
 	if (!pFontTexture)
 		return;
+
+	// USE OPTIMIZED BATCHED RENDERING SYSTEM - PERFORMANCE BOOST!
+	__RenderBatched(pClipRect);
+	return;
 
 	float fStanX = m_v3Position.x;
 	float fStanY = m_v3Position.y + 1.0f;
@@ -532,6 +559,7 @@
 
 	//WORD FillRectIndices[6] = { 0, 2, 1, 2, 3, 1 };
 
+	STATEMANAGER.SaveRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
 	STATEMANAGER.SaveRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
 	STATEMANAGER.SaveRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
 	DWORD dwFogEnable = STATEMANAGER.GetRenderState(D3DRS_FOGENABLE);
@@ -859,6 +887,7 @@
 		}		
 	}
 
+	STATEMANAGER.RestoreRenderState(D3DRS_ALPHABLENDENABLE);
 	STATEMANAGER.RestoreRenderState(D3DRS_SRCBLEND);
 	STATEMANAGER.RestoreRenderState(D3DRS_DESTBLEND);
 
@@ -1148,3 +1177,511 @@
 {
 	Destroy();
 }
+
+// PERFORMANCE OPTIMIZATION: Batched text rendering system
+// Reduces 5 draw calls per character to 2 draw calls total (outline batch + main batch)
+void CGraphicTextInstance::__RenderBatched(RECT* pClipRect)
+{
+	if (m_pCharInfoVector.empty())
+		return;
+
+	CGraphicText* pText = m_roText.GetPointer();
+	if (!pText)
+		return;
+
+	CGraphicFontTexture* pFontTexture = pText->GetFontTexturePointer();
+	if (!pFontTexture)
+		return;
+
+	// Use static buffers to avoid per-frame allocations
+	s_outlineBatch.clear();
+	s_mainBatch.clear();
+	
+	// Pre-reserve space based on character count to avoid reallocations
+	if (m_isOutline)
+		s_outlineBatch.reserve(m_pCharInfoVector.size() * 4); // 4 outline quads per character (matches original)
+	s_mainBatch.reserve(m_pCharInfoVector.size());
+	
+	// Build character batches using static buffers
+	__BuildCharacterBatch(s_outlineBatch, s_mainBatch);
+
+	// PER-FRAME RENDER STATE CACHING - Cache states for current frame only
+	static DWORD s_lastFrameId = 0;
+	static DWORD s_currentFrameId = 0;
+	s_currentFrameId++; // Increment each render call
+	
+	LPDIRECT3DDEVICE9 currentDevice = CGraphicBase::GetDevice();
+	
+	// Cache valid only for current frame and same device
+	bool cacheValid = s_renderStateCache.isInitialized && 
+					  s_renderStateCache.frameId == s_currentFrameId &&
+					  s_renderStateCache.devicePtr == currentDevice;
+	
+	DWORD dwFogEnable, dwLighting;
+	
+	// ALWAYS save render states to maintain save/restore balance (prevents flickering)
+	STATEMANAGER.SaveRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
+	STATEMANAGER.SaveRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
+	STATEMANAGER.SaveRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
+	
+	if (!cacheValid)
+	{
+		// Cache miss: Get current states and set new ones
+		dwFogEnable = STATEMANAGER.GetRenderState(D3DRS_FOGENABLE);
+		dwLighting = STATEMANAGER.GetRenderState(D3DRS_LIGHTING);
+		STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, FALSE);
+		STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE);
+		
+		// Cache the state values for this frame
+		s_renderStateCache.frameId = s_currentFrameId;
+		s_renderStateCache.devicePtr = currentDevice;
+		s_renderStateCache.savedFogEnable = dwFogEnable;
+		s_renderStateCache.savedLighting = dwLighting;
+		s_renderStateCache.textureStatesSet = false; // Reset texture state flag for new frame
+		s_renderStateCache.isInitialized = true;
+	}
+	else
+	{
+		// Cache hit: Use cached values and set states 
+		// PERFORMANCE BOOST: Skip expensive GetRenderState calls (2 DirectX calls saved)
+		dwFogEnable = s_renderStateCache.savedFogEnable;
+		dwLighting = s_renderStateCache.savedLighting;
+		STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, FALSE);
+		STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE);
+	}
+
+	// Cache texture stage states (only set once per frame with device validation)
+	if (!s_renderStateCache.textureStatesSet)
+	{
+		STATEMANAGER.SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);
+		STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
+		STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
+		STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
+		STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
+		STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
+		STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
+		s_renderStateCache.textureStatesSet = true;
+	}
+	// Else: Skip expensive texture stage state calls (cached!)
+
+	// Render outline batch (if outline enabled)
+	if (m_isOutline && !s_outlineBatch.empty())
+	{
+		__RenderCharacterBatch(s_outlineBatch, true);
+	}
+
+	// Render main character batch
+	if (!s_mainBatch.empty())
+	{
+		__RenderCharacterBatch(s_mainBatch, false);
+	}
+
+	// Handle cursor rendering (preserved from original)
+	UINT defCodePage = GetDefaultCodePage();
+	if (m_isCursor)
+	{
+		// Draw Cursor
+		float sx, sy, ex, ey;
+		TDiffuse diffuse;
+
+		int curpos = CIME::GetCurPos();
+		int compend = curpos + CIME::GetCompLen();
+
+		__GetTextPos(curpos, &sx, &sy);
+
+		// If Composition
+		if(curpos<compend)
+		{
+			diffuse = 0x7fffffff;
+			__GetTextPos(compend, &ex, &sy);
+		}
+		else
+		{
+			diffuse = 0xffffffff;
+			ex = sx + 2;
+		}
+
+		// FOR_ARABIC_ALIGN
+		if (defCodePage == CP_ARABIC)
+		{
+			sx += m_v3Position.x - m_textWidth;
+			ex += m_v3Position.x - m_textWidth;
+			sy += m_v3Position.y;			
+			ey = sy + m_textHeight;
+		}
+		else
+		{
+			sx += m_v3Position.x;
+			sy += m_v3Position.y;
+			ex += m_v3Position.x;
+			ey = sy + m_textHeight;
+		}
+
+		TPDTVertex vertices[4];
+		vertices[0].diffuse = diffuse;
+		vertices[1].diffuse = diffuse;
+		vertices[2].diffuse = diffuse;
+		vertices[3].diffuse = diffuse;
+		vertices[0].position = TPosition(sx, sy, 0.0f);
+		vertices[1].position = TPosition(ex, sy, 0.0f);
+		vertices[2].position = TPosition(sx, ey, 0.0f);
+		vertices[3].position = TPosition(ex, ey, 0.0f);
+
+		STATEMANAGER.SetTexture(0, NULL);
+
+		CGraphicBase::SetDefaultIndexBuffer(CGraphicBase::DEFAULT_IB_FILL_RECT);
+		if (CGraphicBase::SetPDTStream(vertices, 4))
+			STATEMANAGER.DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 4, 0, 2);
+
+		int ulbegin = CIME::GetULBegin();
+		int ulend = CIME::GetULEnd();
+
+		if(ulbegin < ulend)
+		{
+			const WORD c_FillRectIndices[6] = { 0, 2, 1, 2, 3, 1 };
+			__GetTextPos(curpos+ulbegin, &sx, &sy);
+			__GetTextPos(curpos+ulend, &ex, &sy);
+
+			sx += m_v3Position.x;
+			sy += m_v3Position.y + m_textHeight;
+			ex += m_v3Position.x;
+			ey = sy + 2;
+
+			vertices[0].diffuse = 0xFFFF0000;
+			vertices[1].diffuse = 0xFFFF0000;
+			vertices[2].diffuse = 0xFFFF0000;
+			vertices[3].diffuse = 0xFFFF0000;
+			vertices[0].position = TPosition(sx, sy, 0.0f);
+			vertices[1].position = TPosition(ex, sy, 0.0f);
+			vertices[2].position = TPosition(sx, ey, 0.0f);
+			vertices[3].position = TPosition(ex, ey, 0.0f);
+
+			STATEMANAGER.DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, 4, 2, c_FillRectIndices, D3DFMT_INDEX16, vertices, sizeof(TPDTVertex));
+		}		
+	}
+
+	// Handle hyperlinks (preserved from original)
+	if (m_hyperlinkVector.size() != 0)
+	{
+		int lx = gs_mx - m_v3Position.x;
+		int ly = gs_my - m_v3Position.y;
+
+		if (GetDefaultCodePage() == CP_ARABIC) {
+			lx = -lx;
+			ly = -ly + m_textHeight;
+		}
+
+		if (lx >= 0 && ly >= 0 && lx < m_textWidth && ly < m_textHeight)
+		{
+			std::vector<SHyperlink>::iterator it = m_hyperlinkVector.begin();
+
+			while (it != m_hyperlinkVector.end())
+			{
+				SHyperlink & link = *it++;
+				if (lx >= link.sx && lx < link.ex)
+				{
+					gs_hyperlinkText = link.text;
+					break;
+				}
+			}
+		}
+	}
+
+	// Restore render states
+	STATEMANAGER.RestoreRenderState(D3DRS_ALPHABLENDENABLE);
+	STATEMANAGER.RestoreRenderState(D3DRS_SRCBLEND);
+	STATEMANAGER.RestoreRenderState(D3DRS_DESTBLEND);
+	STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, dwFogEnable);
+	STATEMANAGER.SetRenderState(D3DRS_LIGHTING, dwLighting);
+}
+
+void CGraphicTextInstance::__BuildCharacterBatch(std::vector<BatchedCharacter>& outlineBatch, std::vector<BatchedCharacter>& mainBatch)
+{
+	CGraphicText* pText = m_roText.GetPointer();
+	CGraphicFontTexture* pFontTexture = pText->GetFontTexturePointer();
+	
+	float fStanX = m_v3Position.x;
+	float fStanY = m_v3Position.y + 1.0f;
+
+	// Apply alignment exactly like original
+	UINT defCodePage = GetDefaultCodePage();
+
+	if (defCodePage == CP_ARABIC)
+	{
+		switch (m_hAlign)
+		{
+			case HORIZONTAL_ALIGN_LEFT:
+				fStanX -= m_textWidth;
+				break;
+
+			case HORIZONTAL_ALIGN_CENTER:
+				fStanX -= float(m_textWidth / 2);
+				break;	
+		}
+	}
+	else
+	{	
+		switch (m_hAlign)
+		{
+			case HORIZONTAL_ALIGN_RIGHT:
+				fStanX -= m_textWidth;
+				break;
+
+			case HORIZONTAL_ALIGN_CENTER:
+				fStanX -= float(m_textWidth / 2);
+				break;	
+		}
+	}
+
+	switch (m_vAlign)
+	{
+		case VERTICAL_ALIGN_BOTTOM:
+			fStanY -= m_textHeight;
+			break;
+
+		case VERTICAL_ALIGN_CENTER:
+			fStanY -= float(m_textHeight) / 2.0f;
+			break;
+	}
+
+	float fCurX = fStanX;
+	float fCurY = fStanY;
+	float fFontMaxHeight = 0.0f;
+
+	const float fFontHalfWeight = 1.0f;
+	
+	// Pre-calculate frequently used values
+	const float fLimitWidth = m_fLimitWidth;
+	const float fPositionX = m_v3Position.x;
+	const bool bIsMultiLine = m_isMultiLine;
+	const bool bIsOutline = m_isOutline;
+	const size_t characterCount = m_pCharInfoVector.size();
+
+	for (size_t i = 0; i < characterCount; ++i)
+	{
+		const CGraphicFontTexture::TCharacterInfomation* pCurCharInfo = m_pCharInfoVector[i];
+		
+		// Cache character properties to reduce member access
+		const float fFontWidth = static_cast<float>(pCurCharInfo->width);
+		const float fFontHeight = static_cast<float>(pCurCharInfo->height);
+		const float fFontAdvance = static_cast<float>(pCurCharInfo->advance);
+		
+		// Update max height (branchless when possible)
+		if (fFontHeight > fFontMaxHeight)
+			fFontMaxHeight = fFontHeight;
+
+		// Optimized width limit check
+		const float fNewX = fCurX + fFontWidth;
+		if (fNewX - fPositionX > fLimitWidth)
+		{
+			if (bIsMultiLine)
+			{
+				fCurX = fStanX;
+				fCurY += fFontMaxHeight;
+			}
+			else
+			{
+				break; // Early exit for single line
+			}
+		}
+
+		const float fFontSx = fCurX - 0.5f;
+		const float fFontSy = fCurY - 0.5f;
+		const float fFontEx = fFontSx + fFontWidth;
+		const float fFontEy = fFontSy + fFontHeight;
+
+		// Cache texture coordinates to avoid repeated member access
+		const float fU1 = pCurCharInfo->left;
+		const float fV1 = pCurCharInfo->top;
+		const float fU2 = pCurCharInfo->right;
+		const float fV2 = pCurCharInfo->bottom;
+		const DWORD dwTextureIndex = pCurCharInfo->index;
+
+		// Create outline characters (4 directions) - matches original crisp quality
+		if (bIsOutline)
+		{
+			BatchedCharacter outlineChar;
+			outlineChar.u1 = fU1;
+			outlineChar.v1 = fV1;
+			outlineChar.u2 = fU2;
+			outlineChar.v2 = fV2;
+			outlineChar.color = m_dwOutLineColor;
+			outlineChar.textureIndex = dwTextureIndex;
+
+			// Left outline (pre-calculated extents)
+			outlineChar.x = fFontSx - fFontHalfWeight;
+			outlineChar.y = fFontSy;
+			outlineChar.ex = outlineChar.x + fFontWidth;
+			outlineChar.ey = outlineChar.y + fFontHeight;
+			outlineBatch.push_back(outlineChar);
+
+			// Right outline
+			outlineChar.x = fFontSx + fFontHalfWeight;
+			outlineChar.y = fFontSy;
+			outlineChar.ex = outlineChar.x + fFontWidth;
+			outlineChar.ey = outlineChar.y + fFontHeight;
+			outlineBatch.push_back(outlineChar);
+
+			// Top outline
+			outlineChar.x = fFontSx;
+			outlineChar.y = fFontSy - fFontHalfWeight;
+			outlineChar.ex = outlineChar.x + fFontWidth;
+			outlineChar.ey = outlineChar.y + fFontHeight;
+			outlineBatch.push_back(outlineChar);
+
+			// Bottom outline
+			outlineChar.x = fFontSx;
+			outlineChar.y = fFontSy + fFontHalfWeight;
+			outlineChar.ex = outlineChar.x + fFontWidth;
+			outlineChar.ey = outlineChar.y + fFontHeight;
+			outlineBatch.push_back(outlineChar);
+		}
+
+		// Create main character using cached values and pre-calculated extents
+		BatchedCharacter mainChar;
+		mainChar.x = fFontSx;
+		mainChar.y = fFontSy;
+		mainChar.ex = fFontEx;
+		mainChar.ey = fFontEy;
+		mainChar.u1 = fU1;
+		mainChar.v1 = fV1;
+		mainChar.u2 = fU2;
+		mainChar.v2 = fV2;
+		mainChar.color = m_dwColorInfoVector[i];
+		mainChar.textureIndex = dwTextureIndex;
+		mainBatch.push_back(mainChar);
+
+		fCurX += fFontAdvance;
+	}
+}
+
+void CGraphicTextInstance::__RenderCharacterBatch(const std::vector<BatchedCharacter>& batch, bool isOutline)
+{
+	if (batch.empty())
+		return;
+
+	CGraphicText* pText = m_roText.GetPointer();
+	CGraphicFontTexture* pFontTexture = pText->GetFontTexturePointer();
+
+	// ALLOCATION-FREE TEXTURE GROUPING using fixed-size arrays
+	s_activeTextureGroupCount = 0;
+	
+	// Group characters by texture using fixed arrays (no allocations!)
+	for (const auto& character : batch)
+	{
+		// Find existing texture group or create new one
+		int groupIndex = -1;
+		for (int i = 0; i < s_activeTextureGroupCount; ++i)
+		{
+			if (s_textureGroupsArray[i].textureIndex == character.textureIndex)
+			{
+				groupIndex = i;
+				break;
+			}
+		}
+		
+		// Create new group if not found
+		if (groupIndex == -1)
+		{
+			if (s_activeTextureGroupCount < MAX_FONT_TEXTURES)
+			{
+				groupIndex = s_activeTextureGroupCount++;
+				s_textureGroupsArray[groupIndex].textureIndex = character.textureIndex;
+				s_textureGroupsArray[groupIndex].count = 0;
+			}
+			else
+			{
+				// Fallback to first group if we exceed max textures (rare case)
+				groupIndex = 0;
+			}
+		}
+		
+		// Add character to group (with bounds check)
+		TextureGroup& group = s_textureGroupsArray[groupIndex];
+		if (group.count < 256) // Max characters per texture group
+		{
+			group.characters[group.count++] = &character;
+		}
+	}
+
+	// TRUE VERTEX BUFFER BATCHING - MASSIVE PERFORMANCE BOOST!
+	// Renders entire texture groups in single draw calls instead of N individual calls
+	for (int groupIdx = 0; groupIdx < s_activeTextureGroupCount; ++groupIdx)
+	{
+		const TextureGroup& group = s_textureGroupsArray[groupIdx];
+		DWORD textureIndex = group.textureIndex;
+
+		// Set texture once per group
+		pFontTexture->SelectTexture(textureIndex);
+		STATEMANAGER.SetTexture(0, pFontTexture->GetD3DTexture());
+
+		// Clear and reserve space in static buffers (avoid per-frame allocations)
+		s_batchVertices.clear();
+		s_batchIndices.clear();
+		s_batchVertices.reserve(group.count * 4); // 4 vertices per quad
+		s_batchIndices.reserve(group.count * 6);  // 6 indices per quad (2 triangles)
+
+		// Build massive vertex buffer containing all characters for this texture
+		WORD vertexIndex = 0;
+		for (int charIdx = 0; charIdx < group.count; ++charIdx)
+		{
+			const BatchedCharacter* pChar = group.characters[charIdx];
+			// OPTIMIZED: Create quad vertices using pre-calculated extents and cached values
+			const float posZ = m_v3Position.z;
+			const DWORD color = pChar->color;
+			const float u1 = pChar->u1, v1 = pChar->v1, u2 = pChar->u2, v2 = pChar->v2;
+			const float x = pChar->x, y = pChar->y, ex = pChar->ex, ey = pChar->ey;
+
+			SPDTVertex vertices[4];
+			// Top-left
+			vertices[0].position = TPosition(x,  y,  posZ);
+			vertices[0].diffuse = color;
+			vertices[0].texCoord = TTextureCoordinate(u1, v1);
+
+			// Bottom-left  
+			vertices[1].position = TPosition(x,  ey, posZ);
+			vertices[1].diffuse = color;
+			vertices[1].texCoord = TTextureCoordinate(u1, v2);
+
+			// Top-right
+			vertices[2].position = TPosition(ex, y,  posZ);
+			vertices[2].diffuse = color;
+			vertices[2].texCoord = TTextureCoordinate(u2, v1);
+
+			// Bottom-right
+			vertices[3].position = TPosition(ex, ey, posZ);
+			vertices[3].diffuse = color;
+			vertices[3].texCoord = TTextureCoordinate(u2, v2);
+
+			// Add vertices to batch buffer
+			s_batchVertices.insert(s_batchVertices.end(), vertices, vertices + 4);
+
+			// Add triangle indices for this quad (2 triangles = 6 indices)
+			s_batchIndices.push_back(vertexIndex + 0);
+			s_batchIndices.push_back(vertexIndex + 1);
+			s_batchIndices.push_back(vertexIndex + 2);
+			s_batchIndices.push_back(vertexIndex + 1);
+			s_batchIndices.push_back(vertexIndex + 3);
+			s_batchIndices.push_back(vertexIndex + 2);
+
+			vertexIndex += 4;
+		}
+
+		// MASSIVE PERFORMANCE BOOST: Render entire texture group in 1 draw call!
+		// Was: N draw calls (one per character)
+		// Now: 1 draw call (entire texture group)
+		if (!s_batchVertices.empty())
+		{
+			STATEMANAGER.DrawIndexedPrimitiveUP(
+				D3DPT_TRIANGLELIST,
+				0,
+				s_batchVertices.size(),
+				s_batchIndices.size() / 3,
+				&s_batchIndices[0],
+				D3DFMT_INDEX16,
+				&s_batchVertices[0],
+				sizeof(SPDTVertex)
+			);
+		}
+	}
+}
Index: src/EterLib/GrpTextInstance.h
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/EterLib/GrpTextInstance.h b/src/EterLib/GrpTextInstance.h
--- a/src/EterLib/GrpTextInstance.h	(revision bb19e9abda71c4545d35a3f9bf8cfedf3ce3c7b7)
+++ b/src/EterLib/GrpTextInstance.h	(revision fc1c64cfd2cace42c872955604f5e9aef67a3ae3)
@@ -73,6 +73,55 @@
 		int  __DrawCharacter(CGraphicFontTexture * pFontTexture, WORD codePage, wchar_t text, DWORD dwColor);
 		void __GetTextPos(DWORD index, float* x, float* y);
 		int __GetTextTag(const wchar_t * src, int maxLen, int & tagLen, std::wstring & extraInfo);
+		
+		// Optimized batched rendering methods
+		struct BatchedCharacter {
+			float x, y;           // Start position
+			float ex, ey;         // End position (pre-calculated for performance)
+			float u1, v1, u2, v2; // Texture coordinates
+			DWORD color;
+			DWORD textureIndex;
+		};
+		
+		// Memory pool for batched rendering (eliminates per-frame allocations)
+		static std::vector<BatchedCharacter> s_outlineBatch;
+		static std::vector<BatchedCharacter> s_mainBatch;
+		static std::map<DWORD, std::vector<const BatchedCharacter*>> s_textureGroups;
+		
+		// True vertex buffer batching (massive performance boost)
+		static std::vector<SPDTVertex> s_batchVertices;
+		static std::vector<WORD> s_batchIndices;
+		
+		// Fixed-size texture grouping (eliminates std::map allocations)
+		static const int MAX_FONT_TEXTURES = 16; // Most fonts use 1-4 textures
+		struct TextureGroup {
+			DWORD textureIndex;
+			const BatchedCharacter* characters[256]; // Max characters per texture group
+			int count;
+		};
+		static TextureGroup s_textureGroupsArray[MAX_FONT_TEXTURES];
+		static int s_activeTextureGroupCount;
+		
+		// Safe render state caching (per-frame validation + device change detection)
+		struct FrameRenderStateCache {
+			DWORD frameId;
+			LPDIRECT3DDEVICE9 devicePtr; // For device change detection
+			bool isInitialized;
+			bool textureStatesSet; // Track if texture stage states are already set this frame
+			
+			// Cached render states 
+			DWORD savedFogEnable;
+			DWORD savedLighting;
+		};
+		static FrameRenderStateCache s_renderStateCache;
+		
+	public:
+		// Call this when graphics context changes (map changes, device reset, etc.)
+		static void InvalidateRenderStateCache();
+		
+		void __RenderBatched(RECT* pClipRect);
+		void __BuildCharacterBatch(std::vector<BatchedCharacter>& outlineBatch, std::vector<BatchedCharacter>& mainBatch);
+		void __RenderCharacterBatch(const std::vector<BatchedCharacter>& batch, bool isOutline);
 
 	protected:
 		struct SHyperlink

Implement Nicu's patch for text rendering, it adds 10-30 fps in client in certain instances BUT NOTE: MOVE EMOJI RENDERING after implementing it , to be initialized at the end of the render, otherwise you will not have emojis anymore in client ``` Subject: [PATCH] optimizing client text rendering --- Index: src/EterLib/GrpBase.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/EterLib/GrpBase.h b/src/EterLib/GrpBase.h --- a/src/EterLib/GrpBase.h (revision bb19e9abda71c4545d35a3f9bf8cfedf3ce3c7b7) +++ b/src/EterLib/GrpBase.h (revision fc1c64cfd2cace42c872955604f5e9aef67a3ae3) @@ -134,6 +134,7 @@ static DWORD GetAvailableTextureMemory(); static const D3DXMATRIX& GetViewMatrix(); static const D3DXMATRIX & GetIdentityMatrix(); + static LPDIRECT3DDEVICE9 GetDevice() { return ms_lpd3dDevice; } enum { @@ -213,6 +214,7 @@ static void SetDefaultIndexBuffer(UINT eDefIB); static bool SetPDTStream(SPDTVertexRaw* pVertices, UINT uVtxCount); static bool SetPDTStream(SPDTVertex* pVertices, UINT uVtxCount); + static UINT GetPDTVertexNum() { return PDT_VERTEX_NUM; } protected: static D3DXMATRIX ms_matIdentity; Index: src/EterLib/GrpTextInstance.cpp IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/EterLib/GrpTextInstance.cpp b/src/EterLib/GrpTextInstance.cpp --- a/src/EterLib/GrpTextInstance.cpp (revision bb19e9abda71c4545d35a3f9bf8cfedf3ce3c7b7) +++ b/src/EterLib/GrpTextInstance.cpp (revision fc1c64cfd2cace42c872955604f5e9aef67a3ae3) @@ -9,6 +9,29 @@ extern DWORD GetDefaultCodePage(); +// Static memory pools for optimized batched rendering +std::vector<CGraphicTextInstance::BatchedCharacter> CGraphicTextInstance::s_outlineBatch; +std::vector<CGraphicTextInstance::BatchedCharacter> CGraphicTextInstance::s_mainBatch; +std::map<DWORD, std::vector<const CGraphicTextInstance::BatchedCharacter*>> CGraphicTextInstance::s_textureGroups; + +// True vertex buffer batching (massive performance boost) +std::vector<SPDTVertex> CGraphicTextInstance::s_batchVertices; +std::vector<WORD> CGraphicTextInstance::s_batchIndices; + +// Fixed-size texture grouping (eliminates std::map allocations) +CGraphicTextInstance::TextureGroup CGraphicTextInstance::s_textureGroupsArray[CGraphicTextInstance::MAX_FONT_TEXTURES]; +int CGraphicTextInstance::s_activeTextureGroupCount = 0; + +// Safe render state caching (per-frame validation) +CGraphicTextInstance::FrameRenderStateCache CGraphicTextInstance::s_renderStateCache = {0, NULL, false, false, 0, 0}; + +void CGraphicTextInstance::InvalidateRenderStateCache() +{ + s_renderStateCache.isInitialized = false; + s_renderStateCache.frameId = 0; + s_renderStateCache.devicePtr = NULL; +} + const float c_fFontFeather = 0.5f; CDynamicPool<CGraphicTextInstance> CGraphicTextInstance::ms_kPool; @@ -486,6 +509,10 @@ CGraphicFontTexture* pFontTexture = pkText->GetFontTexturePointer(); if (!pFontTexture) return; + + // USE OPTIMIZED BATCHED RENDERING SYSTEM - PERFORMANCE BOOST! + __RenderBatched(pClipRect); + return; float fStanX = m_v3Position.x; float fStanY = m_v3Position.y + 1.0f; @@ -532,6 +559,7 @@ //WORD FillRectIndices[6] = { 0, 2, 1, 2, 3, 1 }; + STATEMANAGER.SaveRenderState(D3DRS_ALPHABLENDENABLE, TRUE); STATEMANAGER.SaveRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); STATEMANAGER.SaveRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); DWORD dwFogEnable = STATEMANAGER.GetRenderState(D3DRS_FOGENABLE); @@ -859,6 +887,7 @@ } } + STATEMANAGER.RestoreRenderState(D3DRS_ALPHABLENDENABLE); STATEMANAGER.RestoreRenderState(D3DRS_SRCBLEND); STATEMANAGER.RestoreRenderState(D3DRS_DESTBLEND); @@ -1148,3 +1177,511 @@ { Destroy(); } + +// PERFORMANCE OPTIMIZATION: Batched text rendering system +// Reduces 5 draw calls per character to 2 draw calls total (outline batch + main batch) +void CGraphicTextInstance::__RenderBatched(RECT* pClipRect) +{ + if (m_pCharInfoVector.empty()) + return; + + CGraphicText* pText = m_roText.GetPointer(); + if (!pText) + return; + + CGraphicFontTexture* pFontTexture = pText->GetFontTexturePointer(); + if (!pFontTexture) + return; + + // Use static buffers to avoid per-frame allocations + s_outlineBatch.clear(); + s_mainBatch.clear(); + + // Pre-reserve space based on character count to avoid reallocations + if (m_isOutline) + s_outlineBatch.reserve(m_pCharInfoVector.size() * 4); // 4 outline quads per character (matches original) + s_mainBatch.reserve(m_pCharInfoVector.size()); + + // Build character batches using static buffers + __BuildCharacterBatch(s_outlineBatch, s_mainBatch); + + // PER-FRAME RENDER STATE CACHING - Cache states for current frame only + static DWORD s_lastFrameId = 0; + static DWORD s_currentFrameId = 0; + s_currentFrameId++; // Increment each render call + + LPDIRECT3DDEVICE9 currentDevice = CGraphicBase::GetDevice(); + + // Cache valid only for current frame and same device + bool cacheValid = s_renderStateCache.isInitialized && + s_renderStateCache.frameId == s_currentFrameId && + s_renderStateCache.devicePtr == currentDevice; + + DWORD dwFogEnable, dwLighting; + + // ALWAYS save render states to maintain save/restore balance (prevents flickering) + STATEMANAGER.SaveRenderState(D3DRS_ALPHABLENDENABLE, TRUE); + STATEMANAGER.SaveRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + STATEMANAGER.SaveRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + + if (!cacheValid) + { + // Cache miss: Get current states and set new ones + dwFogEnable = STATEMANAGER.GetRenderState(D3DRS_FOGENABLE); + dwLighting = STATEMANAGER.GetRenderState(D3DRS_LIGHTING); + STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, FALSE); + STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE); + + // Cache the state values for this frame + s_renderStateCache.frameId = s_currentFrameId; + s_renderStateCache.devicePtr = currentDevice; + s_renderStateCache.savedFogEnable = dwFogEnable; + s_renderStateCache.savedLighting = dwLighting; + s_renderStateCache.textureStatesSet = false; // Reset texture state flag for new frame + s_renderStateCache.isInitialized = true; + } + else + { + // Cache hit: Use cached values and set states + // PERFORMANCE BOOST: Skip expensive GetRenderState calls (2 DirectX calls saved) + dwFogEnable = s_renderStateCache.savedFogEnable; + dwLighting = s_renderStateCache.savedLighting; + STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, FALSE); + STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE); + } + + // Cache texture stage states (only set once per frame with device validation) + if (!s_renderStateCache.textureStatesSet) + { + STATEMANAGER.SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + s_renderStateCache.textureStatesSet = true; + } + // Else: Skip expensive texture stage state calls (cached!) + + // Render outline batch (if outline enabled) + if (m_isOutline && !s_outlineBatch.empty()) + { + __RenderCharacterBatch(s_outlineBatch, true); + } + + // Render main character batch + if (!s_mainBatch.empty()) + { + __RenderCharacterBatch(s_mainBatch, false); + } + + // Handle cursor rendering (preserved from original) + UINT defCodePage = GetDefaultCodePage(); + if (m_isCursor) + { + // Draw Cursor + float sx, sy, ex, ey; + TDiffuse diffuse; + + int curpos = CIME::GetCurPos(); + int compend = curpos + CIME::GetCompLen(); + + __GetTextPos(curpos, &sx, &sy); + + // If Composition + if(curpos<compend) + { + diffuse = 0x7fffffff; + __GetTextPos(compend, &ex, &sy); + } + else + { + diffuse = 0xffffffff; + ex = sx + 2; + } + + // FOR_ARABIC_ALIGN + if (defCodePage == CP_ARABIC) + { + sx += m_v3Position.x - m_textWidth; + ex += m_v3Position.x - m_textWidth; + sy += m_v3Position.y; + ey = sy + m_textHeight; + } + else + { + sx += m_v3Position.x; + sy += m_v3Position.y; + ex += m_v3Position.x; + ey = sy + m_textHeight; + } + + TPDTVertex vertices[4]; + vertices[0].diffuse = diffuse; + vertices[1].diffuse = diffuse; + vertices[2].diffuse = diffuse; + vertices[3].diffuse = diffuse; + vertices[0].position = TPosition(sx, sy, 0.0f); + vertices[1].position = TPosition(ex, sy, 0.0f); + vertices[2].position = TPosition(sx, ey, 0.0f); + vertices[3].position = TPosition(ex, ey, 0.0f); + + STATEMANAGER.SetTexture(0, NULL); + + CGraphicBase::SetDefaultIndexBuffer(CGraphicBase::DEFAULT_IB_FILL_RECT); + if (CGraphicBase::SetPDTStream(vertices, 4)) + STATEMANAGER.DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 4, 0, 2); + + int ulbegin = CIME::GetULBegin(); + int ulend = CIME::GetULEnd(); + + if(ulbegin < ulend) + { + const WORD c_FillRectIndices[6] = { 0, 2, 1, 2, 3, 1 }; + __GetTextPos(curpos+ulbegin, &sx, &sy); + __GetTextPos(curpos+ulend, &ex, &sy); + + sx += m_v3Position.x; + sy += m_v3Position.y + m_textHeight; + ex += m_v3Position.x; + ey = sy + 2; + + vertices[0].diffuse = 0xFFFF0000; + vertices[1].diffuse = 0xFFFF0000; + vertices[2].diffuse = 0xFFFF0000; + vertices[3].diffuse = 0xFFFF0000; + vertices[0].position = TPosition(sx, sy, 0.0f); + vertices[1].position = TPosition(ex, sy, 0.0f); + vertices[2].position = TPosition(sx, ey, 0.0f); + vertices[3].position = TPosition(ex, ey, 0.0f); + + STATEMANAGER.DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, 4, 2, c_FillRectIndices, D3DFMT_INDEX16, vertices, sizeof(TPDTVertex)); + } + } + + // Handle hyperlinks (preserved from original) + if (m_hyperlinkVector.size() != 0) + { + int lx = gs_mx - m_v3Position.x; + int ly = gs_my - m_v3Position.y; + + if (GetDefaultCodePage() == CP_ARABIC) { + lx = -lx; + ly = -ly + m_textHeight; + } + + if (lx >= 0 && ly >= 0 && lx < m_textWidth && ly < m_textHeight) + { + std::vector<SHyperlink>::iterator it = m_hyperlinkVector.begin(); + + while (it != m_hyperlinkVector.end()) + { + SHyperlink & link = *it++; + if (lx >= link.sx && lx < link.ex) + { + gs_hyperlinkText = link.text; + break; + } + } + } + } + + // Restore render states + STATEMANAGER.RestoreRenderState(D3DRS_ALPHABLENDENABLE); + STATEMANAGER.RestoreRenderState(D3DRS_SRCBLEND); + STATEMANAGER.RestoreRenderState(D3DRS_DESTBLEND); + STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, dwFogEnable); + STATEMANAGER.SetRenderState(D3DRS_LIGHTING, dwLighting); +} + +void CGraphicTextInstance::__BuildCharacterBatch(std::vector<BatchedCharacter>& outlineBatch, std::vector<BatchedCharacter>& mainBatch) +{ + CGraphicText* pText = m_roText.GetPointer(); + CGraphicFontTexture* pFontTexture = pText->GetFontTexturePointer(); + + float fStanX = m_v3Position.x; + float fStanY = m_v3Position.y + 1.0f; + + // Apply alignment exactly like original + UINT defCodePage = GetDefaultCodePage(); + + if (defCodePage == CP_ARABIC) + { + switch (m_hAlign) + { + case HORIZONTAL_ALIGN_LEFT: + fStanX -= m_textWidth; + break; + + case HORIZONTAL_ALIGN_CENTER: + fStanX -= float(m_textWidth / 2); + break; + } + } + else + { + switch (m_hAlign) + { + case HORIZONTAL_ALIGN_RIGHT: + fStanX -= m_textWidth; + break; + + case HORIZONTAL_ALIGN_CENTER: + fStanX -= float(m_textWidth / 2); + break; + } + } + + switch (m_vAlign) + { + case VERTICAL_ALIGN_BOTTOM: + fStanY -= m_textHeight; + break; + + case VERTICAL_ALIGN_CENTER: + fStanY -= float(m_textHeight) / 2.0f; + break; + } + + float fCurX = fStanX; + float fCurY = fStanY; + float fFontMaxHeight = 0.0f; + + const float fFontHalfWeight = 1.0f; + + // Pre-calculate frequently used values + const float fLimitWidth = m_fLimitWidth; + const float fPositionX = m_v3Position.x; + const bool bIsMultiLine = m_isMultiLine; + const bool bIsOutline = m_isOutline; + const size_t characterCount = m_pCharInfoVector.size(); + + for (size_t i = 0; i < characterCount; ++i) + { + const CGraphicFontTexture::TCharacterInfomation* pCurCharInfo = m_pCharInfoVector[i]; + + // Cache character properties to reduce member access + const float fFontWidth = static_cast<float>(pCurCharInfo->width); + const float fFontHeight = static_cast<float>(pCurCharInfo->height); + const float fFontAdvance = static_cast<float>(pCurCharInfo->advance); + + // Update max height (branchless when possible) + if (fFontHeight > fFontMaxHeight) + fFontMaxHeight = fFontHeight; + + // Optimized width limit check + const float fNewX = fCurX + fFontWidth; + if (fNewX - fPositionX > fLimitWidth) + { + if (bIsMultiLine) + { + fCurX = fStanX; + fCurY += fFontMaxHeight; + } + else + { + break; // Early exit for single line + } + } + + const float fFontSx = fCurX - 0.5f; + const float fFontSy = fCurY - 0.5f; + const float fFontEx = fFontSx + fFontWidth; + const float fFontEy = fFontSy + fFontHeight; + + // Cache texture coordinates to avoid repeated member access + const float fU1 = pCurCharInfo->left; + const float fV1 = pCurCharInfo->top; + const float fU2 = pCurCharInfo->right; + const float fV2 = pCurCharInfo->bottom; + const DWORD dwTextureIndex = pCurCharInfo->index; + + // Create outline characters (4 directions) - matches original crisp quality + if (bIsOutline) + { + BatchedCharacter outlineChar; + outlineChar.u1 = fU1; + outlineChar.v1 = fV1; + outlineChar.u2 = fU2; + outlineChar.v2 = fV2; + outlineChar.color = m_dwOutLineColor; + outlineChar.textureIndex = dwTextureIndex; + + // Left outline (pre-calculated extents) + outlineChar.x = fFontSx - fFontHalfWeight; + outlineChar.y = fFontSy; + outlineChar.ex = outlineChar.x + fFontWidth; + outlineChar.ey = outlineChar.y + fFontHeight; + outlineBatch.push_back(outlineChar); + + // Right outline + outlineChar.x = fFontSx + fFontHalfWeight; + outlineChar.y = fFontSy; + outlineChar.ex = outlineChar.x + fFontWidth; + outlineChar.ey = outlineChar.y + fFontHeight; + outlineBatch.push_back(outlineChar); + + // Top outline + outlineChar.x = fFontSx; + outlineChar.y = fFontSy - fFontHalfWeight; + outlineChar.ex = outlineChar.x + fFontWidth; + outlineChar.ey = outlineChar.y + fFontHeight; + outlineBatch.push_back(outlineChar); + + // Bottom outline + outlineChar.x = fFontSx; + outlineChar.y = fFontSy + fFontHalfWeight; + outlineChar.ex = outlineChar.x + fFontWidth; + outlineChar.ey = outlineChar.y + fFontHeight; + outlineBatch.push_back(outlineChar); + } + + // Create main character using cached values and pre-calculated extents + BatchedCharacter mainChar; + mainChar.x = fFontSx; + mainChar.y = fFontSy; + mainChar.ex = fFontEx; + mainChar.ey = fFontEy; + mainChar.u1 = fU1; + mainChar.v1 = fV1; + mainChar.u2 = fU2; + mainChar.v2 = fV2; + mainChar.color = m_dwColorInfoVector[i]; + mainChar.textureIndex = dwTextureIndex; + mainBatch.push_back(mainChar); + + fCurX += fFontAdvance; + } +} + +void CGraphicTextInstance::__RenderCharacterBatch(const std::vector<BatchedCharacter>& batch, bool isOutline) +{ + if (batch.empty()) + return; + + CGraphicText* pText = m_roText.GetPointer(); + CGraphicFontTexture* pFontTexture = pText->GetFontTexturePointer(); + + // ALLOCATION-FREE TEXTURE GROUPING using fixed-size arrays + s_activeTextureGroupCount = 0; + + // Group characters by texture using fixed arrays (no allocations!) + for (const auto& character : batch) + { + // Find existing texture group or create new one + int groupIndex = -1; + for (int i = 0; i < s_activeTextureGroupCount; ++i) + { + if (s_textureGroupsArray[i].textureIndex == character.textureIndex) + { + groupIndex = i; + break; + } + } + + // Create new group if not found + if (groupIndex == -1) + { + if (s_activeTextureGroupCount < MAX_FONT_TEXTURES) + { + groupIndex = s_activeTextureGroupCount++; + s_textureGroupsArray[groupIndex].textureIndex = character.textureIndex; + s_textureGroupsArray[groupIndex].count = 0; + } + else + { + // Fallback to first group if we exceed max textures (rare case) + groupIndex = 0; + } + } + + // Add character to group (with bounds check) + TextureGroup& group = s_textureGroupsArray[groupIndex]; + if (group.count < 256) // Max characters per texture group + { + group.characters[group.count++] = &character; + } + } + + // TRUE VERTEX BUFFER BATCHING - MASSIVE PERFORMANCE BOOST! + // Renders entire texture groups in single draw calls instead of N individual calls + for (int groupIdx = 0; groupIdx < s_activeTextureGroupCount; ++groupIdx) + { + const TextureGroup& group = s_textureGroupsArray[groupIdx]; + DWORD textureIndex = group.textureIndex; + + // Set texture once per group + pFontTexture->SelectTexture(textureIndex); + STATEMANAGER.SetTexture(0, pFontTexture->GetD3DTexture()); + + // Clear and reserve space in static buffers (avoid per-frame allocations) + s_batchVertices.clear(); + s_batchIndices.clear(); + s_batchVertices.reserve(group.count * 4); // 4 vertices per quad + s_batchIndices.reserve(group.count * 6); // 6 indices per quad (2 triangles) + + // Build massive vertex buffer containing all characters for this texture + WORD vertexIndex = 0; + for (int charIdx = 0; charIdx < group.count; ++charIdx) + { + const BatchedCharacter* pChar = group.characters[charIdx]; + // OPTIMIZED: Create quad vertices using pre-calculated extents and cached values + const float posZ = m_v3Position.z; + const DWORD color = pChar->color; + const float u1 = pChar->u1, v1 = pChar->v1, u2 = pChar->u2, v2 = pChar->v2; + const float x = pChar->x, y = pChar->y, ex = pChar->ex, ey = pChar->ey; + + SPDTVertex vertices[4]; + // Top-left + vertices[0].position = TPosition(x, y, posZ); + vertices[0].diffuse = color; + vertices[0].texCoord = TTextureCoordinate(u1, v1); + + // Bottom-left + vertices[1].position = TPosition(x, ey, posZ); + vertices[1].diffuse = color; + vertices[1].texCoord = TTextureCoordinate(u1, v2); + + // Top-right + vertices[2].position = TPosition(ex, y, posZ); + vertices[2].diffuse = color; + vertices[2].texCoord = TTextureCoordinate(u2, v1); + + // Bottom-right + vertices[3].position = TPosition(ex, ey, posZ); + vertices[3].diffuse = color; + vertices[3].texCoord = TTextureCoordinate(u2, v2); + + // Add vertices to batch buffer + s_batchVertices.insert(s_batchVertices.end(), vertices, vertices + 4); + + // Add triangle indices for this quad (2 triangles = 6 indices) + s_batchIndices.push_back(vertexIndex + 0); + s_batchIndices.push_back(vertexIndex + 1); + s_batchIndices.push_back(vertexIndex + 2); + s_batchIndices.push_back(vertexIndex + 1); + s_batchIndices.push_back(vertexIndex + 3); + s_batchIndices.push_back(vertexIndex + 2); + + vertexIndex += 4; + } + + // MASSIVE PERFORMANCE BOOST: Render entire texture group in 1 draw call! + // Was: N draw calls (one per character) + // Now: 1 draw call (entire texture group) + if (!s_batchVertices.empty()) + { + STATEMANAGER.DrawIndexedPrimitiveUP( + D3DPT_TRIANGLELIST, + 0, + s_batchVertices.size(), + s_batchIndices.size() / 3, + &s_batchIndices[0], + D3DFMT_INDEX16, + &s_batchVertices[0], + sizeof(SPDTVertex) + ); + } + } +} Index: src/EterLib/GrpTextInstance.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/EterLib/GrpTextInstance.h b/src/EterLib/GrpTextInstance.h --- a/src/EterLib/GrpTextInstance.h (revision bb19e9abda71c4545d35a3f9bf8cfedf3ce3c7b7) +++ b/src/EterLib/GrpTextInstance.h (revision fc1c64cfd2cace42c872955604f5e9aef67a3ae3) @@ -73,6 +73,55 @@ int __DrawCharacter(CGraphicFontTexture * pFontTexture, WORD codePage, wchar_t text, DWORD dwColor); void __GetTextPos(DWORD index, float* x, float* y); int __GetTextTag(const wchar_t * src, int maxLen, int & tagLen, std::wstring & extraInfo); + + // Optimized batched rendering methods + struct BatchedCharacter { + float x, y; // Start position + float ex, ey; // End position (pre-calculated for performance) + float u1, v1, u2, v2; // Texture coordinates + DWORD color; + DWORD textureIndex; + }; + + // Memory pool for batched rendering (eliminates per-frame allocations) + static std::vector<BatchedCharacter> s_outlineBatch; + static std::vector<BatchedCharacter> s_mainBatch; + static std::map<DWORD, std::vector<const BatchedCharacter*>> s_textureGroups; + + // True vertex buffer batching (massive performance boost) + static std::vector<SPDTVertex> s_batchVertices; + static std::vector<WORD> s_batchIndices; + + // Fixed-size texture grouping (eliminates std::map allocations) + static const int MAX_FONT_TEXTURES = 16; // Most fonts use 1-4 textures + struct TextureGroup { + DWORD textureIndex; + const BatchedCharacter* characters[256]; // Max characters per texture group + int count; + }; + static TextureGroup s_textureGroupsArray[MAX_FONT_TEXTURES]; + static int s_activeTextureGroupCount; + + // Safe render state caching (per-frame validation + device change detection) + struct FrameRenderStateCache { + DWORD frameId; + LPDIRECT3DDEVICE9 devicePtr; // For device change detection + bool isInitialized; + bool textureStatesSet; // Track if texture stage states are already set this frame + + // Cached render states + DWORD savedFogEnable; + DWORD savedLighting; + }; + static FrameRenderStateCache s_renderStateCache; + + public: + // Call this when graphics context changes (map changes, device reset, etc.) + static void InvalidateRenderStateCache(); + + void __RenderBatched(RECT* pClipRect); + void __BuildCharacterBatch(std::vector<BatchedCharacter>& outlineBatch, std::vector<BatchedCharacter>& mainBatch); + void __RenderCharacterBatch(const std::vector<BatchedCharacter>& batch, bool isOutline); protected: struct SHyperlink ```
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: Metin2/bug-tracker#6
There is no content yet.