Monday, March 19, 2012

OpenGL Buffer Packing

This weekend I felt a drain to my creativity brought about by a combination of not feeling well, disappointment in the ending of Mass Effect 3, and actually getting decent at call of duty. Drop-box replacing some important code files with emptiness didn't help either. So I've decided to start from scratch, rather than trying to repair my existing code base.

Starting from square one usually involves a trip to good old fashioned C. If I can code it in C, I can take it anywhere. I had gotten quite good at doing OpenGL from inside OpenTK, so my idea was to take some of the  modern concepts learned from there, and apply them back in C.

Modern OpenGL consists really of two things, cramming things in to buffers, and making pretties with shaders.

So, let's talk about array buffers, no, wait, let's talk about vertexes first.

In a mathematical sense, a vertex buffer is an intersection of two or more lines in space. We use vertexes to define the bounds of our geometry instead of lines or rays. However, in OpenGL, vertexes do not just represent points in space that define boundaries of geometry. Color, texture, lighting, and other information can also be attached to a vertex as well.

There are two ways of packing data into buffers. The first way is to give each type of element it's own buffer. 

So in case A:

* vertexes would be in their own vertex buffer
* colors would be in their own color buffer
* texture coordinates would be in their own buffer
* normals would be in their own normal buffer

Case B: (Eeach vertex would contain the following)

* x, y, z (position)
* s, t (texture)
* r, g, b, a (color)
* nx, ny, nz (normal)

In general, case B is the better way to go. It only requires one bind buffer operation, vs one for each component type. 

For case A, you would do your drawing like this:

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_handle);
glVertexPointer(3, GL_FLOAT, 0, 0);

glBindBuffer(GL_ARRAY_BUFFER, color_buffer_handle);
glColorPointer(4, GL_FLOAT, 0, 0);

glDrawArrays(GL_TRIANGLES, 0, number_triangles);
...

for case B, you would draw like this:

typedef struct {

float x, y, z;
float r, g, b, a;

}custom_vertex_format;

...

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

