h1

OpenGL Style on-the-fly Geometry Instructions in Direct3D9

THEY ARE IMPOSSIBLE! In OpenGL it’s really a snap (glBegin(), glEnd(), glVertex(), glNormal(), etc.) but in Direct3D it’s fucking impossible. You cannot do any of it on the fly. You consolidate everything down to a single call, (a few complex ones for vertex buffers) that requires all this pain-in-the-ass setup, and it’s STILL less efficient. I don’t get you fucking people at M$. ARB can do it, so you definitely can.

But anyway… I created a little class called GeometryInterfaceD3D9. It takes OpenGL style on-the-fly commands, then does the work for you. It’s not really fully optimized to the extent you can with D3D, but it works, and emulates OpenGL very well. Just avoid quads at all costs, because while I technically added emulation for them (quad primitives not available in D3D9 wtf?) they are DEADLY SLOW. I could fix this with a bit more preprocessing, but for now it just does multiple calls to RenderPrimitiveUP(), so it is, as I said, ridiculously slow. THIS IS NOT THE FINAL ITERATION, I WILL FIX IT KAY ZOMG.

The reason was that the main Renderer class and children RendererOpenGL and RendererD3D9 were initially designed for these calls. Then I eventually realized that this is not how you do it at all with Direct3D. But I firmly believe that these on-the-fly geometry instructions are the clean way of doing the API. So, I accepted a less efficient implementation on RendererD3D9. Don’t worry about the lack of efficiency there. Just as VertexBuffer and VertexBufferVBO are efficiency optimized for OpenGL, VertexBufferD3D9 will be optimized for Direct3D. And of course, all the specific implementations are invisible to the user of the library, so it is important to add layers that unify the API.

But down to brass tax on the interface class. It is designed to to be created with a “new foo(bar),” fed the info, then deleted with a “delete foo” after calling “foobar->RenderPrimitives()”. RenderPrimitives() returns a Boolean success indicator. The interface looks like this:

// geometry interface only for use internally, though some may find it useful.
// copy away!

class GeometryInterfaceD3D9
{
private:
int flags; // the indices flag will be ignored
int fvf;

unsigned int numtextures;
unsigned int norm_offset;
unsigned int tex_offset;
unsigned int color_offset;
unsigned int vertptr;
unsigned int vertsize; // depends on the elements of vertices here
char *verts;

bool is_valid;
public:
GeometryInterfaceD3D9(int flags=RAVEN_GEOMETRY_FLAG_VERTICES,unsigned int numtextures=1);
~GeometryInterfaceD3D9();

bool IsValid() {return is_valid;}

void SetColor(const Vector4 &color);
void SetNormal(const Vector3 &norm);
void SetTexCoords(const Vector2 &tc,int idx);
void SetVertex(const Vector3 &vert);

void AdvancePtr();

bool RenderPrimitives(GeometryType geomtype,LPDIRECT3DDEVICE9 dev);
};

It has the extremely OpenGL like feature of just letting you send the data of each vertex, then calling AdvancePtr(). That is one way it differs, in OpenGL glVertex() advances the buffer pointer for you. Basically, the renderer classes have this next API for on-the-fly geometry:

virtual bool BeginGeometry(int attribs,int numtextures,GeometryType geomtype)=0;
virtual void GeometryVertex(const Vector3 &vert)=0;
virtual void GeometryNormal(const Vector3 &norm)=0;
virtual void GeometryColor(const Vector4 &color)=0;
virtual void GeometryTexCoord(const Vector2 &texcoord,int idx)=0;
virtual bool EndGeometry()=0;

Just like with OpenGL, on the Direct3D version of the renderer, on GeometryVertex() in addition to calling SetVertex() it also calls AdvancePtr(). The OpenGL version pretty much does a single raw call to OpenGL for each method, aside from keeping track of a few things. The Direct3D version of the on-the-fly API looks even more raw on the inside, because it lets GeometryInterfaceD3D9 keep track of everything. So, I give you the OpenGL and Direct3D versions of those methods for comparison.

OpenGL on-the-fly API implementation:

