A few questions (C++/DirectX 9 related)

Started by
6 comments, last by Shannon Barber 16 years, 2 months ago
Hi, Route I've taken so far - you can skip this part as I'm not describing the problem as such. I'm working towards the goal of being able to produce a game such as Tetris. To do this, I've read through several beginning game programming books but I seem to come unstuck each time. The books that I've read so far either seem to be geared towards full 2D programming, using DirectDraw, or making 3D games with Direct3D. I don't feel ready to jump into making a 3D game so I wanted to use Direct3D to write a 2D game as this seemed like a logical step. This is all well and good except that I've been pouring over different books and online articles in order to understand certain things. /End After all that, I'm left with a simple DirectX 9 application I've written which draws a triangle to the screen. When you press a key (except escape), the triangle will rotate slightly. If you hold the key down, the triangle will rotate a bit more each frame (going off the screen) and eventually come back into position. Also, if you click the left mouse button anywhere on the screen, it will colour that pixel white. Although the program works, I have several issues with it. Here's the relevant source:

// Globals
bool g_Update = false;
CUSTOMVERTEX g_mouseVerts[100];  // Used to store points where the LMB has been clicked.
int g_mouseVertsCount = 0;  // Keep a count of the points.

That highlights my first problem. What if there were 255 points? Then I'd be screwed. The thing is, I can't work out how to efficiently pass input data to the render loop. The organisation I'm using at the moment is definitely wrong and if someone could offer me some advice or a book suggestion, I'd be very grateful. Here's the game loop:

if (SUCCEEDED(InitD3D(hwnd, true)))
{
	if (SUCCEEDED(InitVertexBuffer()))
	{
		// Game loop.
		while (msg.message != WM_QUIT)
		{
			if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			else
				Render();
		}
	}
}

This is the code for the Render function:

void Render()
{
	// Clear the backbuffer to a blue colour.
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 127), 1.0f, 0);

	// Begin the scene.
	if (SUCCEEDED(g_pd3dDevice->BeginScene()))
	{
		// I have concerns here.
		// If a key has been pressed, process any transformations.
		if (g_Update)
			SetupMatrices();

		// Draw the scene.
		g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));
		g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
		g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

		g_pd3dDevice->DrawPrimitiveUP(D3DPT_POINTLIST, g_mouseVertsCount, g_mouseVerts, sizeof(CUSTOMVERTEX));
		
		// End scene.
		g_pd3dDevice->EndScene();
	}

	// Present.
	g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
}

Here's the WndProc:

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
	switch (message)
	{
	case WM_KEYDOWN:
		{
			g_Update = true;
			if (wparam == VK_ESCAPE)
				PostQuitMessage(0);

			return 0;
		}

	case WM_LBUTTONDOWN:
		{
			float xCoord = LOWORD(lparam);
			float yCoord = HIWORD(lparam);

			// Process the mouse click.
			AddMousePoint(xCoord, yCoord);

			return 0;
		}

	case WM_DESTROY:
		{
			Cleanup();
			PostQuitMessage(0);
			return 0;
		}
	}

	return DefWindowProc(hwnd, message, wparam, lparam);
}

Finally, here's the AddMousePoint function:

void AddMousePoint(float x, float y)
{
	g_mouseVerts[g_mouseVertsCount].x = x;
	g_mouseVerts[g_mouseVertsCount].y = y;
	g_mouseVerts[g_mouseVertsCount].z = 0.0f;
	g_mouseVerts[g_mouseVertsCount].rhw = 1.0f;
	g_mouseVerts[g_mouseVertsCount].colour = D3DCOLOR_XRGB(255, 255, 255);

	g_mouseVertsCount++;
}

So here are my questions. 1. As you can see from my Render function, it checks to see if g_Update is true before calling SetupMatrices, which basically just rotates the triangle. It feels like it's the wrong way to go about it. I mean if I needed to make lots of different decisions like that in my Render function, I could end up passing all kinds of stuff into it each frame. The way I'm looking at it here, the Render function should do just that - render. If the way I'm doing this is fine, would I have to create some kind of RenderStateManager class which managed the whole render state and either pass that to Render or make it global? Does that seem sensible? 2. Also, what is a better way to go about storing the vertex data without defining mouseVerts to be global? The main problem I see here is maintaining the state of the vertices each frame. I made them global as I couldn't work out how to do that properly. Would I create another vertex buffer to do that? 3. I also have a C++-related question which stems from the following lines of code:

