Release version graphics bug - need help!

Started by
8 comments, last by steve coward 15 years, 6 months ago
My game is an isometric tile based RTS game written in C++ MS 2005 Express. The graphics engine (dx9 based) uses 5 different vertex arrays of varying sizes and two vertex buffers - 1 for triangles and 1 for lines. My game has a graphics bug that occurs only when compiled with /MD or /MT flags. The bug is not present when /MDd or /MTd is used. When the bug is present, the problem disappears if I limit the number of vertices contained in the 3rd vertex array - this array holds vertices of shroud or fog tiles - by revealing some portion of the map through unit movement. The bug causes extra, random polygons to be drawn to the screen. The polygons appear to be from my font textures only. Also, at short intervals, certain polygons are not drawn - such as the frame around the game window. I am at a loss at how to begin to debug this problem. It seems like some sort of vertex overflow problem, but I am unable to see any problems. I have included main code for the creation of the vertex buffers and for drawing the vertices. I am not sure what additional code snippets might be useful for others to help. Any ideas would be greatly appreciated. thanks

HRESULT CD3DApplication::CreateVertexBuffers()
{
	HRESULT hr;
	int i;

	AccessCriticalSection(g_csDrawDatabase1, true, CRITICALSECTION_DRAW, __LINE__, __FILE__);

	InitDrawQueue();

	// m_viewIsoTileLength = number x tiles in view
	// m_viewIsoTileWidth  = number y tiles in view
	//
	// terrain vertices drawn first for each draw iteration.
	// shroud vertices drawn towards very end of draw iteration.
	// The contents of these two arrays tend to change infrequently, they are not necessarily updated each draw iteration.
	m_vertexArrayType[TERRAIN_VERTICES] = _TLVERTEX;
	m_vertexArrayType[SHROUD_VERTICES] = _TLVERTEX;
	m_vertexArrayType[GENERIC_VERTICES] = _TLVERTEX;
	m_vertexArrayType[LINE_VERTICES] = _LINEVERTEX;
	m_vertexArrayType[FRAME_VERTICES] = _TLVERTEX;
	m_vertexArrayType[OVERLAY_VERTICES] = _TLVERTEX;

	//  drawing loop looks like
	//	for (y=ptItiStart.y;y<=ptItiEnd.y;y++) {
	//		for (x=max(0,ptItiStart.x-1);x<=ptItiEnd.x;x++) {
	//		}
	//	}
	m_vertexArraySize[TERRAIN_VERTICES] = (m_viewIsoTileLength+3) * (m_viewIsoTileWidth+1) * NUM_VERTICES_PER_QUAD * 4;
	m_vertexArraySize[SHROUD_VERTICES]	= (m_viewIsoTileLength+3) * (m_viewIsoTileWidth+1) * NUM_VERTICES_PER_QUAD;
	m_vertexArraySize[GENERIC_VERTICES] = MAX_GENERIC_QUADS_PERDRAW * NUM_VERTICES_PER_QUAD;
	m_vertexArraySize[LINE_VERTICES] = MAX_LINE_VERTICES_PERDRAW * NUM_VERTICES_PER_LINE;
	m_vertexArraySize[FRAME_VERTICES] = MAX_FRAME_QUADS_PERDRAW * NUM_VERTICES_PER_QUAD;
	m_vertexArraySize[OVERLAY_VERTICES] = MAX_OVERLAY_VERTICES_PERDRAW * NUM_VERTICES_PER_QUAD;
		
	m_vertexArrayStatic[TERRAIN_VERTICES] = 1;
	m_vertexArrayStatic[SHROUD_VERTICES] = 1;
	m_vertexArrayStatic[GENERIC_VERTICES] = 0;
	m_vertexArrayStatic[LINE_VERTICES] = 0;
	m_vertexArrayStatic[FRAME_VERTICES] = 0;
	m_vertexArrayStatic[OVERLAY_VERTICES] = 0;


	LPAPP_TL_VERTEX pTLVertices;
	LPAPP_LINE_VERTEX pLineVertices;
	
	for (i=0;i<NUM_VERTEX_ARRAYS;i++) {
		m_vertexArrayNum = 0;
		
		if (m_vertexArrayType == _TLVERTEX) {
			pTLVertices = (LPAPP_TL_VERTEX)m_mapVertexArray;
			
			if (pTLVertices == NULL) {
				pTLVertices = (APP_TL_VERTEX*) new APP_TL_VERTEX[m_vertexArraySize];
				m_mapVertexArray = (void*)pTLVertices;
			}
		}
		else {
			pLineVertices = (LPAPP_LINE_VERTEX)m_mapVertexArray;
			
			if (pLineVertices == NULL) {
				pLineVertices = (APP_LINE_VERTEX*) new APP_LINE_VERTEX[m_vertexArraySize];
				m_mapVertexArray = (void*)pLineVertices;
			}
		}

		DPRINTF("Vertex Array %d Size: %d\n", i, m_vertexArraySize);
	}
	
	// one vertex buffer for each type of vertex used by the app
	// they are sized equal to the largest vertex array of that type
	hr = m_pd3dDevice->CreateVertexBuffer(sizeof(APP_TL_VERTEX) * m_vertexArraySize[TERRAIN_VERTICES], m_dwVBUsage,
		D3DFVF_TLVERTEX, D3DPOOL_DEFAULT, &m_pVertexBuffer, NULL);
	if (FAILED(hr))
		DisplayErrorMsg(hr, MSG_NONE);
	hr = m_pd3dDevice->CreateVertexBuffer(sizeof(APP_LINE_VERTEX) * m_vertexArraySize[LINE_VERTICES], m_dwVBUsage,
		D3DFVF_LINEVERTEX, D3DPOOL_DEFAULT, &m_pLineVertexBuffer, NULL);
	if (FAILED(hr))
		DisplayErrorMsg(hr, MSG_NONE);

	m_pd3dDevice->SetStreamSource(0, m_pVertexBuffer, 0, sizeof(APP_TL_VERTEX));

	AccessCriticalSection(g_csDrawDatabase1, false, CRITICALSECTION_DRAW, __LINE__, __FILE__);

	return(hr);
}


