Sign in to follow this  

Hierarchy of graphical engine. Problem.

This topic is 3842 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

Hello again ppl :) First of all I'm using SDL lib for graphics and C++ as my main language. I'll try to explain my problem as much easy as I can. What I have now: I have class CObject. Class that have some object info (like edges, vertices) and methods to work with this object like Drawing. Here is how Draw function looks like:
void CObject::Draw(SDL_Surface* screen, int x1, int y1, int x2, int y2, int color)
{
 some calculations
 Draw_Line(screen, x1, y1, x2, y2, color);
}



(Draw_Line() is SDL_draw lib function) As you can see I pass pointer to screen in order to draw on it. Now I want to do this: I want to wrap everything that connected to graphics into new class: CGraphics and one of his methods is DrawLine that will look like:
void CGraphics::DrawLine(int x1, int y1, int x2, int y2, int color)
{
 Draw_Line(this->screen, x1 ,y1, x2, y2, color);
}



Now I need some way to access to this function from my CObject class. And here comes the problem, I have no idea how to access from CObject to methods inside CGraphics. The only solution I can see is to give CObject::Draw method reference to CGraphics class and the CObject::Draw will look like:
void CObject::Draw(CGraphics& graphics, int x1, int y1, int x2, int y2, int color);



But Is this the correct way to work with graphic/game engine? Do I need to pass reference to graphics system to every class that need to draw something? I hope You understood my question and will be able to help me. Thanks a lot :) [Edited by - s.kwee on June 8, 2007 7:04:01 AM]

Share this post


Link to post
Share on other sites
Quote:

Do I need to pass reference to graphics system to every class that need to draw something?

Basically, yes.

This is not as bad as it sounds, however, provided to also take the necessary steps to ensure not everything needs to know how to draw something. In practice you don't need to pass a lot of parameters around.

Share this post


Link to post
Share on other sites
Thanks for the answer :)

So you want to tell me that commercial game/graphics engines work by this system too? I mean if I use X engine with my program and i need to draw something from Y class I pass reference of X to Y?

It sounds slow.

I just want to know what is the best way to do it, maybe C++ have some features I don't know about.

I hope for more answers that supports jpetrie words, or another solutions.

Thanks a lot again.

Share this post


Link to post
Share on other sites
Quote:

It sounds slow.

It's nowhere near slow. Concerns about the cost of passing parameters are an extreme micro-optimization. It is impossible to alleviate the cost of parameter passing beyond a certain point, as when you get into performance characteristics at that scale (amazingly small), C++ is far to high level to provide deterministic guarantees; you need to know far too much about the host environment to have a meaningful discussion on the subject, and it's so environment-specific that it isn't worth worrying about because it will operate in an entirely different fashion in another environment.

Anyway, as I suggested, the actual amount of passing things around you do is fairly easy to minimize, and it helps reduce dependencies and increase the visibility of required dependencies. Often people feel that passing parameters around will induce "parameter bloat" involving the passing of lots of arguments to lots of functions; this is typically because the underlying design they've been using is broken, and requires or assumes that subsystems are globally accessible from everywhere (this is a bad idea). By fixing that larger issue, the issue of parameter bloat becomes almost irrelevant.

First consider the interface of your renderer. In your game, you'll want to initialize a renderer with various useful settings (perhaps including things like screen size, fullscreen flags, et cetera). Every "tick" of your main game loop, you'll want to (a) process input, (b) process game logic, and (c) draw current game state. (c) is all the renderer is involved with.

Now we think about what is actually involved in (c). At the highest level, it's quite simple: (a) tell the renderer about all the things we wish to draw, and (b) actually draw them. There are two ways to go about this. The first is to use a method like Renderer::DrawObject(const Object &o), and the second is to use a method like Object::Draw(). Personally, I prefer the first form; your design seems to be tending towards the second, however.

In order to actually implement Object::Draw(), however, the object's internals will still need access to the renderer, which in your case seems to hold all the useful SDL objects. You could specify the dependency explicitly via Object::Draw(Renderer &renderer), or, you could make the Object take (and store) a pointer to the renderer when it is constructed. If the object stores its renderer, it does not need to accept the renderer as a parameter in the draw function. The parameter only thus needs to be passed once.