bool RendererOpenGL::BeginGeometry(int attribs,int numtextures,GeometryType geomtype)
{
if (geomrun) return false;

glBegin(geomtype_lookup[geomtype]);
geomattrib=attribs;
geomtexdef=numtextures;
cd=nd=td=false;

return geomrun=true;
}

void RendererOpenGL::GeometryVertex(const Vector3 &vert)
{
if ((geomattrib&RAVEN_GEOMETRY_FLAG_COLORS)&&(!cd)) glColor4fv((float *)Vector4(1.0f));
if ((geomattrib&RAVEN_GEOMETRY_FLAG_NORMALS)&&(!nd)) glNormal3fv((float *)Vector3(0.0f));
if ((geomattrib&RAVEN_GEOMETRY_FLAG_TEXCOORDS)&&(!td)) glTexCoord2f(0.0f,0.0f);

glVertex3fv((float *)vert);

cd=nd=td=false;
}

void RendererOpenGL::GeometryNormal(const Vector3 &norm)
{
if (!(geomattrib&RAVEN_GEOMETRY_FLAG_NORMALS)) return;
glNormal3fv((float *)norm);
nd=true;
}

void RendererOpenGL::GeometryColor(const Vector4 &color)
{
if (!(geomattrib&RAVEN_GEOMETRY_FLAG_COLORS)) return;
glColor4fv((float *)color);
cd=true;
}

void RendererOpenGL::GeometryTexCoord(const Vector2 &texcoord,int idx)
{
if (!(geomattrib&RAVEN_GEOMETRY_FLAG_TEXCOORDS)) return;
if (idx==0) // no multi-tex for you! yet.
glTexCoord2fv((float *)texcoord);
td=true;
}

bool RendererOpenGL::EndGeometry()
{
if (!geomrun) return false;
glEnd();
geomrun=false;
return true;
}

Direct3D on-the-fly API implementation:

bool RendererD3D9::BeginGeometry(int attribs,int numtextures,GeometryType geomtype)
{
if (geom_d3di) return false;
geom_d3di=new GeometryInterfaceD3D9(attribs,numtextures);
if (!geom_d3di->IsValid())
{
delete geom_d3di;
geom_d3di=NULL;
return false;
}
d3dgeomtype=geomtype;
return true;
}

void RendererD3D9::GeometryVertex(const Vector3 &vert)
{
if (!geom_d3di) return;
geom_d3di->SetVertex(vert);
geom_d3di->AdvancePtr();
}

void RendererD3D9::GeometryNormal(const Vector3 &norm)
{
if (!geom_d3di) return;
geom_d3di->SetNormal(norm);
}

void RendererD3D9::GeometryColor(const Vector4 &color)
{
if (!geom_d3di) return;
geom_d3di->SetColor(color);
}

void RendererD3D9::GeometryTexCoord(const Vector2 &texcoord,int idx)
{
if (!geom_d3di) return;
geom_d3di->SetTexCoords(texcoord,idx);
}

bool RendererD3D9::EndGeometry()
{
bool result=true;
if (!geom_d3di) return false;
if (!geom_d3di->RenderPrimitives(d3dgeomtype,d3dcore->dev)) result=false;

delete geom_d3di;
geom_d3di=NULL;

return result;
}

And just so you can see what drives the Direct3D version, here is in all honesty the meat of this article.
GeometryInterfaceD3D9 implementation:

GeometryInterfaceD3D9::GeometryInterfaceD3D9(int flags,unsigned int numtextures):
vertptr(0), verts(NULL), is_valid(true), flags(flags), numtextures(numtextures)
{
if (!(flags&RAVEN_GEOMETRY_FLAG_VERTICES))
{
is_valid=false;
return;
}
vertsize=sizeof(float)*3;
fvf=D3DFVF_XYZ;

if (flags&RAVEN_GEOMETRY_FLAG_NORMALS)
{
norm_offset=vertsize;
vertsize+=sizeof(float)*3;
fvf|=D3DFVF_NORMAL;
}

if (flags&RAVEN_GEOMETRY_FLAG_COLORS)
{
color_offset=vertsize;
vertsize+=sizeof(DWORD);
fvf|=D3DFVF_DIFFUSE;
}

if (flags&RAVEN_GEOMETRY_FLAG_TEXCOORDS)
{
tex_offset=vertsize;
vertsize+=sizeof(float)*2*numtextures;
fvf|=numtextures<SetFVF(fvf);
if (primtype) // d3d support, render it all!
{
dev->DrawPrimitiveUP((D3DPRIMITIVETYPE)primtype,prim_count(numverts,geomtype),verts,vertsize);
return true;
}
else // no d3d support, get creative (less efficient but serves for portability)
{
switch (geomtype)
{
case RAVEN_GEOMETRY_LINE_LOOP:
AdvancePtr();
numverts=vertptr+1;
memcpy(verts+vertptr*vertsize,verts,vertsize);
return dev->DrawPrimitiveUP(D3DPT_LINESTRIP,prim_count(numverts,RAVEN_GEOMETRY_LINE_STRIP),verts,vertsize)==D3D_OK;
case RAVEN_GEOMETRY_QUADS:
for (register unsigned int i=0; iDrawPrimitiveUP(D3DPT_TRIANGLEFAN,2,verts+i*vertsize,vertsize)!=D3D_OK) return false;
}
return true;
default:
return false;
}
}
}

There are some holes in what you can see from this, so I’ll create another code block here that defines many of the enums/consts/macros used:

// from vertexbuffer.h

#define RAVEN_GEOMETRY_FLAG_INDICES 0x01
#define RAVEN_GEOMETRY_FLAG_VERTICES 0x02
#define RAVEN_GEOMETRY_FLAG_NORMALS 0x04
#define RAVEN_GEOMETRY_FLAG_COLORS 0x08
#define RAVEN_GEOMETRY_FLAG_TEXCOORDS 0x10

enum GeometryType
{
RAVEN_GEOMETRY_POINTS,
RAVEN_GEOMETRY_LINES,
RAVEN_GEOMETRY_LINE_STRIP,
RAVEN_GEOMETRY_LINE_LOOP,
RAVEN_GEOMETRY_TRIS,
RAVEN_GEOMETRY_TRI_STRIP,
RAVEN_GEOMETRY_TRI_FAN,
RAVEN_GEOMETRY_QUADS,
RAVEN_GEOMETRY_QUAD_STRIP
};

// from the renderer_opengl.cpp

static GLint geomtype_lookup[]=
{
GL_POINTS, //RAVEN_GEOMETRY_POINTS
GL_LINES, //RAVEN_GEOMETRY_LINES
GL_LINE_STRIP, //RAVEN_GEOMETRY_LINE_STRIP
GL_LINE_LOOP, //RAVEN_GEOMETRY_LINE_LOOP
GL_TRIANGLES, //RAVEN_GEOMETRY_TRIS
GL_TRIANGLE_STRIP, //RAVEN_GEOMETRY_TRI_STRIP
GL_TRIANGLE_FAN, //RAVEN_GEOMETRY_TRI_FAN
GL_QUADS, //RAVEN_GEOMETRY_QUADS
GL_QUAD_STRIP //RAVEN_GEOMETRY_QUAD_STRIP
};

So hopefully this should give you some pointers as to how to accomplish on-the-fly rendering capabilities with Direct3D. Now, this is NOT considered to be efficient enough for big scenes by either Direct3D *OR* OpenGL. It is sufficient for something like a single textured quad or a quick cube demo, things like that. It is nice and convenient, so Direct3D’s failure to incorporate this in to it’s core API is a huge shortcoming that I have attempted to patch over with GeometryInterfaceD3D9. Hopefully you will find this useful.

Peace!

Advertisements

2 comments

  1. OpenGL FTW!!!1!1!one!1eleven 🙂
    But seriously good work. I’ll never use it but thats cause I always use OpenGL.


    • Yeah. I am very much on your side with this. It’s just that OpenGL support sucks big green lizard cock on Vista, which is the only reason I’d bother with Direct3D at all. Yes, Vista sucks, but when a platform sucks, it INVITES me to make a port. Like… I guess it’s the hacker instinct bringing me on to a challenge. 🙂



Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: