Jump to content
  • Advertisement
Sign in to follow this  
X-Shiryu

Help about design a Render Class

This topic is 2267 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I redesign some of my classes, and i thinking about:

Current implementation - no polymorphism, just a vector of void* and renderable object type (enum).
What i trying to do - Each renderable know about how renders itself, but implementing itself go to render class.


class Renderable
{
public:
virtual void Draw(Video& video) const = 0;
};

class Sprite : public Renderable
{
public:
virtual void Draw(Video& video) const
{
video.Draw(*this);
}
};

class Shape: public Renderable
{
public:
virtual void Draw(Video& video) const
{
video.Draw(*this);
}
};

class Text: public Renderable
{
public:
virtual void Draw(Video& video) const
{
video.Draw(*this);
}
};

class Video
{
private:
vector<Renderable> renderables;

public:
void DrawAllObjects()
{
for (int i =0; i < this->renderables.size(); i++)
this->renderables.Draw(*this);
}

void Draw(Sprite& sprite)
{
//Specific Direct3D functions to draw a sprite
}

void Draw(Text& text)
{
//Specific Direct3D functions to draw a text
}

void Draw(Shape& shape)
{
//Specific Direct3D functions to draw a shape
}
}


At main we have something like this:
Video call DrawAllObjects
Sprite Draw
but now go back to Video Specific Draw Sprite implementation

This is good for my case?
Now Video knows Sprite and Sprite know Video

The problem is: where to put de implementation it self (calls of D3D functions) Edited by X-Shiryu

Share this post


Link to post
Share on other sites
Advertisement
I don't think it's a good idea to have each object draw itself. A more elegant pipeline would be based purely on data, which would encode how any given object should look.

Then, you can have a single draw function, which will generate the approprite drawing commands based on the transform, geometry, and the material of the object in question.

Share this post


Link to post
Share on other sites
The whole ‘every object can draw itself’ type of thing can seem like a good thing to do I think because a lot of C++ tutorial and books show us that type of architecture in order to demonstrate a language feature. In that sense, it can seem familiar or warm and fuzzy if you like which makes us think it’s a good thing. But I would urge you to consider just how much responsibility you’re putting into the Draw function and how much of that is going to be common to each of your renderable types. Anything common is better off in a common place, outside of draw and anything specific may not be specific to drawing at that point.

Common functionality

All of the object types you have listed, shape, text and sprite are ultimately rendering triangles. That’s what is really drawing here, so I would try and refactor that functionality outside of the draw function and into a common place. Anything more specific to each type of object can remain specific, but bear in mind that whatever is left may not be specific to Draw(). Personally that’s a good thing in my opinion because it will help justify the need for a specific object type, or make such a thing obsolete. We’ll talk about that in a minute anyway.

Really, what I’m suggesting here is that you consider setting up lower level data structures to represent the triangle data and move that outside of renderable. As Goran says, I wouldn’t wrap that in classes I would just present that in plain old data structures. In many cases you aren’t going to be doing much other than rendering that data, so it’s entirely appropriate to have that in a structure, not a class and just pass it to rendering functions. This might also help you decide a little about your rendering architecture – what you describe as you real problem above.

In my case I simply have structures that represent points, line lists, line strips, triangles, triangle fans and triangle strips – what is really being drawn. I have a renderer or renderdevice type class which has two functions (amongst many others) that draw these primitives – one handles indexed primitives and one handles non-indexed primitives. I should add that I really only pass the type and size of primitive with this function call. This means I have other SetVertexBuffer, SetIndexBuffer calls that point my render object to the actual data.

This means I can do something like this

RenderDevice rd;
rd.SetVertexStream( 0, pVertexBuffer ); // where first # is a vertex stream index
rd.SetVertexStream( 1, pNormals );
rd.SetIndexStream( pindexBuffer )
// also things like SetTexture( x, texture), SetShader( shaderprogram ), etc
rd.DrawIndexed( TRIANGLE_LIST, num_triangles ); // where triangle list is an enum


Bear in mind there is some implied knowledge in that example that I’m always passing in two vertex streams. In reality, I also have a SetVertexFormat type of function and I can pass in any amount of vertex streams, some of which have vertex and normal data interleaved.

This maps quite well to a number of differing rendering architectures so it’s quite portable. Can you see however, that I don’t really need a Draw function at that point…I just need a function that pulls out model data from your renderable class. Something like this would work:

