Skip to main content
gamedev.net
🔒 Locked

The Render Method

Started by Kram 11 replies 2.6k views
Original Post
Kram
Kram
Im trying to learn more about the entire rendering process, and I want to ask a bit more about the Render() method that is typically associated with the WM_PAINT and the like. My render method is as follows:

void Render()
{
	//only render if the device has been setup and is not null
	if( p_d3dDevice == NULL )
        return;

	// Clear the back buffer to white
	p_d3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );

	HRESULT beginHr = p_d3dDevice->BeginScene();

	if( SUCCEEDED( beginHr ) )
    {
		//This is where im going to render the results
		p_d3dDevice->SetStreamSource( 0, g_kt->GetVertexBuffer(), 0, sizeof(CUSTOMVERTEX) );
		p_d3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
		p_d3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 );

        // End the scene
        p_d3dDevice->EndScene();
    }

	p_d3dDevice->Present( NULL, NULL, NULL, NULL );
}


So in this case, the g_kt is an application object that has a VertexBuffer that is build up of a vector of D3DFVF_CUSTOMVERTEX

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)


I certainly understand that there is more than 1 way to skin a cat, but how is a Render() method typically setup? I people usually iterate through all the objects in a 3D space and constantly call SetStreamSource methods, etc... on those objects? Thanks for the help... Mark [Edited by - Kram on March 12, 2008 12:16:42 AM]
Buckeye
Buckeye
Yep, that's pretty much what is done.

If you have several objects with the same vertex format, you can bunch them together preceded by just 1 SetFVF call, but I'm guessing the savings in time is trivial.

If you want, you can set up a couple of routines named, for instance, DrawNear() and DrawFar() and call them in order between the beginscene and endscene pair in the render function.

DrawNear draws objects likely to be closer to the eye and DrawFar draws objects likely to be further away.

Is that what your question was?
Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly. You don't forget how to play when you grow old; you grow old when you forget how to play.
Kram
Kram
Quote:
Original post by Buckeye
Yep, that's pretty much what is done.

If you have several objects with the same vertex format, you can bunch them together preceded by just 1 SetFVF call, but I'm guessing the savings in time is trivial.

If you want, you can set up a couple of routines named, for instance, DrawNear() and DrawFar() and call them in order between the beginscene and endscene pair in the render function.

DrawNear draws objects likely to be closer to the eye and DrawFar draws objects likely to be further away.

Is that what your question was?


Sort of, I think I understand what you mean, and again, I guess its up to the developer.

But with the method calls:

p_d3dDevice->SetStreamSource( 0, g_kt->GetVertexBuffer(), 0, sizeof(CUSTOMVERTEX) );p_d3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );p_d3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 );


These 3 methods look pretty coupled together for the sort of rendering that I would use for a simple triangle.

If you have a far more sophisticated object, like an Orge (for example) which will be more that just a primitive type, you would need far more methods called for the render approach I'm assuming?

If so, then would it be wise to move the Render() method inside each object that you have, which will be given references to the D3D device and such and can do its own determining if it is far away, and so on?

Thanks for the help!
Mark
Buckeye
Buckeye
Quote:

If you have a far more sophisticated object, like an Orge (for example) which will be more that just a primitive type, you would need far more methods called for the render approach I'm assuming?


Why do you say you would have more than one primitive type? I'm not sure what you mean by sophisticated.

Quote:

If so, then would it be wise to move the Render() method inside each object that you have, which will be given references to the D3D device and such and can do its own determining if it is far away, and so on?


If you want to use object-oriented programming, yes, it would be almost mandatory for each object to have its own Render() routine.

If you're concerned with making hundreds of calls to SetStream and DrawPrimitive, and you have hundreds of objects to render, that's how it's done.

I have several apps, each with several hundred meshes comprising thousands of vertices. Each of those several hundred meshes is rendered through the equivalent of a DrawPrimitive call.

As my own preference, I don't generally use objects (in the C++ sense, with their own functions, etc.). That way I don't have to pass a bunch of parameters (devices, textures, locations, etc.).

As an example of rendering some terrain meshes, here's some pseudo-code:
for(int row=0; row < 12; row++) {   for(int col=0; col < 12; col++) {      DrawTerrainMesh(row,col);   }}...void DrawTerrainMesh(row,col){   CalculatePosition(row,col);   if( distance is too far ) return;   SetupTexture(row,col); // m_pDevice->SetTexture(texture for this mesh)   SetupOtherNecessaryParamters();   SetSourceStream;   SetFVF;   mesh->Draw();}


Note: I would really only have to setup the FVF once if all the meshes have the same format (which they do).

I usually use meshes but the principle is the same. And the above calls are only for the terrain. That doesn't include buildings, trees, bullets, missiles, troop movement, etc. Each of those has some sort of Draw call.

Is that getting at your question?
Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly. You don't forget how to play when you grow old; you grow old when you forget how to play.
Kram
Kram
Quote:

Why do you say you would have more than one primitive type? I'm not sure what you mean by sophisticated.


I'm not really sure what I mean, I'm still new to all this and I think I got carried away while typing... ;)