(The other option, where "Object" only describes the rendering but does not actually do it, does not require parameter passing at all; however, it is a more complex system that might be overkill for your needs, which seem simple; also, it's better to make refactoring phases smaller and changing to such a radically different system would be a large, sweeping change that might break a lot of your code, so you should move towards that goal in tiny steps if its something you are interested in).

Share this post


Link to post
Share on other sites
Quote:
Original post by s.kwee
Thanks for the answer :)

So you want to tell me that commercial game/graphics engines work by this system too? I mean if I use X engine with my program and i need to draw something from Y class I pass reference of X to Y?

It sounds slow.

I just want to know what is the best way to do it, maybe C++ have some features I don't know about.

I hope for more answers that supports jpetrie words, or another solutions.

Thanks a lot again.

If you want good OO C++ code then he's right. I'd suggest that you follow that advice.

Share this post


Link to post
Share on other sites
First of all thanks for the detailed post!!

Second:
Quote:

Now we think about what is actually involved in (c). At the highest level, it's quite simple: (a) tell the renderer about all the things we wish to draw, and (b) actually draw them. There are two ways to go about this. The first is to use a method like Renderer::DrawObject(const Object &o), and the second is to use a method like Object::Draw(). Personally, I prefer the first form; your design seems to be tending towards the second, however.

I event didn't think about the first form, it sound more like a real engine that draw objects instead of the second form when object draw himself by using Renderer.
But in order to use the first form I need a basic class Object, the parent of all other classes that will be derived from it.
It may create new problem when some of the object will be abstract SDL_Surface (pictures actually, or fonts), the other object will be just a list of coordinates that need to be drawn and connected with lines. So here actually I can add another methods to Rendered that will draw this objects.

I'm still open to suggestion although I think I'll use the fist form that jpetrie presented.

Share this post


Link to post
Share on other sites
Quote:

But in order to use the first form I need a basic class Object, the parent of all other classes that will be derived from it.

Eh, no you don't. In fact, in the case of extreme simplicity, you don't even need a class for this -- a enum might do.

The system I use is for 3D objects. It consists of a RenderInstance object that is constructed by the game-level code; this object essentially contains a bunch of information about which source data to use for the rendering (the mesh, or whatnot), which transforms the game would like to apply to the source data (to position it in the world, et cetera), which visual features or effects should be applied ("software-skinning normal-mapped"), and any properties related to those effects (such as which texture to use for the normal map).

RenderInstance objects are submitted into a rendering queue, where a bunch of junk unrelated to your situation happens. At some point in that pipeline, the instance is examined by a MethodMapper object, which uses the instance information to assign that instance to a particular RenderMethod, which implements the actual rendering via whatever means are available (software deformation, shaders, et cetera).

In your case, you don't really need inheritance. It sounds like you are doing mostly 2D stuff; all of your 2D primitives can be fundamentally represented by a bunch of points, optionally a texture, and an enum saying what those points represent. The same data for each primitive, all that changes is the values. Use the named-constructor idiom:

class RenderRequest2D
// represents a request to the renderer to draw a certain 2D shape
{
public:
enum PrimitiveType
{
PT_POINT,
PT_LINE,
PT_QUAD,
//...
};

static RenderRequest2D CreatePointRequest(...) { return RenderRequest2D(PT_POINT,...); }
static RenderRequest2D CreateLineRequest(...) { return RenderRequest2D(PT_LINE,...); }
static RenderRequest2D CreateQuadRequest(...) { return RenderRequest2D(PT_QUADS,...); }

private:
RenderRequest2D(PrimitiveType type,const std::vector<Vertex2D> &points);
};


The ellipses here are supposed to be the actual data required, not literal ... tokens (because the ... construct is evil in C++). So CreatePointRequest() might take data for a single point, while CreateQuadRequest() might take data for four points, on so on.

Type-switching (ugly) or some form of multiple-dispatch (better) could be used within the renderer to select which method to actually use to draw the requested geometry. You avoid unnecessary (and probably incorrect) inheritance in this fashion; this makes your code more maintainable.

Share this post


Link to post
Share on other sites

This topic is 3842 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.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this