HRESULT CD3DApplication::DrawQueue()
{
	HRESULT ddrval;
	int vertexArray;
	bool bAdded;
	bool bDone;
	bool bAtEnd;
	DWORD numArrayVertices;
	int numEntriesProcessed;
	unitID ID;
	LISTDRAWQUEUE* pListDrawQueue;
	LISTDRAWQUEUE::iterator itr, begin, end;
	LPAPP_QUEUE_VERTEX lpQueueEntry;
	LPAPP_QUEUE_VERTEX lpQueueNextEntry;

	
	ddrval = D3D_OK;
	  
	AccessCriticalSection(g_csDrawDatabase1, true, CRITICALSECTION_DRAW, __LINE__, __FILE__);

	//TANK_ASSERT((m_vertexArrayNum[TERRAIN_VERTICES] == 0), "");
	TANK_ASSERT((m_vertexArrayNum[GENERIC_VERTICES] == 0), "");
	//TANK_ASSERT((m_vertexArrayNum[SHROUD_VERTICES] == 0), "");
	TANK_ASSERT((m_vertexArrayNum[FRAME_VERTICES] == 0), "");
	TANK_ASSERT((m_vertexArrayNum[LINE_VERTICES] == 0), "");
	TANK_ASSERT((m_vertexArrayNum[OVERLAY_VERTICES] == 0), "");
	
	// this for loop enforces vertex type draw order
	for (vertexArray=0;vertexArray<NUM_QUEUE_TYPES;vertexArray++) {

		numArrayVertices = 0;
		numEntriesProcessed = 0;

		if ((m_vertexArrayStatic[vertexArray] == 1) && (m_vertexArrayNum[vertexArray] > 0)) {
			// nonempty static array of vertices

			pListDrawQueue = (LISTDRAWQUEUE*)m_mapVertexQueue[vertexArray];
			ddrval = DrawArrayVertices(vertexArray, (*pListDrawQueue->begin())->lpSrcTexture, (*pListDrawQueue->begin())->lpDestTexture, (*pListDrawQueue->begin())->ID, 0, m_vertexArrayNum[vertexArray]);
			TANK_ASSERT(!ShowError(ddrval, __LINE__, __FILE__), "");
		}
		else if ((pListDrawQueue = (LISTDRAWQUEUE*)m_mapVertexQueue[vertexArray]) != NULL) {
			begin = pListDrawQueue->begin();
			end = pListDrawQueue->end();
			for (itr=begin;itr!=end;itr++) {

				lpQueueEntry = *itr;

				if (lpQueueEntry->vertexType != vertexArray) {
					DPRINTF("DrawQueue type mismatch\n");
				}
					
				itr++;
				bAtEnd = (itr == end);
				if (bAtEnd) {
					lpQueueNextEntry = NULL;
				}
				else {
					lpQueueNextEntry = *itr;
				}
				itr--;

				ID = lpQueueEntry->ID;
				
				if (m_vertexArrayType[vertexArray] == _LINEVERTEX) {
					bAdded = AddD3DLine(
						CCltPoint(lpQueueEntry->rcDest.left, lpQueueEntry->rcDest.top),
						CCltPoint(lpQueueEntry->rcDest.right, lpQueueEntry->rcDest.bottom),
						lpQueueEntry->vertexColor,
						ID
						);
				}
				else {	
					bAdded = AddD3DQuad(
						lpQueueEntry->vertexType,
						lpQueueEntry->lpSrcTexture,
						&lpQueueEntry->srcVertices,
						&lpQueueEntry->rcDest,
						lpQueueEntry->vertexColor,
						lpQueueEntry->flags,
						lpQueueEntry->rotate,
						ID
						);
				}

				numEntriesProcessed += bAdded;
				
				bDone = bAtEnd && bAdded;
				
				if (bAdded) {
					
					if (m_vertexArrayStatic[vertexArray] == 0) {
						m_listFreeQueueVertices.push_back(lpQueueEntry);
					}

					if (m_vertexArrayType[vertexArray] == _LINEVERTEX) {
						numArrayVertices += NUM_VERTICES_PER_LINE;
					}
					else {
						numArrayVertices += NUM_VERTICES_PER_QUAD;
					}
					
					if (bDone || (lpQueueEntry->lpSrcTexture != lpQueueNextEntry->lpSrcTexture) || (lpQueueEntry->lpDestTexture != lpQueueNextEntry->lpDestTexture) || (numArrayVertices == m_vertexArraySize[vertexArray])) {
						TANK_ASSERT(!ShowError(ddrval, __LINE__, __FILE__), "");
						ddrval = DrawArrayVertices(vertexArray, lpQueueEntry->lpSrcTexture, lpQueueEntry->lpDestTexture, ID, 0, numArrayVertices);
						TANK_ASSERT(!ShowError(ddrval, __LINE__, __FILE__), "");
						//DPRINTF("DrawArrayVertices type:%d num:%d\n", vertexArray, numArrayVertices);

						TANK_ASSERT((numArrayVertices < m_vertexArraySize[vertexArray]) || (m_vertexArrayStatic[vertexArray] != 1) || bDone, "Static arrays must be sized to draw all static vertices in one pass.");

						d3dPerfStats[NUM_TEXTURE_CHANGES] += !bDone;
						numArrayVertices = 0;
						if (m_vertexArrayStatic[vertexArray] != 1) {
							m_vertexArrayNum[vertexArray] = 0;
						}
					}
				}
				else {
					ddrval = DrawArrayVertices(vertexArray, lpQueueEntry->lpSrcTexture, lpQueueEntry->lpDestTexture, ID, 0, numArrayVertices);
					TANK_ASSERT(!ShowError(ddrval, __LINE__, __FILE__), "");
					numArrayVertices = 0;
					if (m_vertexArrayStatic[vertexArray] != 1) {
						m_vertexArrayNum[vertexArray] = 0;
					}
					else {
						DPRINTF("should not be here.\n");
					}

					itr--;
				}
			}

			if (m_vertexArrayStatic[vertexArray] == 0) {
				pListDrawQueue->clear();
			}
		}
	}

	//TANK_ASSERT((m_vertexArrayNum[0] == 0), "");
	TANK_ASSERT((m_vertexArrayNum[1] == 0), "");
	//TANK_ASSERT((m_vertexArrayNum[2] == 0), "");
	TANK_ASSERT((m_vertexArrayNum[3] == 0), "");
	TANK_ASSERT((m_vertexArrayNum[4] == 0), "");
	TANK_ASSERT((m_vertexArrayNum[5] == 0), "");

	AccessCriticalSection(g_csDrawDatabase1, false, CRITICALSECTION_DRAW, __LINE__, __FILE__);

	return(ddrval);
}


