Advertisement

Renderer design

Started by November 19, 2002 04:38 AM
8 comments, last by aker_jus 22 years, 3 months ago
I am building a simple renderer for my engine, so all rendering code lies there. I do not want to get into scenegraphs and such, I am trying to make an easy to build but fast renderer. This is how I am thinking of it: The Renderer class, where you can pass an array of vertices for use with the vertex array. I will be able to specify NormalPointer, TexcoordPointers, VertexPointers, etc. A function called Render would draw the vertices. It sounds simple and powerful to me. The mesh class will have an instance of the renderer, just to keep it simple. For example, I load the model, I put all the vertices into one big array, and pass that array to the Renderer class. when I need to render my mesh, I use renderer.Render(); WOuld this work? Cause even if it is simple, it needs lots of work and studying before I have something working. Any suggestions that would make it better? What I am trying to do here is avoid something that my previous engine had. Lots of glVertex3f calls (and DL, ok, but no VA), they were everywhere in the engine! Im trying to make it more compact.. Yopu get the idea.
GraphicsWare|RenderTechhttp://www.graphicsware.com3D Graphics & Solutions
I have put all my loading/unloading and rendering code + mesh data in the same class.

It''s shit simple to use.
Advertisement
consider using an object (class) to represent a vertex array, and another to represent an index array. With variations of both for static and dynamic data. This is what I do (I also use a very complex group of renderer objects, scene graphs, etc), but this makes furture things very, very easy... eg, converting to D3D is a good example, optimizations, etc.
(each of these vb''s, for example, may have copyIn, request, release, finalize, render functions, etc, all of which check to make sure nothing is going to go a miss)

for the vertex array, I use a template, so I can use different combinations of tex coord sets, colours, normals, etc. This keeps all the data in the same array, and gives an extra ''black box'' level to how it all works behind the scenes, which is very important, it also helps for optimizations.

The golden rule I''ve found, in my experience, is to have the renderer handle state changes internally. Ie, you can pass on a set of parametres with your vertex array, ie, lighting enabled, depth writing, etc, and the renderer can change these if they differ to the current state, But DO NOT have renderer->enableLighting(), etc, doing this, as your project gets more complex (and this is the whole idea of a renderer class, to accomidate increasing complexity) things will fall apart enormously. I''ve done this in the past, and my project has become an absolute and utter mess because of this. I''ve learnt the hard way.

This is how I''ve gone about doing things, and I can safly say that it''s payed off hugly for me.. (I''m not saying do this, I''m just saying this has worked for me)

<-- smile :-)

Project-X
Thank you for you reply, I will follow the ''Renderer'' plan! However, I am newbie when comes to VA, so I would need a simple tutorial, do you have one? I managed to render a cube, but that''s as far I can go with VA yet.

