Writing an Abstract Renderer, part 1

This is a three part series:

Part 1 – Introduction to Abstract Renderers
Part 2 – DirectX 11 Interface & Gotchas
Part 3 – Shader Interface

Note: Unfortunately I can’t share my actual abstract renderer implementation. All code in this series should be considered pseudo-code for demonstrative purposes only.

Introduction

Deployment to multiple platforms is virtually a requirement for today’s games. It’s difficult to justify a large budget for a game that will only launch on one console or one type of phone, unless you’re lucky enough to have a publisher pay you a bonus for being exclusive.

As such, robust abstraction layers for rendering, sound, input, and network communication are common.

The major graphics APIs in use by games today – DirectX 9, DirectX 11, OpenGL, and OpenGL ES 2 are similar enough that a single interface is achievable. However, it’s not always easy to find common ground, particularly when interfacing with shaders.

Abstract at the System API Level

There are two major philosophies when it comes to writing an abstraction interface:

  • Abstract at the object level – provide different implementations of your Mesh, Texture, or Sound Effect classes for each platform and have them directly call the system API
  • Abstract at the system API level – provide a single abstract wrapper over the system API and write shared Mesh, Texture, or Sound Effect classes that only talk to the wrapper

In general, abstracting at the system API level has a higher upfront cost but makes porting to other platforms cheaper in the long run. When you abstract at the object level, you can immediately write your DX11 Mesh class and you pay no cost until you decide to port to iOS, at which point you have to write an OpenGL Mesh class. However, you will find yourself duplicating the porting effort each time you create a new graphics object(say, a TextRenderer class).

As such, I strongly recommend abstracting once at the system API level and only having to maintain a single Mesh, Texture, TextRenderer, etc.

Interface

There are many methods to write an abstraction interface – a singleton, a namespace, etc.

My preferred method is an abstract base class with derived implementations, i.e.


class Renderer
{
public:
	virtual class VertexBufferResource * createVertexBuffer( unsigned int size, void * data, bool isDynamic ) = 0;
	virtual void* lockVertexBuffer( class VertexBufferResource * vertexBuffer, unsigned int offset, unsigned int size ) = 0;
	virtual void unlockVertexBuffer( class VertexBufferResource * vertexBuffer ) = 0;
	virtual void releaseVertexBuffer( class VertexBufferResource * vertexBuffer ) = 0;
...

class OpenGLRenderer : public Renderer
...

This way, when writing a new renderer implementation, you’ll get compile errors for anything you miss. It also means that you could theoretically have similar renderers share code in a base class, for instance DX11Renderer and DX9Renderer could both derive from DXRenderer. Note: in practice, DX9 and DX11 are different enough that they cannot actually share any code, as we’ll see in Part 2.

Wrapping Resources

DX9, DX11, and OpenGL share a rough idea of graphics ‘objects’ or ‘resources’, like textures, vertex buffers, index buffers, compiled shader code, or render targets.

In OpenGL, these take the form of a GLuint handle that gets passed to functions like glGenBuffers, glBindBuffer, and glDeleteBuffers. In DirectX, they take the form of objects derived from a reference-counted resource interface that are constructed and manipulated by functions but destroy themselves when their reference count reaches 0.

One way to wrap these objects is just to follow the OpenGL C-style construction/manipulation/destruction interface:

class VertexBufferResource
{
};

...
class GLVertexBufferResource : public VertexBufferResource
{
public:
	GLuint resource;
};

...
VertexBufferResource * OpenGLRenderer::createVertexBuffer( unsigned int size, void * data, bool isDynamic )
{
	GLVertexBufferResource * resource = new GLVertexBufferResource();

	glGenBuffers(1, &resource->resource);

	glBindBuffer(GL_ARRAY_BUFFER, resource->resource);

	glBufferData(GL_ARRAY_BUFFER, size, data, isDynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);

	assert(glGetError() == 0, "Could not create vertex buffer.");

	return resource;
}

void* OpenGLRenderer::lockVertexBuffer( VertexBufferResource * vertexBuffer, unsigned int offset, unsigned int size )
{
	//  we assume it's actually a GLVertexBufferResource - you can provide more robust checks if you want
	GLVertexBufferResource * resource = static_cast<GLVertexBufferResource>(vertexBuffer);

	glBindBuffer(GL_ARRAY_BUFFER, resource->resource);

	void * outData = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES);

