Skip to main content
gamedev.net
🔒 Locked

API agnostic render core

Started by Keyframe_ Sep 25, 2005 at 4:11 PM 11 replies 3.1k views
Original Post
Keyframe_
Keyframe_
OK, maybe this has been covered before - but I was hoping for some resources or general opinions.. let's say I wan't to develop a render engine which should be more or less prepared for several platforms - Windows and OSX comes first to mind.. and let's say I wan't to bypass SDL.. Here's what I'm thinking of: - On Windows I'd like to use Direct3D 'driver' of sorts to my API - On OSX I'd like to use OpenGL 'driver' of sorts to my API now, how'd I go about it? Should I first write the interface of my API - function by function of what I need in my engine core and then write implementations of it in 'modules' (drivers of sorts - per needed platform).. encapsulating only raw metal access like drawing primitives, texture handling etc.. and abstracting effects and more high level processes with my API? It would be easy to do if those APIs (Direct3D and OpenGL) weren't so different in philosophy (right hand vs left hand, different shading langs (ok CG could possibly be used as a bypass...) )... but it seems rather complex Thus, Where should I begin? I was looking at great, truly great enginuity stuff pig wrote, but unfortunately - when things got interesting - he stop writing those.. and he didn't elaborate on the philosophy behind this that I'm interested in.. Anyways, if you have some opinions, links, books - whatever about this, I'm all ears! Thanks! PS Scene graph etc.. would be developed independent of this, I'm only talking about pure rendering now... damnit :)
Keyframe_
Keyframe_
ok, just a quick rambling that went through my head now..

if it's rather too complex to make something like this - agnostic API core - I should still make something of an abstraction to the render API, because if I wan't to port the software to another platform, last thing I'd wan't to see is DirectX or OpenGL code interveined within whole of code in the application.. I suspect that the answer probably lies in some of design patterns - question is which one ;)
dyerseve
dyerseve
The way I do it is:

I have a CDirect3D class. And an COpenGL class. Both derived from an abstract CRenderer class. The CRenderer class has stuff like

bool Initialize(int xres, int yres, int bpp, bool fullscreen)=0;

and basically all render states that are common to both opengl and direct3d like:

void EnableZTest()=0;

and matrix stuff like:

void MatrixMultiply(Matrix* v)=0;
void MatrixIndentity()=0;

and texture stuff like:

TextureHandle /*typedef'd to int*/ LoadTexture(string filename)=0;

and finally, I have a CMesh class, with all the geometry. Basically, all the vertex data (colors, normals, multi-texture coords, etc...) in an array of floats, and the indices in a separate array. And then I have a list of batches (primitive type: point list, triange strip, etc..., first index, last index, texture) The handles to textures are in it too. The CRenderer class has:

void MeshInitialize(CMesh* m)=0; //Creates VBO's or vertex buffers and whatnot
void MeshRender(CMesh* m)=0; //Updates vertex buffers (if the m->m_bDynamic flag is set) and draws the damn thing
void MeshDone(CMesh* m)=0; //Erases VBO, etc...

I'm not sure if this is a great way to go about this (suggestions/criticism is good), but it works. I haven't actually gotten around to implementing actual effects, but it looks like effects relying on advanced features on the API will have to implemented via the driver.

I'm thinking you could probably do stuff like render-to-texture in the driver. Then effects based on that would be done in the engine.

As for shaders, I'm planning on using Cg.
silvermace
silvermace
If you're going to be using OpenGL eventually, why not stick with that for windows as well? It will save you alot of pointless work. I have written my rendering engine around OpenGL, and it is still API independant (through the use of interfaces) but I do not hesitate to break this independance if it will limit functionality (which, I assume it will at some stage).

Cheers
-Danu

PS. The concept is not hard, simply provide pure/abstract interfaces and then implement the code using your prefered API

eg.
struct ITexture {   virtual void bind() = 0;   virtual void unbind() = 0;};struct GLTexture : ITexture {   void bind() { glBindTexture( GL_TEXTURE_2D, myTex ); }   void unbind() { glBindTexture( GL_TEXTURE2D, 0 ); }}
dyerseve
dyerseve
Quote:
Original post by silvermace
struct ITexture {   virtual void bind() = 0;   virtual void unbind() = 0;};struct GLTexture : ITexture {   void bind() { glBindTexture( GL_TEXTURE_2D, myTex ); }   void unbind() { glBindTexture( GL_TEXTURE2D, 0 ); }}