My renderer class is like this:
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
class CRenderer
{
public:
CRenderer();
void PassVertices(float *vertices, int *indices);
void Render(PrimType PT);
void Release();

private:
float *vertices;
int *indices;
UINT size;
};
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Do you think it is good? PassVertices uses glVertexPointer (future support will include glNormalPointer and others. Render makes a call to glDrawElements.
GraphicsWare|RenderTechhttp://www.graphicsware.com3D Graphics & Solutions
Yeah, the "renderer" idea sounds good, but remember, instantiating a different renderer per mesh/object is gonna be bad. Plus, if more than one object use the same texture, then it might result in a lot of texture thrashing.

The best way I can think of is, to have a set of "buckets", each of which can store the vertices and indices. These "buckets" are made based on the textureID. That way, the scene makes PassVertices calls, where PassVertices creates a new "bucket" if no faces are found that use that textureID. Otherwise it adds those faces to an existing "bucket".

While rendering, the renderer makes one call to glBindTexture and glDrawElements per "bucket", and since the bucket is sorted by texture, there is no texture thrashing.....this is a lot faster.

Each mesh/object will need pointers to global renderer AND texturelibrary instances. The texturelibrary will make sure that only one texture is uploaded irrespective of how many meshes/objects use that texture file. This is very important, since if lots of texture IDs are generated for the same texture file, having "buckets" per texture ID is useless.....u might as well use glBindTexture per face

Hope this helps.
(^-^)
That hepled a lot man. Thanks.
Currently, my Texture class is a erm.. a class. I should make it a singleton, right? That way I can easier manage my textures and use taht bucket you say.
GraphicsWare|RenderTechhttp://www.graphicsware.com3D Graphics & Solutions
Advertisement
Close, but not quite

You should always think carefully before using the singleton pattern... is there a compelling reason to only ever have one texture? No... in fact, your game would look very sucky with only one texture

I think what you meant was perhaps a single TextureManager class (implemented as a singleton if you like that sort of thing). Texture should definitely be a class in its own right... a Texture object can contain all the information pertinent to a texture (dimensions, colour depth, texture ID for association with a 3D API, etc). The TextureManager class could be responsible for (as the name suggests) managing the lifetime of Texture objects within your system (both creation and deletion).

The way I would tend to set it up is to have the TextureManager class load a requested texture file, and hand back a reference to the created Texture object (which is kept internal to the manager)... this way, if the manager finds that you are requesting a texture which has already been loaded, it can just hand back a reference to the existing Texture object.

In fact, I would take this approach with any resources that need to be loaded from files (Models/Meshes, Sounds, etc).
Handy hint : an STL std:map is ideal for this, as you can store the resource objects associated with a key (the filename).
Handy hint 2 : you will need to implement reference counting on these managed resource objects, so that when another object is finished with them, the resource object is not inadvertantly deleted when another object may still need it.

And there's my 2 cents about that

NOTE: some would disagree with this approach, saying that it presents a God-class problem, but I feel this (resource management) is a situation where a God-class may actually be the best approach in terms of efficiency (i.e. not duplicating expensive resources such as texture, model, sound, etc data)

[edited by - Bad Monkey on November 25, 2002 6:41:09 AM]
That is what I meant, a tetxure manager in a singleton

My old texture manager was very low on features, so I will propably add more to this one.
Thanks for everything!
GraphicsWare|RenderTechhttp://www.graphicsware.com3D Graphics & Solutions
Yeah....Bad Monkey is close to what I meant.

TextureLibrary.LoadTexture(aFilename) returns an integer that is an index into the textures stored in it. Each of the textures is a class (OOP rocks!).

So if u try to load Data/Textures/blocks1.jpg, the texture library will search and see if the texture is already loaded (each texture class can have a public member initialized with the filename after loading). If it is not present, it will load it. Otherwise, it will simply return the index where the texture has (already) been loaded.

Of course, after loading all the textures, u will have to loop through all the faces and then change/assign the Face.TextureID to the one returned by TextureLibrary.LoadTexture(aFilename).

The face structure sorta looks like this

Face = record
TextureID: integer; // Index into texture library
// rest of the info here...
end;

So the bucket becomes

Bucket = record
TextureID: integer; // Again, into the texture library
Faces : ListOfFaces; // List of faces to render
end;

Then, the AddFace routine does this

1. Go through the current buckets and see if the Face.TextureID is already present in a Bucket.TextureID.
2. If yes, then add this face to that bucket.
3. If not, then create a new bucket,
3a. Assign Face.TextureID to thatBucket.TextureID
3b. Add that face to the faceList of that bucket.

Then, while rendering, u do that following

1. go through all buckets
Bind that buckets texture (and make whatever state changes are necessary)
2. Render all the faces in that bucket with a single glDrawElements call (this is most often the best driver path to take)
and so on....

Sorry about the Pascal-like syntax guys. I do all my engine development in Delphi....looks a lot like pseudo code anyway...

Regs,
HellSpawn
(^-^)
Yea, I thought about this before going to bed, I have the plan to add it today and it is very easy cuase I have structured the TextureManager to be able to do that.

So, thank you all, I will keep that approach of Texture + Renderer and I hope my engine will be fast!
GraphicsWare|RenderTechhttp://www.graphicsware.com3D Graphics & Solutions

This topic is closed to new replies.

Advertisement