Virtual Functions Woes

Started by
6 comments, last by LonelyTower 20 years, 1 month ago
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.
"Magic makes the world go round." - Erasmus, Quest For Glory I
Advertisement
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
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]
"Magic makes the world go round." - Erasmus, Quest For Glory I
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);} 
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.
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.
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.
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!


"Magic makes the world go round." - Erasmus, Quest For Glory I

This topic is closed to new replies.

Advertisement