Out of curiosity, how would you implement a D3DTexture class? In order to bind a texture you need a IDirect3DDevice9 object. Would you store that with the ITexture structure? Would you have a global variable?

What class is responsible for the actual calls to glGenTextures, glTexImage2D, etc...? Direct3D texture creation looks nothing like opengl's. My solution is for the renderer class ("driver" as you're calling it) to expose only a handle, and doing all the real work with the texture itself.
Keyframe_
Keyframe_
well this sort of thing you both do is what I had in mind

have an abstract interface which both drivers conform to (I know if using OpenGL only would save trouble on agnostic development, but think of it as a platform where I could develop software renderer if I need it, or some weird console API (less likely).. purely as academic excercise)

now if I had an abstract API, I think that implementing drivers should consist of:

- implementing classes (ITexture like you have here, Primitives etc...)
- global data container for driver (here I would implement handles and stuff like Direct3D needs, or texture pool manager and stuff)

this should basically cover it in the simplest form.. I just don't know how would I approach shader 'thing'.. possibly, and only solution I see as viable is to have Shader object of some sorts which knows (based on the driver used - IShader is a part of driver) which shader to call.. so, basically - I should have one shader for OpengL (HLSL), one for Direct3D etc etc.. yes, it may sound like extra work - but I think it would be safer to do this than to invent some intermediate code which would translate to both (or more) languages..

Basic idea that I have - it's only a hobby, so it may be made or not :) - is to provide this core functionality, no matter which API is behind it (openGL, DX, software..whatever), and then I'd like to develop a 2D engine and 3D engine with it.. while either engine (be it 2D or 3D) can be used as a surface for the other.. for some sorts of HUD, GUI, or texture (with coordinate feedback eventually - like DOOM3 has)

I'm not trying to invent 'one solution fits them all' - I have a pretty much set in stone what I'd like to do with it.. I have two things in mind:

- a game - some sort of 2D meets 3D.. think of Migle Miglia meets Micro Machines, but with raised quality in graphics and gameplay (although both games are really fun :) )

and this is what I would really like to develop:

- interactive 2D/3D content for broadcast graphics.. rendering on one of high-end NVidia boards and outputing image buffer via Bluefish444 to the mix pult (TV Stations).. think of meteo graphics, sports graphics etc etc..

Maybe I should concentrate on OpenGL only - I believe I will, since I'm more comfortable with it anyways - but I don't wan't to be stuck with it once the engine really takes off (if!).. and I have this idea to mix somehow drivers (well not openGL and direct3D, but wither one with software one) to render effects I know how to do with software rendering, eventually replacing it with effects supported by hardware drivers

I hope it all made sense what I wrote, if not - ah well :)

Thanks!
UltimaX
UltimaX
You can use the Wild Magic Engine as a reference.
You will need to check out the renderers here and the render states/main rederer interface here.
Keyframe_
Keyframe_
Quote:
Original post by UltimaX
You can use the Wild Magic Engine as a reference.
You will need to check out the renderers here and the render states/main rederer interface here.


ah, lots of stuff to explore then :)) thanks, unless my brain explodes ;)
dyerseve
dyerseve
How does the Wild Magic Engine deal with shaders?


I'm considering something like this for my own little project:

ShaderHandle CDirect3D::LoadShader(string shaderfilename) {
shaderfilename+=".d3d"
...
}

ShaderHandle COpenGL::LoadShader(string shaderfilename) {
shaderfilename+=".ogl"
...
}

So it would hide the fact that theres two versions of the shader from the engine.
silvermace
silvermace
I wrap the shaders inside a material class of some sort

material file:
<!-- material file--><?xml version="1.0" encoding="utf-8"?><material name="Basic Shader" author="silvermace">	<hardware_shader implements="material">		<pass name="pass_0">			<!-- dynamic texture -->			<texture_unit id="0" name="decalMap" file="data/textures/a0_d.tga" />			<texture_unit id="1" name="bumpMap" file="data/textures/a0_nm.tga" />			<texture_unit id="2" name="glossMap" file="data/textures/a0_gloss.tga" />			<vertex_program	file="data/shaders/cg/dot3_vp.cg"	/>			<fragment_program file="data/shaders/cg/dot3_fp.cg" />			<render_state variable="zwrite" state="on" />			<render_state variable="blend" state="on" src="one" dest="one_minus_src_alpha" />		</pass>	</hardware_shader></material>