// Lock the vertex buffer and get a pointer to it.
	void *pVertices;
	if (FAILED(g_pVB->Lock(0, sizeof(vertices), (void **)&pVertices, 0)))
		return E_FAIL;

I'm having trouble understanding exactly what's happening here and I've seen this syntax in a lot of books without a true explanation as to what is really happening. I know it's passing in the address of pVertices to g_pVB->Lock, which is why the (void **) cast is required, but why not declare pVertices as a void **pVertices? Also, what is the pointer-to-a-pointer used for here? I know that last one is a dodgy question but I don't really understand what you'd use a pointer-to-a-pointer for. 4. Lastly, in order to remember the position of the triangle and draw it correctly each frame, I've been using the following function:

void SetupMatrices()
{
	D3DXMATRIX matVerts, matNewVerts, matTrans, matRotate;
	D3DXMatrixTranslation(&matTrans, 0.3f, 0.3f, 0.0f);
	D3DXMatrixRotationZ(&matRotate, D3DX_PI / 250);

	// Lock the vertex buffer and get a pointer to it.
	CUSTOMVERTEX *pVertices;
	g_pVB->Lock(0, 0, (void **)&pVertices, 0);

	// Copy the vertices into a matrix.
	for (int cr = 0; cr < 4; cr++)
	{
		matVerts(cr, 0) = pVertices[cr].x;
		matVerts(cr, 1) = pVertices[cr].y;
		matVerts(cr, 2) = pVertices[cr].z;
		matVerts(cr, 3) = 1.0f;
	}

	// Apply transforms.
	matNewVerts = matVerts * matTrans * matRotate;

	// Copy the new vertices over the old ones ready for rendering.
	for (int cr = 0; cr < 4; cr++)
	{
		pVertices[cr].x = matNewVerts(cr, 0);
		pVertices[cr].y = matNewVerts(cr, 1);
		pVertices[cr].z = matNewVerts(cr, 2);
	}
	g_pVB->Unlock();

	// Updates processed - reset flag.
	g_Update = false;
}

Is this the correct way to update vertices per frame? I've been given the impression that locking/unlocking a vertex buffer is a slow process. Whilst it's obviously not a problem in my application, is what I'm doing still considered to be an acceptable way of doing what I'm trying to achieve? -- Sorry the post is such a long one and thanks for your time.
Advertisement
1. Well you'll have to check that anyway, but better way to do it would be using some kind of state system. For example having a class or struct called StateCache which contains your matrices and calls to send matrices, e.g. if the matrix has not been changed it won't be updated.

Also you should have Render and Update - functions. :)

2. Well you could have a own class or struct for the called MouseVertexBuffer which contains your vertices, but well, it might just make things more complex than they are needed. Think about it like this : do you need a more abstract and dynamic system for handling the mouse vertices? If it works for you I don't see any reason for changing it.

3. Ah, void *pVertices - points exactly where you want, void **pVertices is pointing to pVertices pointer. By using void **pVertices yourself you would still need to point to the vertex data in order to handle it *pVertices.x = x_Pos .. and so on.

4. Yep locking buffers that are in GPU memory is slow as it stalls the GPU for the time the buffer is locked. You should use normal PROJECTION and MODELVIEW matrices that are passed to DirectX instead of transforming the vertices by yourself in the code. Check out : DirectX Tutorial - there is also other good tutorials that you should check.

Sincerely,Arto RuotsalainenDawn Bringer 3D - Tips & Tricks
Whew, that's a lot to answer! Let me see what I can clear up for you. [smile]

1. Creating some sort of class to encapsulate your rendering process is usually the most straightforward path to proceed. As you've already noticed, as you move towards making an actual game you're just going to be throwing more and more at your renderer and you're likely to need more support functions to go with it. If you're following OOP, making those functions part of a class is the way to go.

2. For your simple application here, I see no problem with having the small number of global variables you're using. I mean unless clicking on points and making them turn a color is going to be some integral part of an actual game or program, there's no need to worry about it. As for how you're storing them in an array...you've pointed out how since you've used a statically allocated array it is "stuck" at one fixed size which you cannot exceed. For this kind of problem you really have 3 options:

a) Limit the number of points to the size of array - simple, easy to implement, comes with restrictions
b) Use dynamically allocated arrays that you resize as needed. This isn't too difficult to implement, you just need to be careful with your new's and delete's to ensure no memory leaks.

// Initially allocate the arrayint g_mouseVertsSize = 100;CUSTOMVERTEX* g_mouseVerts = new CUSTOMVERTEX [g_mouseVertsSize];int g_mouseVertsCount = 0;// Then in your AddMousePoint function...if ( mouseVertsCount + 1 >= g_MouseVertsSize ){    // resize by double    g_mouseVertsSize *= 2;    CUSTOMVERTEX* newMouseVerts = new CUSTOMVERTEX [g_mouseVertsSize];       // copy over old data    CopyMemory( newMouseVerts, g_mouseVerts, g_mouseVertsCount * sizeof(CUSTOMVERTEX) );    // delete old array    delete [] g_mouseVerts;    g_mouseVerts = newMouseVerts;}


c) Use some sort of container class that will grow dynamically for you. std::vector is the obvious choice, you can simply add points to it and it will grow as necessary. The problem with std::vector is that once your points are in there, there is no easy way for you to send all the points to Direct3D (since you can't provide a pointer to an array of vertices for DrawPrimitiveUP()). You could copy the points to a dynamically allocated array, but this isn't much better than option b). What would be nice is using a container that automatically resizes but can provide you with a pointer to an internal array, such as the CAtlArray class (part of Microsoft's ATL library, which comes with Pro versions of Visual Studio).

3. In the case of locking an vertex buffer like that, typically a pointer to a type that can be used to access the internal contents of the buffer is used for convenience. So for example with your buffer, which was initially filled with elements of type CUSTOMVERTEX, a pointer of type CUSTOMVERTEX* is used to iterate through the vertex buffer so that the contents can be accessed. If you wanted you could declare the pointer as type void* so that it's not necessary to cast to void** passing the address of the pointer to the Lock() function, however this would mean that after locking you would need to cast that pointer to some other type before reading anything or writing anything to the buffer. void* is a generic type and can't be used for accessing data, all it can be used for is to point to data.

As for why the Lock() function takes a pointer to a pointer (AKA a variable of type void** and not void* as a parameter), the reason is because of the differences in C and C++. First let's talk about what the Lock() function does: it takes as a parameter a pointer, and then it takes that pointer and points it at some data representing the contents of the vertex buffer. Basically before you call the function you don't know where that data is, so you call it and D3D tells you. So this means that the function has to be able to change the parameter. Now you've said your new to C++, but I'm sure you've gone over how parameters are handled in C++. By default, the variables a function gets are copies of the original, and any changes you make will have no effect on the original variable. When it is necessary for the original to be changed, parameters are passed by reference. Like in this example:

void Add ( int a, int b, int& c ){     c = a + b;}


Okay, great. So you may be wondering: why isn't the ppbData parameter of Lock() passed by reference? The reason why is because the D3D API is written so it can be used from both C and C++ by making use of COM interfaces. And in C, there is no pass by reference. Instead, things have to be done by explicitly using pointers to types. In C the equivalent function to what I wrote above would look like this:

void Add ( int a, int b, int* c ){     *c = a + b;}


Now it should be apparent why the Lock() function looks the way it does. Since it ultimately wants to change the value of a pointer, it needs the a pointer to that pointer so it the changes will be reflected in the original. This is why you send "&pVertices", since the & operator means "get a pointer to this".

4. No, you're not really doing that in the most optimal way. You're right when you assume that locking a vertex buffer is slow, it's something that really shouldn't be done unless you have to. In general with 3D programming on the PC, when you have a triangle (or group of triangles) that you wanted to be transformed by several matrices, you want this transformation to be done on the GPU and not on the CPU. The way you're doing it, where you Lock the vertex buffer and manually apply the rotation and translation matrices, all of that calculation happens on the CPU. While for 4 vertices this is absolutely no problem at all, for 4,000 it would be quite wasteful. However for you to start getting this happening on the GPU, you would need to make some modifications.

You see the way you're doing things now is with pre-transformed vertices. The x and y components of your vertices correspond to on-screen coordinates, and the GPU is drawing them there accordingly. This is not common for 3D graphics work, since as I already mentioned it requires all transformation work to be done on the GPU. Instead what's usually done is the vertices are sent in their original positions (referred to as object space) and then, on the GPU, transformed by matrices. In the simplest method, using the fixed-function pipeline, these transforms are specified through IDirect3DDevice9::SetTransform(). There are typically three transforms used:

-the world transform, which usually translates and rotates primitives to their in-world positions and orientation
-the view transform, which translates and rotates primitives so that they are in positions and orientations that are relative to the virtual camera, or eye.
-the projection transform, projects the primitives into 2D space. Usually a perspective projection is used, which makes distant objects appear smaller than closer ones.

If this is all new to you, you should probably read a bit more on these topics or look through some of the SDK samples that deal with this early material. While it may not be completely necessary for 2D work, it's definitely essential for anything in 3D or for understanding how D3D works under the hood.

Speaking of 2D work...if 2D is your goal you may want to look at ID3DXSprite. It's a helper class for rendering of 2D sprites, and can get you started without messing with any of the lower level D3D stuff.
Hi!

Thanks a lot for the responses! I wasn't expecting to get as much information as you've both provided and it's given me great encouragement. You've both given me a few other questions though - hope that's alright.

Quote:
Also you should have Render and Update - functions. :)