HRESULT CD3DApplication::DrawArrayVertices(int vertexID, LPDIRECT3DTEXTURE9 lpSrcTexture, LPDIRECT3DTEXTURE9 lpDestTexture, unitID ID, DWORD startVertex, DWORD numVertices)
{
	HRESULT ddrval;
	LPDIRECT3DSURFACE9 lpD3DSurfDest = NULL;
	LPDIRECT3DSURFACE9 pBackBuffer = NULL;

	ddrval = D3D_OK;
	
	if (lpDestTexture != NULL) {							
		ddrval = m_pd3dDevice->GetRenderTarget(0, &pBackBuffer);
		TANK_ASSERT(!ShowError(ddrval, __LINE__, __FILE__), "");

		ddrval = lpDestTexture->GetSurfaceLevel(0, &lpD3DSurfDest);
		TANK_ASSERT(!ShowError(ddrval, __LINE__, __FILE__), "");
		
		ddrval = m_pd3dDevice->SetRenderTarget(0, lpD3DSurfDest);
		TANK_ASSERT(!ShowError(ddrval, __LINE__, __FILE__), "");
	}
	
	// issue do not special case this - instead track pipeline state changes
	if ((ID == MINIMAPSHROUD_ID))
	{	
		m_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_INVSRCALPHA);
		m_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);
		// for white shroud:
		// white areas of shroud texture show as white, black as transparent
		//m_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR);
		//m_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR);
	}
	
	TANK_ASSERT(startVertex <= m_vertexArraySize[vertexID], "");
	
	if (vertexID == LINE_VERTICES) {
		ddrval = BlitBatchLinesD3D(vertexID, startVertex, numVertices);
	}
	else {
		ddrval = BlitBatchTrianglesD3D(lpSrcTexture, vertexID, startVertex, numVertices);
	}
	TANK_ASSERT(!ShowError(ddrval, __LINE__, __FILE__), "");
	
	if ((ID == MINIMAPSHROUD_ID))
	{	
		m_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
		m_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
	}
	
	if (lpDestTexture != NULL) {		
		ddrval = m_pd3dDevice->SetRenderTarget(0, pBackBuffer);
		TANK_ASSERT(!ShowError(ddrval, __LINE__, __FILE__), "");

		pBackBuffer->Release();
		
		ddrval = lpD3DSurfDest->Release();
		TANK_ASSERT(!ShowError(ddrval, __LINE__, __FILE__), "");
	}
	
	return(ddrval);
}


