Archived

This topic is now archived and is closed to further replies.

Virtual Functions Woes

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

As an experiment, I am trying to build a generic 2d graphics library. One of the goals I have in mind is to be able to use different implementation, ranging from SDL to DirectDraw. The "proper" way I have been taught is to use abstract base classes, so I define one:
:
class IGraphics
{
virtual void ~IGraphics() = 0 {};
virtual void BeginRender() = 0;
virtual void EndRender() = 0;
.
.
.
}
 
So far so good, but I come to a problem with the BlitBitmap function. Now, SDL, DirectDraw, OpenGL and Allegro uses different data types for blitting images. I cannot do this:
virtual void Blit() = 0;
 
''cos I need to define the image to blit for the function, but different API uses different types (surfaces for SDL, textures for D3D, and I forgot what Allergo uses) And here''s another problem - I define a structure to store the video mode settings, the bytes per pixel and the resolution - as different API keeps track of different stuff (SDL keeps track of a few variables compared to DX), I would need to define different structures. This would not work
virtual void GetSetting() const = 0;
 
So all right, is all this talk about inheritance and proper object modelling and generic interfaces just pie in the sky? Any suggestions? Many thanks in advance.

Share this post


Link to post
Share on other sites
in the abstract base class put all the variables that they have in common such as resolution and bpp and the like

in the derived classes put the variables specific to that implementation such as texture for D3D, surface for SDL and BITMAP for Allegro

now when you override the Blit function in the derived classes you can do it using the variables specific for that implementation.

get it ?


"A soldier is a part of the 1% of the population that keeps the other 99% free" - Lt. Colonel Todd, 1/38th Infantry, Ft. Benning, GA

Share this post


Link to post
Share on other sites
So are you suggesting a inheritiance chain something like this?


IGraphics
-> ISDL, where virtual void ISDL::Blit(surface * ) = 0 is defined
-> CSDL_Impl, where void CSDL_Impl::Blit(surface *) is implemented.
-> IDX, where virtual void IDX::Blit(ID3DTEXTURE8 *) is defined
-> CDX_Impl, where void CDX_Impl::Blit(ID3DTEXTURE8 *) is implemented?


Or


IGraphics
-> CSDL_Impl, where void CSDL_Impl::Blit(surface *) is implemented
-> CDX_Impl, where void CSDL_Impl::Blit(ID3DTEXTURE8 *) is implemented





[edited by - LonelyTower on March 20, 2004 9:00:06 AM]

Share this post


Link to post
Share on other sites
quote:

So far so good, but I come to a problem with the BlitBitmap function. Now, SDL, DirectDraw, OpenGL and Allegro uses different data types for blitting images. I cannot do this:


virtual void Blit() = 0;


''cos I need to define the image to blit for the function, but different API uses different types (surfaces for SDL, textures for D3D, and I forgot what Allergo uses)



You''ll have to define ISurface, parallel to IGraphics. The problem is ensuring you don''t mix ISurface for SDL with IGraphics for Allegro, etc. If Blit() recieves the surface to be blitted, that surface would be just an ISurface, and not necesarily of the type IGraphics expects. You''ll have to dynamic_cast the pointer to make sure.

The alternative is to have Blit() in ISurface, not in IGraphics. You would need a pointer from the specific ISurface to the right IGraphics.


class ISurface {
virtual void blit() = 0;
};

class IGraphics {
virtual ISurface* createSurface(const char* path) = 0;
...
};

class SDLSurface {
SDLGraphics* graphics;
virtual void blit();
};

class SDLGraphics {
virtual SDLSurface* createSurface(const char* path); // override
void blit(SDLSurface*);
};

SDLSurface* SDLGraphics::createSurface(const char* path)
{
return new SDLSurface(path, this);
}

SDLSurface::SDLSurface(const char* path, SDLGraphics g) :
graphics(g)
{
...
}

void SDLSurface::blit()
{
grapics.blit(this);
}

Share this post


Link to post
Share on other sites
In addition to the abstract base class, I also have a couple of API-independent structures, such as ISurface, ITexture, etc. Then, when I''ve decided which graphics API to use, I have specific classes such as CD3DSurface and CD3DTexture that I can use, and these are used by the derived CD3DGraphics class. Unfortunately, using two APIs at once poses a problem to my system, because all the renderer knows about a CD3DSurface is that it has an ISurface interface, which may not be enough information for a COGLGraphics to try and render it. I''m currently working on restructuring my graphics classes to deal with problems like that.

So far, I''ve come up with putting every setting that you could need, since it''s good to keep them around anyway. For instance, if Allegro didn''t need you to pick a device to render to, while Direct3D did, then I would save the information anyway.

Share this post


Link to post
Share on other sites
quote:
Original post by Br1
quote:

So far so good, but I come to a problem with the BlitBitmap function. Now, SDL, DirectDraw, OpenGL and Allegro uses different data types for blitting images. I cannot do this:


virtual void Blit() = 0;


''cos I need to define the image to blit for the function, but different API uses different types (surfaces for SDL, textures for D3D, and I forgot what Allergo uses)



You''ll have to define ISurface, parallel to IGraphics. The problem is ensuring you don''t mix ISurface for SDL with IGraphics for Allegro, etc. If Blit() recieves the surface to be blitted, that surface would be just an ISurface, and not necesarily of the type IGraphics expects. You''ll have to dynamic_cast the pointer to make sure.


Personally, I try to keep as much information in the base class as possible. For instance, if the textures reside in memory, I have the ISurface class keep track of manipulating that memory. With enough information, any graphics API can render / use the object, with its specific overrides (e.g. CD3DSurface) holding extra information that can help it render faster, such as where in VRAM D3D is caching the data. Unfortunately, the problem you described still exists in cases like this, and the way I''ve gotten around it thus far is to make virtual create functions in the IGraphics class, so you can only create an object once you know what API you''re using.
class IGraphics {
public:
virtual ISurface* IGraphics::CreateSurface() = 0;
};
class CD3DGraphics : public IGraphics {
public:
ISurface* CD3DGraphics::CreateSurface() {
return new CD3DSurface();
}
};
ISurface* NewSurface = D3DGraphics.CreateSurface(); 
This way, you can be sure that you have surfaces created that can work with the graphics API, and avoid usage of dynamic_cast<>. Again, I''m unsure as to how this would apply in cases where you use two graphics APIs.

Share this post


Link to post
Share on other sites
quote:
Personally, I try to keep as much information in the base class as possible. For instance, if the textures reside in memory, I have the ISurface class keep track of manipulating that memory. With enough information, any graphics API can render / use the object, with its specific overrides (e.g. CD3DSurface) holding extra information that can help it render faster, such as where in VRAM D3D is caching the data.

That''s a great idea! How about eliminating ISurface altogether and having just Surface:

struct Surface {
String path;
Color transparent;
Rect subSurface; //if you don''t want the entire image
};

struct IGraphics {
virtual void blit(Surface s);
};

class SDLGraphics : public IGraphics {
map theMap;
void blit(Surface s) {
iterator it = theMap.find(s);
if (it == theMap.end()) {
it = theMap.insert(make_pair(s, load(s)));
}
blit(*it.second);
};

quote:
Unfortunately, the problem you described still exists in cases like this, and the way I''ve gotten around it thus far is to make virtual create functions in the IGraphics class, so you can only create an object once you know what API you''re using.
class IGraphics {
public:
virtual ISurface* IGraphics::CreateSurface() = 0;
};
class CD3DGraphics : public IGraphics {
public:
ISurface* CD3DGraphics::CreateSurface() {
return new CD3DSurface();
}
};
ISurface* NewSurface = D3DGraphics.CreateSurface();  
This way, you can be sure that you have surfaces created that can work with the graphics API, and avoid usage of dynamic_cast<>. Again, I''m unsure as to how this would apply in cases where you use two graphics APIs.

Hmm, that was my previous idea also. Maybe it wasn''t clear enough because I forgot to inherit SDLGraphics from IGraphics? :-P. You have to cast anyway. If you are easonably sure, use static_cast, but the overhead of dynamic_cast won''t be noticeable.

Share this post


Link to post
Share on other sites
quote:
Original post by Br1

...snipped...


struct Surface {
String path;
Color transparent;
Rect subSurface; //if you don''t want the entire image
};

struct IGraphics {
virtual void blit(Surface s);
};

class SDLGraphics : public IGraphics {
map theMap;
void blit(Surface s) {
iterator it = theMap.find(s);
if (it == theMap.end()) {
it = theMap.insert(make_pair(s, load(s)));
}
blit(*it.second);
};

quote:
Unfortunately, the problem you described still exists in cases like this, and the way I''ve gotten around it thus far is to make virtual create functions in the IGraphics class, so you can only create an object once you know what API you''re using.
class IGraphics {
public:
virtual ISurface* IGraphics::CreateSurface() = 0;
};
class CD3DGraphics : public IGraphics {
public:
ISurface* CD3DGraphics::CreateSurface() {
return new CD3DSurface();
}
};




This is a superb idea, but let say we want a one-time blit function without the client messing with any map?

Of course, we can always provide 2 blit function, one for bitmaps already loaded into the map, and one that is loaded externally by the client. It''s the latter I am thinking about more.

It seems that once you flirt with polymorphism, you have to marry it - now it seems that everything must be encapsulated within an object and must have an ABC!


Share this post


Link to post
Share on other sites