so you have a material object, which is all the engine deals with (and thus the game)

struct IMaterial {   virtual void bind() = 0;   virtual void unbind() = 0;   // this is implemented in IMaterial, basically parses the XML file   // and calls the member functions below for API specific operations   virtual void load( std::string matFile );protected:   // these are pure virtual   virtual void createTexture( std::string filename, UINT flags ) = 0;   virtual void createCubeTexture( std::vector<std::string> filenames, UINT flags ) = 0;   virtual void createShader( std::string filename ) = 0;};// implementationstruct GLMaterial : IMaterial {  // implement all the protected functions specifically for OpenGL  ...};



now you can use your material easily w/o knowing which API your utilizing

ResourceHandle h = GetResourceManager().createResource( "data/bump.xml" );IMaterial *m = dyanmic_cast<IMaterial*>( GetResourceManager().getResource( h ) );if( m != NULL ) {   m->bind();   // render stuff   m->unbind();}
Promit
Promit
Personally, I'm operating at a very high level, more than everybody seems to be suggesting.

I have a renderer plugin DLL. It exports an interface that provides functionality for init/uninit, receiving window events, creating/loading resources, and rendering "render entities". When you load a model, you get a model handle back, which is basically a lightweight structure with an id that is meaningful to the renderer. Same for materials. (My materials are not dissimilar from the post just before mine, except that the IMaterial* is not exposed outside of the rendering layer.) Then, you create a render entity each frame and pass it into the engine. That render entity contains the model handle, material handle, etc. The engine renders it.

Basically, there is a very high level of interaction. I have no idea how the engine does its job from an external point of view. I tell it to render model X with material Y with a matrix M, and it does so. The big advantage here is that I basically don't have to define any API agnostic abstractions at all. I'm way above that level of thinking. I'm telling the engine in an almost qualitative fashion what I want on screen, and it does so.

The one slightly messy thing is with coordinates systems and matrices. I define my matrices in OGL style column major and transpose them inside the D3D renderer. As for the coordinates, I defined +Z as vertical, with the camera defaulted to pointing down +Y. Then I just switch the two axes for D3D.

[Edited by - Promit on September 26, 2005 12:53:25 AM]
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Keyframe_
Keyframe_
Quote:
Original post by Promit
Basically, there is a very high level of interaction. I have no idea how the engine does its job from an external point of view. I tell it to render model X with material Y with a matrix M, and it does so. The big advantage here is that I basically don't have to define any API agnostic abstractions at all. I'm way above that level of thinking. I'm telling the engine in an almost qualitative fashion what I want on screen, and it does so.


but isn't that what you're doing agnostic? :) this is the idea, to have one unified way to tell the core renderer what you want rendered, and it does so.. specific implementation how does it do that is in the 'driver' which you made wither for D3D, OGL or whatever

Quote:

The one slightly messy thing is with coordinates systems and matrices. I define my matrices in OGL style column major and transpose them inside the D3D renderer. As for the coordinates, I defined +Z as vertical, with the camera defaulted to pointing down +Y. Then I just switch the two axes for D3D.


I have in my mind sort of a scene graph (I'm trying to find a way currently to make physics graph integrate with render graph...bah) to have a process state, where I would tell the graph something like 'look, we're now in this coordinate behaviour - please conmform (you have to be nice with it ;) )' and it takes a few secs where it transforms all coordinates to the appropriate system we wan't..so that way I avoid doing the coordinate transforms via nasty macros within drivers

I'm also thinking of should I hold everything in one big graph, classic scheme (render nodes, materials, cameras, event nodes..blahblah), physics nodes and audio nodes - all in one big stuff, or should I separate them and have them communicate with each other... I'll deal with it when I get to that, obviously.. first, I need to make this core rendering functions which should only expose core funcionality

silvermace
silvermace
you could just tell Direct3D to use the Right Handed coordinate system, it provides RH coord functions as well as LH ones, or like wise tell OpenGL to use a Left handed coord system (all you have to do is glScalef(0,0,-1) I think)

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.