glBindBuffer(GL_ARRAY_BUFFER, buffer_handle);
glVertexPointer(3, GL_FLOAT, sizeof(custom_vertex_format), 
    offsetof(custom_vertex_format, x);

glColorPointer(4, GL_FLOAT, sizeof(custom_vertex_format), 
    offsetof(custom_vertex_format, r);

glDrawArrays(GL_TRIANGLES, 0, number_triangles);

offsetof is a macro defined in stddef.h http://linux.die.net/man/3/offsetof

Again, in most circumstances, method B is going to be faster. The reasoning for this is as follows.

Fewer cache misses, since all the data is tightly packed together. In case A, it might be conceivable that the vertex buffer and the color buffer might be in completely different areas in memory. In case B, each vertex should be adjacent to each other. It's also especially important when working with element arrays.

What's an element array?

Consider the following. Say you have a mesh, and in this mesh, you have multiple adjacent triangles. These triangles, 'share' a vertex. As in, the vertex has the exact same position, color, normal and texture attributes. The more attributes you have packed in your vertex, the more expensive it becomes to have duplicates in your vertex buffer.

Say you have the following vertexes 
V = {A, B, C, D, E, F, G, H, I, J}

Say your triangle mesh 
M = {A, B, C, B, C, D, C, D, E, F, G, H}

So, B is an element twice, C is an element 3 times, D is in there twice, etc. At this point, each vertex is composed of at least 11 floating point elements. If only there was a way to send each vertex to the buffer exactly once, and then just draw by index... There is!

Say V = {A, B, C, D, E, F, G, H, I, J}
and M = {A, B, C, B, C, D, C, D, E, F, G, H}

Let Mi = {1,2,3,2,3,4,3,4,5,6,7,8}

So we would bind V as our vertex buffer, and Mi as our element buffer. we would use glDrawElements instead of glDrawArrays. http://www.opengl.org/sdk/docs/man/xhtml/glDrawElements.xml

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

glBindBuffer(GL_ARRAY_BUFFER, buffer_handle);
glVertexPointer(3, GL_FLOAT, sizeof(custom_vertex_format), 
    offsetof(custom_vertex_format, x);

glColorPointer(4, GL_FLOAT, sizeof(custom_vertex_format), 
    offsetof(custom_vertex_format, r);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_handle);

glDrawElements(GL_TRIANGLES, number_triangles, GL_SHORT, 0);

Take note that we only had to bind the element array buffer in order to use glDrawElements, we didn't have to specify an element pointer, or enable the client state for it.

Also, notice that there isn't a separate element buffer for each attribute of the vertex. It sounds like it could be a great idea, but the reality is that it would be incredibly slow. It's the same reason why non-power of two textures are such a problem. It's just a constraint we have to live with.

I hope this has helped distinguish some of the differences in how OpenGL can process buffers for you.

Thursday, March 15, 2012

Service Driven Game Development

When most people think of a service, web servers, printer spoolers, and drivers come to mind. In a different sense, a service can be thought of as a program that runs for as long as the operating system is running. Most services are independent of each other, do one thing, and do it well, like serve web pages. The same concept can be applied to games. If each component of your game does one thing, and does it well, it allows you to focus on one component at a time, it also makes your game more testable. By bringing services up and down, you can simulate failure in a specific part, such as when the network drops out, or the screen gets unplugged.

One of the things to keep in mind when doing this is cross-service communication. How is one service going to talk to another? In the example presented, each service is going to register for the types of events that it wishes to be notified of. It will be a bit clearer when the code is presented.

First things first, let's define the service interface.

interface IService
+ void RegisterEvents(ServiceManager m)
+ void ExecuteFrame(ServiceManager m)

Each IService must be able to register events with the service manager, and each service must expose a method that the service manager can call to process events.

The other primary component is the Message.

class Message
+ public IService Sender {get; set;}
+ public EventArgs EventArguments {get; set;}

A message contains a sender, and event arguments. Sender tells you what service the message came from, and EventArguments contains other arguments that a service would want.

The Services Manager class is responsible for keeping track of the services and routing the messages between the services.

class ServiceManager
+ public delegate void OnRecievedMessage(Message m);
+ Dictionary<Type, IService> _services;
+ List<Tuple<Type, IService, OnRecievedMessage>> _routes;
+ public void RegisterService(Type service) { ... }
+ public void UnregisterService(Type service) { ... }
+ private void UnregisterEventHandler(IService sender) { ... }
+ public void RegisterEventHandler
    (Type eventsArgType, OnRecievedMessage m) { ... }
+ public void SubmitMessage
    (IService sender, 
        Type eventArgsType, EventArgs message) { ... }
+ public void Execute() { ... }

** OnRecievedMessage is the delegate for when a service receives a message.

** _services is a dictionary of service types mapped to the service. That means for each type, there can be exactly one service.

** _routes specifies the path each message takes when it's dispatched. If you haven't seen a Tuple before, it can be considered to be a read only, fixed length list. This establishes a many to many mapping between the types of messages, and delegates that process messages.

If you had a process that was responsible for switching states, and needed to listen for the pause button, and a state responsible for processing immediate game events, they both might listen for a key down event.

The other thing to note is that any process may dispatch any type of event. If you are writing a multi-player game, and it supports local input as well as network input, the network service might send the same player movement actions as the physics service.


** RegisterService is used to register a new service with the game server manager. Notice that instead of registering an instance of an existing service, you register a type, so that the server manager itself instantiates the object.




public void RegisterService(Type service)
{
    _services[service] = 
        (IService)Activator.CreateInstance(service);
    _services[service].RegisterEvents(this);
}






** UnregisterService is used to take a service out of circulation, including all of the routes that said service might deliver messages.


public void UnregisterService(Type service)
{
    var s = _services[service];
    UnregisterEventHandler(s);
    _services.Remove(service);
}



** UnregisterEventHandler is called internally to remove the services that have been registered when the service was created.


private void UnregisterEventHandler(IService sender)
{
    for (int i = _routes.Count - 1; i >= 0; i--)
    {
        if (_routes[i].Item2 == sender)
            _routes.RemoveAt(i);
    }
}



** RegisterEventHandler is called by a service to add an event to the routing map. It's not really something that should be exposed to anything but the Service, but it is what it is. C# doesn't support the friend keyword like C++ does.


public void RegisterEventHandler(Type eventsArgType, 
    IService sender, 
    OnRecievedMessage m)
{
    _routes.Add(
        new Tuple<Type, IService, OnRecievedMessage>
            (eventsArgType, sender, m));
}



** The SubmitMessage method is used to insert a message for the services to consume.


public void SubmitMessage(IService sender, 
    Type eventArgsType, 
    EventArgs message)
{
    (from r in _routes 
     where r.Item1 == eventArgsType 
     select r.Item3)
        .ToList()
        .ForEach(m => m(new Message() { 
            EventArguments = message, 
            Sender = sender }));
}



** Execute is called each frame, so that each of the services can execute their core. Recall ExecuteFrame from the IService interface.


public void Execute()
{
    _services.Values
        .ToList()
        .ForEach(x => x.ExecuteFrame(this));
}



Activation of the services would look something like this


var sm = new ServicesManager();
sm.RegisterService(typeof(InputService));
sm.RegisterService(typeof(GameService));


If we wanted to execute the game for 10 steps, it would look like this.

for (int i = 0; i < 10; i++)
     sm.Execute();

To unregister a service.

sm.UnregisterService(typeof(GameService));


A sample implementation of InputEventArgs and InputService

class InputEventArgs:EventArgs
{
    public char ScanCode { get; set; }
}

class InputService:IService
{
    public void RegisterEvents(ServicesManager m)
    {}

    public void ExecuteFrame(ServicesManager m)
    {
        m.SubmitMessage(this, 
            typeof(InputEventArgs), 
            new InputEventArgs() {ScanCode = 'a' });
    }
}

Each time ExecuteFrame is called, this sample service puts a new message into the queue.

This is the sample implementation for a game service, it a very trivial implementation.

class GameService : IService
{
    public void RegisterEvents(ServicesManager m)
    {
        m.RegisterEventHandler(
            typeof(InputEventArgs),
            this,
            (a) =>
            {
                Console.WriteLine(
                    (a.EventArguments
                    as InputEventArgs).ScanCode);
            }
            );
    }

    public void ExecuteFrame(ServicesManager m)
    { }
}


That more or less wraps it up. This isn't 'the' way to do it, there's certainly much better ways to do it. You could have each service executing in it's own thread. Some people would rather not use tuples. The takeaway should be that this is another tool you throw in your toolbox.