HRESULT CD3DApplication::BlitBatchTrianglesD3D(LPDIRECT3DTEXTURE9 texture, int iActiveVertex, int iStartVertex, DWORD numVertices)
{
	HRESULT ddrval;
	LPAPP_TL_VERTEX pVertices;
	LPAPP_TL_VERTEX vertices;
	DWORD dwLockFlags;
	
	ddrval = S_OK;
	
	TANK_ASSERT(numVertices <= m_vertexArraySize[iActiveVertex], "need larger vertex buffer\n");
	TANK_ASSERT(numVertices%NUM_VERTICES_PER_TRIANGLE == 0, "number of vertices must be multiple of three\n");
	
	if (numVertices > 0) {
		//DPRINTF("batch draw\n");

		pVertices = (LPAPP_TL_VERTEX)m_mapVertexArray[iActiveVertex];

		if ((m_dwFirstValidVBEntry + numVertices >= m_vertexArraySize[iActiveVertex])) {
			//DPRINTF("discarding vertices next Entry (AGP) %d start vertex %d new vertices %d \n", m_dwFirstValidVBEntry, dwStartVertex, dwNumVertices);
			dwLockFlags = D3DLOCK_DISCARD;
			m_dwFirstValidVBEntry = 0;
			d3dPerfStats[NUM_DISCARD_LOCKS]++;
		}
		else {
			dwLockFlags = D3DLOCK_NOOVERWRITE;
			d3dPerfStats[NUM_NOOVERWRITE_LOCKS]++;
		}
		
		//Set texture
		ddrval = m_pd3dDevice->SetTexture (0, texture);
		TANK_ASSERT(ddrval == 0, "error");
		ddrval = m_pd3dDevice->SetStreamSource(0, m_pVertexBuffer, 0, sizeof(APP_TL_VERTEX));
		TANK_ASSERT(ddrval == 0, "error");
		ddrval = m_pd3dDevice->SetVertexShader(NULL);
		TANK_ASSERT(ddrval == 0, "error");
		ddrval = m_pd3dDevice->SetFVF(D3DFVF_TLVERTEX);
		TANK_ASSERT(ddrval == 0, "error");
		
		ddrval = m_pVertexBuffer->Lock(m_dwFirstValidVBEntry * sizeof(APP_TL_VERTEX), numVertices * sizeof(APP_TL_VERTEX), (void**)&vertices, dwLockFlags);
		TANK_ASSERT(ddrval == 0, "error");
		memcpy(vertices, pVertices+iStartVertex, numVertices * sizeof(APP_TL_VERTEX));
		//Unlock the vertex buffer
		ddrval = m_pVertexBuffer->Unlock();
		TANK_ASSERT(ddrval == 0, "error");
		
		//DPRINTF("drawing %d vertices\n", dwNumVertices);
		ddrval = m_pd3dDevice->DrawPrimitive (D3DPT_TRIANGLELIST, m_dwFirstValidVBEntry, numVertices/NUM_VERTICES_PER_TRIANGLE);
		TANK_ASSERT(ddrval == 0, "error");

		d3dPerfStats[NUM_DRAW_PRIMITIVE_CALLS]++;
		d3dPerfStats[iActiveVertex]++;
		d3dPerfStats[iActiveVertex+NUM_VERTEX_ARRAYS] += (m_vertexArraySize[iActiveVertex] - numVertices);
		d3dPerfStats[iActiveVertex+2*NUM_VERTEX_ARRAYS] = max(d3dPerfStats[iActiveVertex+2*NUM_VERTEX_ARRAYS], numVertices);
		
		m_dwFirstValidVBEntry += numVertices;
	}

	return(ddrval);
}

Advertisement
A few points:
1. Are you using the Debug Runtimes? Any relevant debug output from them?
2. Checking return values against 0 isn't the correct way to check for D3D errors - that's only checking 1 of the 2 billion possible success codes. The correct way is to use the SUCCEEDED or FAILED macros.
3. I hope that your asserts do something in release mode - if IDirect3DVertexBuffer9::Lock fails in release (Which it can do for several reasons), you'll end up using a bad pointer and probably crashing your app.
4. The m_mapVertexArray variable looks suspicious - are you absolutely sure that you're not overrunning that array?
5. Are you using D3D from multiple threads? If so, are you passing the D3DCREATE_MULTITHREADED flag to CreateDevice()?
Thanks for the reply.

I can reply to a few of the points quickly.

2. I changed my code to use my ShowError() routine (which
does use FAILED()) for all cases of "ddrval == 0".

4. mapVertexArray is a map of VertexArrays, not an array.

typedef std::map<WORD, void*> MAPVertexArray;
MAPVertexArray m_mapVertexArray;

It always holds exactly 6 void pointers to vertex arrays.

Does this ease your concerns?

5. I use threads only in my path search algorithm.
No part of my graphics engine is threaded.
Quote:Original post by steve coward
4. mapVertexArray is a map of VertexArrays, not an array.

typedef std::map<WORD, void*> MAPVertexArray;
MAPVertexArray m_mapVertexArray;