Quote:

If you want to use object-oriented programming, yes, it would be almost mandatory for each object to have its own Render() routine.

If you're concerned with making hundreds of calls to SetStream and DrawPrimitive, and you have hundreds of objects to render, that's how it's done.

I have several apps, each with several hundred meshes comprising thousands of vertices. Each of those several hundred meshes is rendered through the equivalent of a DrawPrimitive call.

As my own preference, I don't generally use objects (in the C++ sense, with their own functions, etc.). That way I don't have to pass a bunch of parameters (devices, textures, locations, etc.).

As an example of rendering some terrain meshes, here's some pseudo-code:
for(int row=0; row < 12; row++) {   for(int col=0; col < 12; col++) {      DrawTerrainMesh(row,col);   }}...void DrawTerrainMesh(row,col){   CalculatePosition(row,col);   if( distance is too far ) return;   SetupTexture(row,col); // m_pDevice->SetTexture(texture for this mesh)   SetupOtherNecessaryParamters();   SetSourceStream;   SetFVF;   mesh->Draw();}


Note: I would really only have to setup the FVF once if all the meshes have the same format (which they do).

I usually use meshes but the principle is the same. And the above calls are only for the terrain. That doesn't include buildings, trees, bullets, missiles, troop movement, etc. Each of those has some sort of Draw call.

Is that getting at your question?


That is a very good answer, thanks!
I was honestly thinking that it would be far more complex, but I guess all we are really dealing with here and HEAPS of primitives (or similar) aren't we?

Using your example, the terrain mesh would simply be a mass of squares and triangles wouldn't it? (Hence the call to DrawPrimitive() or similar)

Basically I'm trying to understand exactly how things work (a huge question I know), but I was always under the impression that models like a gun in a FPS that the user holds, or a model of a tree/house would have to have some complex rendering method that wouldn't call things like DrawPrimitive, but rather far a more sophisticated series of methods...
dmatter
dmatter
Quote:
Original post by Buckeye
Quote:

If so, then would it be wise to move the Render() method inside each object that you have, which will be given references to the D3D device and such and can do its own determining if it is far away, and so on?


If you want to use object-oriented programming, yes, it would be almost mandatory for each object to have its own Render() routine.

I wouldn't go as far as to say that.
If you want to use Java-style OOP then each object would likely have it's own render() member function but it's certainly not a mandatory prerequisite for an OOP approach.

The way I see it, there are 3 general approaches:

1) Entity draws itself
Monster::render();

Advantages: Simple and reliable
Disadvantages: Difficult to optimise redundant state-changes (etc) and leads to graphics code that is spread through many classes

2) Renderer draws an entity, or homogeneous set of entities
Renderer::render(const Monster & monster);
and/or
Renderer::render(const std::deque & monsters);

Advantages: Simple and keeps graphics code all in one place, improved state-change (etc) optimisations in the case with a collection.
Disadvantages: Must have functions in the Renderer for every entity type.

3) Renderer draws an intermediate (entity agnostic) representation, or homogeneous set of representations
Renderer::render(const GeometryChunk & chunk);
and/or
Renderer::render(const std::deque & chunks);

Advantages: Keeps graphics code all in one place, can totally optimise state-changes (etc) in the case with a collection and only one render function is needed.
Disadvantages: More complicated than previous approaches.

A variation on that last approach can be found here, where essentially a state optimising render-queue takes the place of the deque from the example above.


Quote:
As my own preference, I don't generally use objects (in the C++ sense, with their own functions, etc.). That way I don't have to pass a bunch of parameters (devices, textures, locations, etc.).

That's a more C-style strategy, perhaps sprinkled with C++ syntax or constructs. Nothing wrong with it as such, it's just not idiomatic C++.

Quote:
Note: I would really only have to setup the FVF once if all the meshes have the same format (which they do).

Yes, this is exactly the type of thing that's difficult to optimise with the "entity draws itself" method.

Quote:
I usually use meshes but the principle is the same. And the above calls are only for the terrain. That doesn't include buildings, trees, bullets, missiles, troop movement, etc. Each of those has some sort of Draw call.

This is similar to the "renderer draws an entity" method, although the terrain drawing code snippets indicate that this renderer is comprised of C-style free/global functions rather than being a C++ style class.
Buckeye
Buckeye
Quote:

I'm not really sure what I mean..


No problem, Kram. The point is that you're trying to learn from others' experience how things are best (or, at least, adequately) done.

That's why I prefaced some of my remarks with "my own preference." As you surmised and dmatter itemized, there are a lot of ways to skin the rendering cat.

Yes, the terrain meshes mentioned contain buffers of vertices which you would recognize as describing triangles. Those vertices are added to the meshes, just as you added your vertices to your g_kt buffer, using a described vertex structure.

I understand what you mean by sophisticated now. "Complex" objects, or those that just appear to be complicated, are often rendered using just a single buffer of data.

The "complex" part may more likely be the way in which that data is manipulated during the rendering process, rather than the type of Draw call that is made.

You'll get into that eventually, I'm sure. As you want to do more "complex" things with your objects, you'll get into vertex and pixels shaders and separate source streams, etc. For now, you're certainly on the right track.

