Wednesday, 4 March 2015

Getting started with GLFW (part 1)

In my little hobby projects I've been using GLFW and I've been thinking awhile about writing a small tutorial series based on my experiences with this.

Two things struck me while playing around with this.
  1. Mac OS X as a platform seems to be underrepresented. Many tutorials focus on Windows and it seems that there is a bit more freedom here. On Mac Os X you hit roadblocks sooner such as OpenGL 1/2 full deprecation which brings us to:
  2. Many tutorials focus on OpenGL 1/2 but at least on the Mac, once you want to use the power of OpenGL 3 or upwards you loose OpenGL 1/2 support. Loosing the fixed rendering pipeline isn't a big deal but loosing all the global state and all the extra setup that is done for you means you find yourself looking at a blank screen for ages till you find that last illusive missing ingredient
Before we go any further however we need to ask ourselves a really important question:

Why?

There are a number of facets to this question and the first isn't even about choosing GLFW but why go through the trouble of writing something from scratch? GLFW only offers us a cross platform blank canvas, why go through all the work to implement everything from there when there are full featured alternatives?

The easy answer to that question is that I'm crazy. I'm not writing this because I've got a delusion that I'm going to write the next big game engine. I've got a good paying job and this is a hobby for me and the biggest reason for most of the things I've done so far is that I simply want to learn how to build these things.

But the hard answer to that question is a little more involving. Deciding to write your own engine instead of using an off the shelf one can make sense. It really depends on what you are building.
If you're after building the next AAA title you probably want to go and save yourself the time and effort and just buy Unity or Unreal or something like that.
If you're building an indie game on a budget and you're willing to do a little more yourself I would suggest having a look at Ogre3D which really seemed to be nice.
But for a lot of simpler concepts you'll actually find that you can get what you need build relatively quickly and doing it yourself does have the added advantage that you'll end up with something smaller, more nibble and easier to adjust to your needs.

The second burning question is why GLFW.
That one is simpler to answer though, it meets my needs and its easy to use.
There are other alternatives and you could use them just as well.

Depending on your style of coding you can write the internals in such a way that you could swap any supporting library out from underneath it easily enough. I'll try to adhere to this as I have the hope to get this to a point where I can replace GLFW with a wrapper made in XCode to support iOS (one of the things lacking from GLFW as a platform). I've got the basics of this working but it's not at a state yet where I am convinced it will get me where I want to go.

What I will not do is abstract the rendering code itself. I'm purely targeting OpenGL 3+ and as far as possible try to stay compatible with OpenGL ES 2

Before we can begin

This is the boring part. I'm going to be writing most from the perspective of Mac OS X.

If you're doing this on Windows or Linux your life is going to be loads easier. On Linux just about everything you need is there, on Windows download the express version of VC++ and you're on your way too.
Just check the GLFW pages just to make sure you get everything right, if you're on Windows you're probably best off just grabbing the binaries and you're up.

