OO, engine, rendering, who-what-where

Started by
7 comments, last by outRider 22 years, 3 months ago
I know this has been brought up in the forums umpteen times, and I''ve searched through a lot of threads, but I''m still not clear on a few things. As it stands I have a CRenderer class, with the pure virtual functions InitRenderer, Shutdown, ClearBuffer, FlipBuffer. I have two classes that inherit from this class, one OpenGL, on D3D8, and they fill in the implementation. Here''s the hitch. Lets say I have a CFont class, I''ve read enough to know that there are more benefits to having the Renderer class handle the drawing specifics, since I''d have to create specific inherited classes of CFont for D3D and OGL so that they could draw themselves. Here''s what I''m stuck on: A) Should the Renderer class have lowlevel methods that map closer to the API (i.e. ClearBuffer, {Start/End}DisplayList, BindTexture) and then have CFont->draw use those methods in the renderer, thereby still not touching the specific API? B) Or maybe the rendere should have highlevel methods like DrawFont that would be implemented in the best way with respect to each specific API? The thing with that is that then CFont->create wouldn''t work out, because then that would have to go in the renderer as well, which leaves the CFont class with little or no methods. With the other method (A), CFont create could just use the lowlevel funcs to build itself then draw itself... Of course then I''d have to map almost 1:1 with each API, where as with highlevel methods it would be much simpler, CFont->draw could be totally different on each API. Both these methods have advantages, but is there a better approach? I honestly didn''t come across any hard statements about how to seperate the rendering class from the other graphical elements. I''d appreciate any insight anyone could give. ------------ - outRider -
Advertisement
I''m using basic Image fonts so i''ve just implimented option 1. But if need be you could create an abstract CFont Class which is a singleton and then create a class from this according to which render you are using. That way all you need to do is call CFont::GetClass or some thing and off you go...

Best of both worlds?

-DigitalBlur
DigitalBlur
A very good reference for engine-architecture can be found on www.radonlabs.de , it tries to do something like the following :

1) All API functionality is wrapped, this means renderstates, vertex-buffers, index-buffers, etc.
2) An API independent interface for these classes is provided, thus you can use the same code for both API''s.
3) The initialization of the derived api-specific classes is done using a factory-class.
4) The only problem is to define API-independent interfaces which work quite well.
Graphix Coding @Skullpture Entertainmenthttp://www.skullpture.de
I''ve looked at that site. I couldn''t find the part mentioning what you said, but from you said I still don''t see how they handled that problem. Wrapping the API(s) with lowlevel funcs would allow the objects to draw themselves, but that doesn''t get me away from the objects drawing themselves and having to know about the screen and the renderer, etc. The only other alternative would be to have the renderer include highlevel funcs to draw the objects, but then the renderer would also have to have a func to create the objects, since for example a font class would best be implemented with textured quads in a display list for each char in OGL, and completely differently in ddraw, gdi, d3d, etc. That would leave some classes with no real member funcs at all... and that doesnt sound right to me either.

------------
- outRider -
What I did for my rendering engine (with a changeable backend) is that I made an IRender and an IViewport class, the viewport class controls the window that the rendering class draws to, it has:
Init, Shut, Minimize, Maximize, Restore, Reset, SetMode (sets the mode with a pointer to a struture), SetModeXY (sets a mode by specifying the width, height etc), GetMode, FindMode, ListModes

And the renderer class has:
Init, Shut, SetCamera, SetSurface, PushTriangles, BeginFrame, EndFrame, GetCamera, GetInfo, ScreenShot, UploadTexture, DeleteTexture

Which means that I just upload the basics to the renderer, and then push triangles to it in a simplified manner.

The camera is an object that specifies the aspect ratio, transformation and perspective matrix, the locatrion of the camera, and other helpful things that are not only used in the renderer.

The surface is a structure that holds all the information that is used in rendering a surface - a bunch of triangles - such as deformation information, colour, texture, blending passes, lightmap, etc. This is *very* similar to quake3''s shaders.

I use the BeginFrame and EndFrame functions to actually clear the frame buffer and do the specific flipping and cleaning etc. This allows me to specify different things for each API. I think these would be equivalent to your ClearBuffer and FlipBuffer functions, except that mine could do a lot more.