This sounds like a great idea but I'm having a little trouble visualising roughly how it would work. I realise this could become quite sophisticated but could you perhaps provide me with a simple definition to cover this for now? Here's what I think you mean, but I'm not sure:

1. Process input and save current frame's state.
2. Apply changes to vertices.
3. Render vertices.

When I write it out like that it looks nice and simple but I'm still not really understanding how to properly manipulate vertex data. Let me try to give you an example as to how I see the problem:

while (msg.message != WM_QUIT){	if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))		// Process message.	else		Render();}


The problem I'm having, is where exactly to define my vertices. Would it perhaps be better to define a GameLoop function and have that called instead of Render, defining all of my vertices in there so that I can more easily pass them around? Here's some pseudo-code:

while (msg.message != WM_QUIT){	if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))		// Process message.	else		GameLoop();}void GameLoop(){	CVertices verts;	ProcessInput();	Update();	Render();}


Something like that seems better... no wait. Hm, that won't work. verts would be constructed/deconstructed each time upon entering/exiting GameLoop. Am I perhaps being pedantic about the organisation? Clearly, my above "solution" won't work, so I'm guessing I should just define all of my persistent vertices in the scope of WinMain. Ok, I should stop typing whilst thinking, but I think that's right. The only thing I need to work out then, is how to pass vertices around to different functions.

Moving onto that problem, would I be right in saying that this could be the perfect place for an interface? I've not got far enough to tell whether you'd end up with different "types" of vertex data, but as I'm sure that would crop up, a class encapsulating vertex data with a common public interface would seem like a good way of passing the data to Render and being able to operate on it but that still doesn't solve how I'd get around passing several chunks of vertex data about without getting into a mess. I'll leave that for now. I'm looking too far ahead I think.

The only other question I do have about that is am I right in having Update called before Render? This would seem to make sense but without a real example, I'm having problems understanding just how to distinguish between the two.

Quote:As for why the Lock() function takes a pointer to a pointer...


Thanks, this explanation helped me a lot. I'm going to write a few more prorgams dealing with pointers-to-pointers. That seems like the first thing I should do before progressing any further.