pModel = pRenderable->GetModel();
rd.SetVertexFormat ( 1, VERTEXNORMALINTERLEAVED ); // one stream, stream data has both vertex normal interleaved
rd.SetVertexStream( 0, pModel->GetVertexBuffer );
// etc


Renderable/type specific functionality

After I’ve done all the above, what do I need Renderable and various derived types of Renderable for?

The only unique things I might have in the equivalent of your rendering classes at this point would be things very specific to that renderable. That helps to justify a concrete class for that object or not. For example:

sprite.SetXY( x, y );
text.SetXY( x, y );
shape.SetPosition( x, y, z );
text.SetText( “Hello World” );


The four functions collectively represent a function common to two of the objects (SetXY), a function common only to a 3D object (SetPosition) and a function specific to a text object. That would imply an architecture for your renderables as

  • Base class Renderable
  • class Text possibly deriving from Renderable, but possibly also Sprite.
  • A Renderable3D derived from Renderable.
  • A Sprite also derived from Renderable.


    i.e kind of what you have now. Only I would further remove the text class and make this a special texture. Every sprite is going to have a texture or bitmap, so there is common functionality which means in this example a special type of text renderable isn’t justified. I’d probably also remove the sprite class and just have a Renderable2D, which with a SetTexture or SetBitmap function would suffice for both sprites and text.

    The goal – try and avoid the whole ‘everything can draw itself’ thing and minimize your Renderable class hierarchy. Edited by freakchild

Share this post


Link to post
Share on other sites
First thing thanks for feedbacking.
My old implementation is based on Video Draw high level things like Sprite,Text(in my case is justificable because of incapsulation of std::string, and some others few atttributes, like bool shadow) and shape (generic class for render 2d geometric things like triangles,rectangles, without texture applied).

What i undestand of freakchild comments is: there is no more all of that high level classes for handling in each situation, every drawable object no matter what type of it, will be rendered calling all flexible methods of render class like set vertexbuffer, index, pass geometry data. In that case is very flexible to render an object, but the problem is encapsulation:


int main()
{
Sprite my_sprite;
while(application_running)
{
video.Draw(my_sprite);
}
return 0;
}


I will have to switch to this



int main()
{
GenericGeometryClass object;
while(application_running)
{
//Pseudo of drawing object
video.SetVertexBuffer(object);
video.SetIndexBuffer(object);
video.ProcessMatrix(object);
video.SetTexture(object);
video.SetSomeShader(object);
video.DrawIndex(object);
......
}
return 0;
}


or i can back to my old implementation
OBS: there is no Renderable interface class;


class Video
{
enum EnumVideoObect
{
Sprite = 1, Text = 2, Shape = 3.....
}


vector<EnumVideoObject>object_type;
vector<void*>object_data;
void DrawAll
{
for (int i =0; i < this->object_type;i++)
{
if (obhect_type == Sprite)
{
Sprite* sprite = (Sprite*) this->object_data
//Implentation is here setvertexbuffer, index ,etc....
}
else if (obhect_type == Text)
{
.....
}

}
}

}


Maybe i can go back the implementation or try using template?

Share this post


Link to post
Share on other sites
There is not necessarily any encapsulation problem. You don't have to switch to this for example:



int main()
{
GenericGeometryClass object;
while(application_running)
{
//Pseudo of drawing object
video.SetVertexBuffer(object);
video.SetIndexBuffer(object);
video.ProcessMatrix(object);
video.SetTexture(object);
video.SetSomeShader(object);
video.DrawIndex(object);
......
}
return 0;
}



I actually didn't comment on where to put those calls, although really it's a very similar problem and solution.

Something that has a vertex buffer, an index buffer, possibly references to textures, shaders and so on is a Model of some sort. I would create a Model class therefore to at least hold the data.

From there on you can create a function which takes the model as input and makes those calls. If you wish, you could wrap this into a modelrenderer type of class.

There is another solution too. We have suggested you stay away from the old 'each object is responsible for drawing itself' architecture. This problem is largely because the net result is often a hierarchy of objects derived from one abstract base class with a function override in each of the children. This is largely what we are looking to get away from then, but personally I have no issue with there being a Draw() or Render() function in a Model class, providing it isn't virtual or otherwise designed to be overridden unless there is an absolute need.

This might appear like your Draw() function and there are similarities. But I think there's also a big difference between asking every instance and class type to draw itself and simply asking each Model. One is clean and has one render function in one place. The other has render functions in every single class of a hierarchy.

Wherever you do put that function bear in mind you don't need to do this...



if (obhect_type == Sprite)
{
Sprite* sprite = (Sprite*) this->object_data
//Implentation is here setvertexbuffer, index ,etc....
}
else if (obhect_type == Text)
{
.....
}



There is absolutely no need for a render or draw function to know what it is rendering. It really only needs to know that it has been asked to render a vertex buffer, index buffer with references to textures, shaders and so on. If you do wrap all that information into a Model class, then all it needs to know is that is has been asked to render a model...and you'll provide some function like GetVertexBuffer on the Model to get at the data.

Either way, just make the render function flexible enough to render a model that has one or more vertex buffers, potentially an index buffer, 0 or more texture references, a shader and so on. If you make it so that it doesn't have to work with a fixed number of these things it will render anything you throw at it. Edited by freakchild

Share this post


Link to post
Share on other sites
Also...


things like Sprite,Text(in my case is justificable because of incapsulation of std::string, and some others few atttributes, like bool shadow) and shape (generic class for render 2d geometric things like triangles,rectangles, without texture applied).


If you still need this hierarchy for type specific functionality that's fine. All I'm saying is that there is no need to spread rendering functionality throughout the hierarchy with a Draw() function in each class. i.e. rendering functionality does not justify that hierarchy, even if another reason does.

Far better to take all that common functionality and put it into one place. If you don't you're creating a lot of future maintenance work for yourself.

Personally I would rename your base class Renderable to something else. Not sure what, but to me it seems the justification of this hierarchy is something other than rendering - it's more about creating an interface to a specific type of entity. Changing the name is certainly not 100% necessary but I just wouldn't think of those as Renderables.

The Model class above, incidentally could be instantiated as a member of that base class either way:


class Renderable // I'll just use the same name for the purpose of the example
{
private:
Model m_Model;
}



You see, just by creating a Model class as I've described in the posts above and including it in the Renderable class as I just did...all the rendering code is now in one place.

This would still validate some things you would want to do, for example:


class Text : public class Renderable // again same name used for sake of an example
{
void SetText( String text )
{
WriteTextIntoBitmap( this->GetModel()->GetTexture(0)->GetBitmap(), text );
}
}


In this manner you could keep your Text class and all the higher level functionality you said you needed...but it would also work with and encapsulate the lower level functionality. Edited by freakchild

Share this post


Link to post
Share on other sites
I did something similiar what you have proposing to do, but at time i had some issues about Text class, because of some specifics attributes and implementations.

Thinking now, i can solve some problems, for example, implementing SetText for preparing data of model object, and passing that object for Video class to render.

Implementing this way, can solve most of problems: dependency, virtual methods, and separated implementation for each type.
Concentrated implementation i think will more easy to make some 'Sprite' (now treated as Model) Batch by texture.

So now i can have class Model like this.

class Model
{
private:
vector<Position> position; // x y
vector<Position> text_cord; // u v
vector<Color> color;
Texture* texture;

//Or make some Vertex class with each of this attributes and create vector<Vertex> object here
}

class Text
{
private:
Model model;
Font* font; //Font class have pointer for Texture
void SetText( String text )
{
//acesssing object model atributes for preparing data to pass to vertex buffer in future,
//based on each char of text
}

const Model& GetModel(); //to pass to Video
}
//So in my Video (or Render) class everything will be handled like a model
class Video
{
private:
vector<Model*> models; //Reference of all models objects to be rendered
public:
void AddToDraw(const Model& model) //Or simply call this 'Draw' not 'AddToDraw'
{
this->push_back(model);
}

//At end of loop i call
void Render()
{
//1º >> Calculate the size of Vertex e Index Buffer here(i using one BIG dynamic for both) based on size of Models
//check for current size, if necessary recreate Vertex Buffer and Index Buffer with incresed size - One time by Loop

//2º >> Pass geometry data to Vertex and Index Buffer using Lock in both - One time by Loop
//there is some problem locking both at same time, to iterate over models one time, not two separately for each Buffer?

//3º >> The actual code for rendering models: process matrix, check for texture will be used
//Vertex Buffer position (SetStreamSource) and Vertex Declaration, shader etc...

//Clean Model List for next loop
}

}

int main()
{
//Loading model geometry
while(AppRun)
{
Video.Draw(model0);
Video.Draw(model1);
Video.Draw(model2);
Video.Draw(model3);

....

video.Render();
}
}

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!