	assert(glGetError() == 0, "Could not lock vertex buffer.");

	return outData;
}
...

void OpenGLRenderer::releaseVertexBuffer( VertexBufferResource * vertexBuffer )
{
	//  we assume it's actually a GLVertexBufferResource - you can provide more robust checks if you want
	GLVertexBufferResource * resource = static_cast<GLVertexBufferResource>(vertexBuffer);

	glDeleteBuffers(1, &resource->resource);

	delete vertexBuffer;
}

You could also make these VertexBufferResource objects follow the RAII pattern. Make sure that you are very careful about thread ownership and order of destruction issues if you go down that route!

NULL Implementation

A great place to start is writing a NULL(or Logging) implementation that doesn’t do any actual work but simply logs out the commands it receives. This can be a very useful tool:

  • When writing a new Renderer, you don’t have to implement every command – you can temporarily derive from the NULL renderer. Whatever commands you don’t implement will show up in the log, and you can focus on implementing functionality one piece at a time.
  • You can temporarily switch to the NULL renderer and generate a complete log of all graphics calls. You could also have the NULL renderer redirect calls to an actual renderer after logging.

Next time…

In Part 2, we’ll discuss why it’s best to design your interface around DirectX 11.

Insanely Twisted Shadow Planet released on Steam!

Insanely Twisted Shadow Planet is now available on Windows via Steam!

Care has been taken to make it a pleasant experience – all of the tools(claw, blaster, magnet, etc.) now take advantage of a keyboard/mouse control scheme. For instance, the claw follows your mouse, so you can just click on the object you want to pick up and drag it around. There are plenty of graphics options as well – AA level, depth of field, shader quality, texture quality, etc.

The Steam release also includes both the Lantern Run and Shadow Hunters multiplayer modes, so be sure to pressure your friends into purchasing it as well so you can try to beat the XBLA records…

Album release – 0212

I have made electronic music for a long time, but only recently began releasing some of it publicly via SoundCloud.

This February, I participated in the RPM Challenge for the third time. RPM stands for “record production month” – the idea is you write and record 40+ min. of music during the month of February as a creative challenge.

This RPM, I decided to try some collaborations, some in person with local Seattle artists and some online with people from all over the US and UK that I met through SoundCloud.

The result is this:

You can download it for free at:

faceculler.bandcamp.com

Here are two tracks from 0212:

Shadow Hunters DLC

I recently had the pleasure of being Lead Engineer on the freshly released Shadow Hunters DLC for Insanely Twisted Shadow Planet. Shadow Hunters is a new LIVE multiplayer mode that has you and four friends racing through procedurally generated planets with a ticking bomb.

John Scrapper(FuelCell’s creative director) and I have long shared a passion for procedural content. This was an exciting opportunity to put some of our ideas into practice.

When I get some time, I’ll try to write up a post-mortem describing how our procedural astroid generation worked. But for now, it’s quickly on to the next project!

Insanely Twisted Shadow Planet now available!

It has been an intense last few months, but Insanely Twisted Shadow Planet is finally done and available on Xbox LIVE as part of the 2011 Summer of Arcade!

It includes Lantern Run, a LIVE enabled multiplayer mode that was the source of a lot of late nights. Since the game is physics based, and has features like an articulated arm that you can use to pick up and throw objects, networking was non-trivial. I’m immensely proud of the end result though and reviewers so far have given the networking two thumbs up!

We are hard at work on something new that should be announced soon…