On Mac OS X, you start off by downloading XCode (but if you're a developer I'd be surprised if you're not already using it). I'm running 6.1.1 at the moment and have a huge love/hate relationship with it. XCode 4 was probably the last I actually enjoyed using but maybe that's because I'm not doing any fullfletched iOS development for which I'm sure XCode 6 is a superior being.

Now life gets a little more crazy, XCode went through a phase where it didn't automatically install the command line tools needed to compile stuff the old fashioned way. Apple has come back to their senses but if you're on XCode 4 or 5 you'll need to open up XCode, go to preferences and check the download tab. There should be an option there to install the command line tools.

The missing tool here is cmake. This is a handy little tool that will generate platform specific makefiles for a project. Installing it is easy, go to the download page, download the dmg and drag cmake from the dmg into applications. If like me your on Mavericks or Yosemite cmake won't start until you go into system preferences -> Security & Privacy and tell OS X you trust it.

Finally we need GLFW, download the latest source (I'm using 3.1 at the moment) and unzip it in a folder of your choice.

Compiling GLFW on Mac OS X

We now need to build GLFW. Again I'm doing the Mac version here, Windows users you are in luck as precompiled binaries are available, get them, don't reinvent the wheel.

One choice you have to make at this point is whether you want to build a static library or a dynamic library. Call me old fashioned but I'm a sucker for static libraries. This means GLFW is fully compiled into your executable and you're not dependent on external files. Dynamic libraries are great for large pieces of kit that you want to replace easily with never version but this rarely goes unpunished and in today's cheap abundent disk storage you really don't care.

If you haven't already opened cmake when you installed it, run it now. You'll get a screen that asks you for the location of the source. Type in the path to where you unzipped GLFW.
It also asks you for a build destination, I use the same path but add /build at the end.
Now press the configure button (it will ask you to create the build folder if needed).
A popup should open asking your what you wish to create. I'm going for "Unix Makefiles" using default native compilers.

It will take a few seconds to fill and present you with a nice list of configure options.
The default options bar one are what I wanted, the option I'm changing is GLFW_BUILD_UNIVERSAL which when ticked builds both 32bit and 64bit copies of the library as a universal library so you can deploy a single executable regardless of having 32bit or 64bit hardware (a really neat feature in OSX).
Now hit Generate and we should end up with some make files.
Open up a terminal and CD into your build folder and type make and grab a coffee!
(don't worry about some of the warnings, most are related to GLFWs support for OpenGL 1 and Apple letting us know it won't be around for much longer)

Once its done rumbling you should end up with a fully compiled copy of GLFW including a few example programs that you can test out in the examples sub folder of your build folder.

After all this there really are only two bits of GLFW that are interesting:
glfw-3.1/include/GLFW/glfw3.h
glfw-3.1/build/src/libglfw3.a
I often find myself copying these two files into my project folder so I can keep it nice and clean.

Our first GLFW application

It's customary to do a hello world application but unfortunately GLFW doesn't natively support font rendering (we'll deal with that another day).

Now at this point you could go and use XCode to manage your source code. It drives me nuts seeing that I'm about to structure my application in a way it doesn't like, it seems to be convinced only full COCOA applications should be deploy-able. Now I fully admit I've given up on this a long time ago so it may simply be a lack of knowledge on my part but I'm ruling out XCode for now.

The other option is this wonderful free x-platform IDE called code::blocks. It would allow us to maintain a single project file that can build our application to both Windows, Mac and Linux. I may one day explore that a little more and revisit this.

For now, I'm staying with old fashioned makefiles. Looking ahead a little I'm going to start with the following folder structure:
3rdparty
  - GLFW
    - glfw3.h
    - libglfw3_mac.a
build
include
macosx
  - Info.plist
  - makefile
resources
  - app.icns
source
  - main.c
windows
Obviously the windows folder will stay empty for now as I'm just doing the Mac side of life.
The files in the 3rdparty folder are the files from our GLFW source.
The build folder will eventually contain our build files
Our include folder is for later use
We also have a resources folder that we'll end up copying into our application bundle. This is a Mac OS X construct that treats a folder of files that form an application to be treated as a single entity.
We'll eventually be putting other distributables here. On windows we'll need to treat this slightly differently.
Finally our source folder contains our source files. 

Lets look at Info.plist first. This is a file that will be copied into our bundles root and tells Mac OS X a little about our application. It's an XML file but with XCode installed you get a nice little plist editor that makes it easier to edit. Our file looks as follows:
The next file is our makefile:
# Compiler directives for Mac OS X
CC = gcc
CPP = g++
CFLAGS = -c -arch i386 -arch x86_64 -I../include -I../3rdparty
LDFLAGS = -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo -arch i386 -arch x86_64

APPNAME = glfw-tutorial
OBJECTDIR = ../build/Objects
CONTENTSDIR = ../build/$(APPNAME).app/Contents

OBJECTS = $(patsubst ../source/%,$(OBJECTDIR)/%,$(patsubst %.c,%.o,$(wildcard ../source/*.c)))
OBJECTS += $(patsubst ../source/%,$(OBJECTDIR)/%,$(patsubst %.cpp,%.o,$(wildcard ../source/*.cpp)))
RESOURCES = $(patsubst ../Resources/%,$(CONTENTSDIR)/Resources/%,$(wildcard ../Resources/*.*))

all: $(CONTENTSDIR)/MacOS \
  $(CONTENTSDIR)/Info.pList \
  $(CONTENTSDIR)/MacOS/$(APPNAME) \
  $(RESOURCES)
 
$(CONTENTSDIR)/MacOS: 
  mkdir -p $(CONTENTSDIR)/MacOS
 
$(CONTENTSDIR)/Info.pList: Info.plist
  cp -f $^ $@
  @chmod 444 $@

$(CONTENTSDIR)/Resources/%: ../Resources/%
  @mkdir -p $(@D)
  @chmod 755 $(@D)
  cp -f $^ $@
  @chmod 444 $@
 
$(CONTENTSDIR)/MacOS/$(APPNAME): $(OBJECTS) ../3rdparty/GLFW/libglfw3.a
  $(CPP) $(LDFLAGS) -o $@ $^

$(OBJECTDIR)/%.o: ../source/%.c
  @mkdir -p $(@D)
  $(CC) $(CFLAGS) -o $@ $<

$(OBJECTDIR)/%.o: ../source/%.cpp
  @mkdir -p $(@D)
  $(CPP) $(CFLAGS) -o $@ $<

clean:
  rm -R -f ../build

I'm not going into how this makefile works. Suffice to say it will do its job for what we're needing now.

Finally we need our main source file. Initially I started this project as a C++ project and it may evolve back into that. But as so far I'm not doing anything with C++ yet I thought it would be best to stick with C for now.

I have half a mind to make this tutorial focus purely on C. Don't get me wrong, I love C++, I have been writing software in C++ for well over a decade and it remains my favorite language. Staying with C however means this code is more portable and will make it easier to make the jump to iOS later on. We'll see how things develop.

This is pretty much a copy of the sample file on the main GLFW website so I take no credit for it. It is just our starting point. Also its OpenGL 1 which is great just to get the ball rolling but we'll be tossing this away very soon.

#include <GLFW/glfw3.h>

#include <stdlib.h>
#include <stdio.h>

void error_callback(int error, const char* description) {
  // we'll implement some error handling here soon...
};

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
  if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
    glfwSetWindowShouldClose(window, GL_TRUE);
};

int main(void) {
  glfwSetErrorCallback(error_callback);
  if (!glfwInit()) {
    exit(EXIT_FAILURE);    
  };
  
  GLFWwindow* window = glfwCreateWindow(640, 480, "Hello world", NULL, NULL);
  if (window) {
    glfwMakeContextCurrent(window);
    glfwSetKeyCallback(window, key_callback);
        
    while (!glfwWindowShouldClose(window)) {
      float ratio;
      int width, height;
      
      glfwGetFramebufferSize(window, &width, &height);
      ratio = width / (float) height;
      
      glViewport(0, 0, width, height);
      glClear(GL_COLOR_BUFFER_BIT);
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      glOrtho(-ratio, ratio, -1.f, 1.f, 1.f, -1.f);
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      glRotatef((float) glfwGetTime() * 50.f, 0.f, 0.f, 1.f);
      glBegin(GL_TRIANGLES);
      glColor3f(1.f, 0.f, 0.f);
      glVertex3f(-0.6f, -0.4f, 0.f);
      glColor3f(0.f, 1.f, 0.f);
      glVertex3f(0.6f, -0.4f, 0.f);
      glColor3f(0.f, 0.f, 1.f);
      glVertex3f(0.f, 0.6f, 0.f);
      glEnd();
      
      glfwSwapBuffers(window);
      glfwPollEvents();
    };
  
    glfwDestroyWindow(window);  
  };
  
  glfwTerminate();
};

With that all in place open up a terminal, cd into the macosx folder and type in: make
You should end up with a nice little Mac OS X application called glfw-tutorial that shows a little spinning triangle.

I've placed the source code so far on my github page:
https://github.com/BastiaanOlij/glfw-tutorial
Note that I'll be updating the files here as this tutorial progresses but I'll be placing  zip files in the archive subfolder containing the files as they are after each article.

What's next?

If I have some spare time later in the week I'll try and add an article to this getting it to work on Windows though I will be mostly targeting Mac OS X in this series.

After that we'll convert the code above to work using OpenGL 3. Be prepared to see this suddenly grow as the setup for OGL 3 is massive but once that is out of the way, trust me, life becomes cool:)



2 comments:

  1. I've made a few small changes most notably switching from C++ back to C for the time being.

    ReplyDelete
  2. Please note, in one of the last commits I had replaced the tabs in the makefile with spaces to make it easier to copy them into this writeup. As it turns out the make in XCode requires tabs to be used. If you copy the makefile from up above or grab the archived version of this source code you may need to edit the makefile and replace the spaces before each line and put in tabs.

    ReplyDelete