It always holds exactly 6 void pointers to vertex arrays.
Sorry, I meant the second part of the map, the void* arrays. If you're overrunning one of them, all sorts of weird things could happen.
1. Are you using the Debug Runtimes? Any relevant debug output from them?

I am now using the Debug Runtimes. I get the following messages:

Direct3D9: :====> ENTER: DLLMAIN(011fe6e0): Process Attach: 00000098, tid=000005b8
Direct3D9: :====> EXIT: DLLMAIN(011fe6e0): Process Attach: 00000098
Direct3D9: (INFO) :Direct3D9 Debug Runtime selected.
Direct3D9: (WARN) :No SW device has been registered. GetAdapterCaps fails.
D3D9 Helper: IDirect3D9::GetDeviceCaps failed: D3DERR_NOTAVAILABLE
'TankBattle.exe': Loaded 'C:\WINDOWS\SYSTEM32\d3dref9.dll', No symbols loaded.
Direct3D9: (INFO) :======================= Hal HWVP Pure device selected

Direct3D9: (INFO) :HalDevice Driver style 9

Direct3D9: :DoneExclusiveMode
Direct3D9: (INFO) :Failed to create driver indexbuffer


3. I hope that your asserts do something in release mode.

Yes they do print an error message. None of the asserts in my code
are triggered by this bug.



4. The m_mapVertexArray variable looks suspicious - are you absolutely sure that you're not overrunning that array?

Mot absolutely sure so I need to look at this in more detail.
However, I do have several asserts monitoring the number of entries in
each vertex array.

Do you compile with /RTC1 in debug mode? That can help getting release build bugs show up when running debug, particularly buffer overruns.
I am now compiling with /RTC options.
This is the first time that I have used it.
Thank you for pointing that out to me.
Amazingly only 1 inconsequential problem was
uncovered in the process.

So I have yet to make headway against the original bug.
That info message you got from the debug runtimes according to this site is caused by the card not supporting index buffers in video ram.

I also noticed that you're creating your non dynamic vertex buffers in the default pool. It's usually best to create as many resources (textures, vertex buffer, and index buffers) as possible in the managed pool so that D3D will handle the allocation of video card memory for you. It also makes lost devices easier to handle.
I have narrowed the occurrence of the bug to builds with both
/O2 and /MT compiler switches present. The other 3 combinations
do not exhibit the buggy behavior. Does anybody have any ideas
what may be the problem? Is this possibly a compiler issue?
Are these two switches incompatible?


- fails
/O2 /D "WIN32" /D "_WINDOWS" /D "_VC80_UPGRADE=0x0600" /D "_MBCS"
/GF /Gm /EHsc /MT /Gy /Fp".\Debug/TankBattle.pch" /Fo".\Debug/"
/Fd".\Debug/" /W3 /nologo /c /Zi /TP /wd4117 /errorReport:prompt

- works
/Od /D "WIN32" /D "_WINDOWS" /D "_VC80_UPGRADE=0x0600" /D "_MBCS"
/GF /Gm /EHsc /MT /Gy /Fp".\Debug/TankBattle.pch" /Fo".\Debug/"
/Fd".\Debug/" /W3 /nologo /c /Zi /TP /wd4117 /errorReport:prompt

- works
/O2 /D "WIN32" /D "_WINDOWS" /D "_VC80_UPGRADE=0x0600" /D "_MBCS"
/GF /Gm /EHsc /MTd /Gy /Fp".\Debug/TankBattle.pch" /Fo".\Debug/"
/Fd".\Debug/" /W3 /nologo /c /Zi /TP /wd4117 /errorReport:prompt

- works
/Od /D "WIN32" /D "_WINDOWS" /D "_VC80_UPGRADE=0x0600" /D "_MBCS"
/GF /Gm /EHsc /MTd /Gy /Fp".\Debug/TankBattle.pch" /Fo".\Debug/"
/Fd".\Debug/" /W3 /nologo /c /Zi /TP /wd4117 /errorReport:prompt
Can I use

#pragma optimize("", off)

#pragma optimize("", on)

to find the code which is failing?

Is #pragma optimize("", off)
the same as /Od ?

This topic is closed to new replies.

Advertisement