So for my choice I went with the minimalist approach - I didn''t want to create another thin API - but instead make a nice and simple render class(es) so that other developnment is faster. And as for your example I have my own Font class but I use it to generate arrays of vertecies, texture coordinates and triangle indicies which I draw with the PushTriangles function.

Dæmin
(Dominik Grabiec)
sdgrab@eisa.net.au
Daemin(Dominik Grabiec)
outRider :

You can find the source code here :

Win : http://prdownloads.sourceforge.net/nebuladevice/nebula-2001-09-30.exe
Linux : http://prdownloads.sourceforge.net/nebuladevice/nebula-src-2001-09-30.zip

Wrapping up a 3d- and a 2d-API using the same interface is nearly impossible. Forget about that, but when using OpenGL and D3D, this is relatively easy. And for drawing stuff, use vertex-buffers and index-buffers in any case. For your font-example : You have one factory-class initializing the objects :

class CFactory{public :    IIndexBuffer* CreateIndexBuffer();    IVertexBuffer* CreateVertexBuffer();    ...}; 


The Vertex-Buffer class for example could look like this :

class IVertexBuffer{public :    virtual void Init(...) = 0;    virtual void Release(...) = 0;};class CVertexBufferD3D : public IVertexBuffer{public :    virtual void Init(...);    virtual void Release(...);};class CVertexBufferGL : public IVertexBuffer{public :    virtual void Init(...);    virtual void Release(...);}; 


So only the factory class has to know about the API used. In your font-class, you create the vertex-buffer using the factory-class and use the interface providede by IVertexBuffer. These classes know about the API that is known, but your font-class does not and does not have to. This makes things much cleaner than having an inherited class for every little thing (i.e. CFontGL and CFontD3D).
Graphix Coding @Skullpture Entertainmenthttp://www.skullpture.de
Hmm... that was already what I was doing in trying to support both D3D and GL. But, it seems to me that it would be simpler to avoid the factory class having methods that should belong to another class. I''d be inclined to have CFactory in your example be IFont, then have CFontGL, and CFontD3D fill in the implementation. Is that what you were trying to illustrate? I don''t see the advantage of another class on top of the interface.

------------
- outRider -
quote:Original post by outRider
Hmm... that was already what I was doing in trying to support both D3D and GL. But, it seems to me that it would be simpler to avoid the factory class having methods that should belong to another class. I''d be inclined to have CFactory in your example be IFont, then have CFontGL, and CFontD3D fill in the implementation. Is that what you were trying to illustrate? I don''t see the advantage of another class on top of the interface.

This implies that you will have an explosion of sub-classes as it fails to isolate the render API variant.
Suppose you start supporting OGL2 and D3D7. You make your CFontGL2 and CFontD3D7 concreate classes - this also implies you will make two concrete classes for every drawable class. CAvatarGL2, CAvatarD3D7; CSkyGL2, CSkyD3D7; etc...

Now, suppose you decide you want to add support for D3D8 or D3D9. You have to make a pile of concreate classes, same with OGL3.


If wouldn''t be a bad idea to make a CFontGL and CFontD3D class first. Then heavily refactor them, so that you have one CFont class that uses services provided by the instanced IRender interface. This will ensure the IRender design meets your needs.

Alternatively, you could make a CFont<class TRender> and build concrete classes automatically.
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
quote:
If wouldn't be a bad idea to make a CFontGL and CFontD3D class first. Then heavily refactor them, so that you have one CFont class that uses services provided by the instanced IRender interface. This will ensure the IRender design meets your needs.


I'm sorry I don't understand what you mean by that. The term refactor is unfamiliar to me. Do you mean that IRender should provide functions that wrap basic processes that I would then use in CFont, thereby making CFont not dependent on any API but rather the interface I provide? I've considered that, but then there are some problems where OGL and D3D diverge. How would I handle the display lists that D3D doesn't have? I could create an ambiguous function IRenderer->StartBufferedCommands/EndBufferedCommands or something, fill it in with display lists or CVAs or whatever in GL, and something that might be equivalent in D3D.

[EDIT]
Actually, rereading everything I understand that these wouldn't necessarily have to go into IRenderer, but since a "renderer" isn't really an API, rather the CFactory class would expose certain processes in the API, but I still don't see how to handle the differences in APIs.

------------
- outRider -

Edited by - outRider on December 22, 2001 8:18:11 PM

This topic is closed to new replies.

Advertisement