Quote:
If this is all new to you, you should probably read a bit more on these topics or look through some of the SDK samples that deal with this early material. While it may not be completely necessary for 2D work, it's definitely essential for anything in 3D or for understanding how D3D works under the hood.


Yeah, there was a lot of that which was new. Although I recognise a lot of the terms/names you've used there, I've never quite understood the whole rendering process which, by the looks of it, is why I'm a bit confused. Looking at it now, it makes perfect sense that I'm not using the GPU properly but it's not something I immediately recognised.

Obviously I need to focus my reading on this part as it seems like it's quite important. First thing I think I'll do, after working more with pointer test programs, is to re-write this application and try to restructure it properly. I must admit, I don't quite understand everything that's been said, but you've certainly given me a lot to go on. I'll make sure to read through that tutorial as well as it looks pretty useful.

Thanks a lot to both of you. The help is much appreciated. :)
Hi, to latest post by OP, you might want to follow MJP advice to move towards making classes to encapsulate the different functions. This can reduce the passing of the variables arounds from your Update()/Input()/Render() functions

of course for a small program, it has no harm to declare the CVertices at global scope. However to move away from global variables, you can do:

class PointSystem{    std::vector<CUSTOMVERTEX> MouseVertices;public:    void AddPoints(float xCoord, float yCoord)    {       CUSTOMVERTEX Vertex;       Vertex.x = x;       Vertex.y = y;       Vertex.z = 0.0f;       Vertex.rhw = 1.0f;       Vertex.colour = D3DCOLOR_XRGB(255, 255, 255);       MouseVertices.push_back(Vertex);    };    void Draw()    {       g_pd3dDevice->DrawPrimitiveUP(D3DPT_POINTLIST, &MouseVertices[0],          MouseVertices.size(), sizeof(CUSTOMVERTEX));    };};


Is not a perfect code, but in my humble opinion, I think MJP meant something like this. where you do not need to worry about passing around the variables

P.S To MJP, I am sorry if I misinterpret your point, please don't bash me up.
Thanks a lot for the reply, littlekid! :)

As it happens, I've been working towards this tonight and what you have there is pretty much what I've been aiming at. I appreciate the post as it makes me feel as though I'm heading more in the right direction. This part was especially useful and it's put to bed some other questions I had:

void Draw(){	g_pd3dDevice->DrawPrimitiveUP(D3DPT_POINTLIST, &MouseVertices[0],		MouseVertices.size(), sizeof(CUSTOMVERTEX));};


I've also started work on a text-based RPG, purely from the standpoint of flexing my C++ skills (or lack of) as I think some of my questions have come simply down to my lack of coding time (which has been unavoidable, sadly).

I've managed to get quite a bit done tonight for both the DirectX 9 application re-write and a good little framework for the RPG (even managed a reasonable design doc + story). Fingers crossed I can get something up and running in a few days.

Thanks again for the post - I feel like a guy on a mission since the help I've received!
Quote:Original post by littlekid
Hi, to latest post by OP, you might want to follow MJP advice to move towards making classes to encapsulate the different functions. This can reduce the passing of the variables arounds from your Update()/Input()/Render() functions

of course for a small program, it has no harm to declare the CVertices at global scope. However to move away from global variables, you can do:

*** Source Snippet Removed ***

Is not a perfect code, but in my humble opinion, I think MJP meant something like this. where you do not need to worry about passing around the variables

P.S To MJP, I am sorry if I misinterpret your point, please don't bash me up.


I'm not 100% on this, but I'm pretty sure that doing that is not good practice. While in general you can assume that std:vector stores it's elements in an array, I don't believe this is actually guaranteed in the spec. Therefore I don't think it's actually safe to send a pointer to the first element to the DrawPrimitiveUP function, unless you can guarantee that the reference returned by std:vector[] is a reference to an element in a C array. But again, I'm not 100% on this.

std::vector is guaranteed to store its data as a contiguous block (i.e. array), however it's allowed to move it around iff you mutate the vector.
i.e. iterators are invalidated on inserts or removes.
(Same goes for std::string.)
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara

This topic is closed to new replies.

Advertisement