In the end, however, each object (in the generic sense and C++ sense) will be rendered with the equivalent of a DrawPrimitive call, be it DrawPrimitive, DrawSubset or whatever.
Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly. You don't forget how to play when you grow old; you grow old when you forget how to play.
Rottbott
Rottbott
Here's the method I like:

In the end even the most complex/unusual entity types will boil down to one or more DrawPrimitive calls with associated set calls for shaders/samplers/renderstates/vertices/indices. I have an Entity.Render function which is implemented differently in each class derived from Entity, but which only goes as far as doing the "boiling down", and in the end registers the Set/Draw parameters with a centralised list managed by the renderer.

The final step after traversing the scene hierarchy is to sort the list (by shader/texture and by Z for alpha objects) and then render each object in turn.

That allows a simple recursive walk of the scene but still gives the benefits of sorting, and it keeps the 'common-to-all-entity-types' code such as state filtering together in one place.

If you manage two lists at once and alternate between them it's also a nice easy way to multithread your rendering since the walking/sorting can happen in parallel with the drawing of the previous frame's list.

Edit: This also works really well when you get into multiple passes for things like shadow maps and motion blur by making lists within the lists. Entities can just specify which passes they'd like to register for.
Kram
Kram
Thank you all for your replies, I highly appreciate the help. I certainly have a lot more to learn, but I think that this particular thread will help a lot in the future when my killer game hits the scene...now I just need the idea :)
Kram
Kram
Actually, dmatter, Can I please get you to elaborate on option 3) a bit more?

I not quite sure what you mean and hence why this method would be beneficial...
dmatter
dmatter
Quote:
Original post by Kram
Actually, dmatter, Can I please get you to elaborate on option 3) a bit more?

I not quite sure what you mean and hence why this method would be beneficial...

Certainly, no problem.

The idea is to generalise the graphical representation of all entities and create a construct to be this representation.

If you think about it, all entities have VBs, IBs, textures, shaders, shader-state, blend-states and any other common rendering states you wish to identify. It doesn't matter whether we're dealing with a simple Sphere, a Monster or a LandScape they have these properties in common. This is obvious really, because in the end they all go through the same pipeline.

So if you had Monster::render() and Landscape::render() then both functions would essentially be calling the same code to set the same pipeline, all that differs are just the actual state values.
What we have is a lot of like-code that is grossly duplicated within each and every entity.

To solve this, we refactor the like-code out and into it's own dedicated function within a Renderer class. This class is entirely responsible for calling the (D3D) functions that setup the pipeline state.
Note: By putting this functionality in a completely separate class, rather than a base class of entity, gains us a better separation of an entity and its graphical representation (see the Model-View-Controller pattern).

So, now we have a dedicated function to setup the pipeline state, but how does an entity inform this function of what specific state values it needs to use?
This is where the GeometryChunk comes in (I use this name synonymously with RenderOp elsewhere). This structure simply stores all the pipeline state for any given entity (VBs, texture, and so on). It is (ideally) completely agnostic of the actual entity it describes - it could describe a Monster or it could be a Landscape.

What we have now are the components to the puzzle, all that is needed is a little extra functionality to glue it all together.

Lets look at how a single entity will get rendered:

1) The entity initialises a GeometryChunk (GC) with the pipeline state it wishes to use.
2) This GC is passed as a parameter to the renderer's render function.
3) The renderer sets the pipeline state using the values from the GC.

Now, rendering many entities can be much the same, just we iterate over them one by one [smile].

Right, now at this point we have a nice, clean separation between entities themselves and the rendering of entities.
This separation allows us to add sophistication to the renderer without affecting the entities or even the external interface of the renderer.

Lets look at a new rendering sequence:

1) The entity initialises a GeometryChunk (GC) with the pipeline state it wishes to use.
2) This GC is passed as a parameter to the renderer's render function.
3) The renderer adds the GC to a render-queue.
4) The queue is sorted to minimise all state-changes.
5) The GC's in the queue are iterated one by one and the pipeline state is setup.

See how by separating entities and GC's, and by allowing the renderer to handle all the GC's at once it has allowed the renderer to choose the order in which GC's are setup such that it can optimise the rendering (because setting state is slow, we want to do it as little as often, it will group similar states together).

A slight variation on this is rather than having the render-queue within the renderer, you could add the GC's to the render-queue outside of the renderer and just pass the whole sorted queue to the renderer directly. The difference is slight and unimportant here.

So I hope that gives you a better idea of how the process would work, further questions are welcome of course [smile]
Kram
Kram
Thank you dmatter, that was a brilliant reply. I highly appreciate the level of detail outlined.

This is probably (for serious) the best online forum I have been lucky enough to deal with.

Thanks again, I understand what you are saying and I have always liked implementing a MCV (or even MVP) design pattern, I feel the control of the app is easy to manage.
FoxHunter2
FoxHunter2
The last method dmatter explained is very well described in this book sample with source code and class diagrams. I implemented something very similiar on my own based on this book, and it works really well and is pretty flexible. You should take a look at it :)

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.