Introduction to OpenPTC

This document is an introduction to OpenPTC 1.0. It was last modified on June 22, 1999.


Table of Contents

1. Introduction

2. What is ... ?

2.1 ... a Format ?
2.2 ... a Surface ?
2.3 ... a Console ?
2.4 ... Exception Handling ?
2.5 ... this int32, short16, char8 business ?

3. How do I ... ?

3.1 ... request my colour format ?
3.2 ... open a Console ?
3.3 ... create a Surface ?
3.4 ... draw my stuff ?

4. Where is ... ?

4.1 ... PTC ?
4.2 ... the API specification ?

5. What the f@$* ?


1. Introduction

OpenPTC (PTC) is a cross-platform graphics API that was developed to make writing games and demos easy - and the result fast. What PTC will do for you is give you access to the framebuffer of the platform you are on, manage offscreen buffers for you and convert these buffers to whatever video format is available at that time.

This approach gives you the advantage that you can concentrate on writing your code for one colour format (32 bit RGB Truecolour would be a good idea) and leave the rest to PTC. You do not even have to worry about any speed losses when the format you use is not available on the target platform - benchmarks show that PTC will convert a 32 bit buffer to 16 bit faster than it can do a 32 bit to 32 bit copy (because less data has to be transferred).

In the next few sections, I shall explain the basic use of PTC, how to get access to the framebuffer, and so on. The structure of the document from now on is an evolutionary one, from the basics to the details:

One thing left to be said: This page is useless if you do not look at the examples included with the PTC distributions. Look at some real code while you are browsing through this, preferably look at the more simple examples such as "Random", "Palette" and some of the simpler demo programs such as "Fire" and "Tunnel".
 


2. What is ... ?

2.1 ... a Format ?

The Format class hold information about how pixels are composed in a specific framebuffer or offscreen buffer. When you use PTC, you will create instances of this class to specify the format you want to use in your application and the format you want the screen to have. PTC supports both indexed formats (e.g. 8 bit colour with a palette) and direct formats (e.g. 16 bit rgb 565 or 32 bit RGB888). Click here for usage information on the Format class.
 

2.2 ... a Surface ?

A Surface is a memory area (in system memory, not in video memory) that you can draw into if you don't want to draw directly to the screen. If you draw into a Surface, that gives you the advantage that the Surface will be automatically converted from the colour format you chose to the colour format of the Console that has been opened. In other words, if you use a Surface, you do not have to worry about converting your pixels to the format made available by the OS.
 

2.3 ... a Console ?

The Console is your interface to the world. Is contains the routines to access the framebuffer, and some simple keyboard routines. No matter what you do, you will always (well, most of the time, anyway) have to create a Console object at some point. On most systems, a Console will create a fullscreen display. On some (e.g. X11, DirectX if requested) a window will be created. To you, it doesn't really matter very much, you won't notice unless you perform special checks to ensure either working mode is used.
One thing you should note is that Console and Surface have the same baseclass in PTC, that is, a Console is of the same type as a Surface (namely they are both Base Surfaces). This should tell you that most operations available for Surfaces are also available for Consoles (e.g. you have to lock() both Consoles and Surfaces, more later).
 

2.4 ... Exception Handling ?

Exception handling is a very efficient way to handle errors in your applications. PTC keeps exception handling to a minimum to avoid overhead because current C++ compilers aren't too great at implementing it. Unless you decide otherwise, you will only have one try/catch block in your application (wrapping your  whole main() function).
PTC uses a single class called Error to report exceptions. If an error occurs, this can be caught and Error::report() will display the error and end the application. Look at the examples, you will understand them immediately (er.. :)
 

2.5 ... this int32, short16, char8 business ?

These types are defined to be the same size across all platforms. Here is what they are supposed to be:

We strongly suggest that you use those types for all pixel manipulation. Not only will this guarantee that your application works on any architecture, it will also prevent you from causing colour distortions through signed/unsigned incompatibilities. Not long ago, I had to answer a question from someone who had used a char* instead of char8* to draw his stuff and got his colours messed up (greetings to Nado :).


3. How do I ... ?

3.1 ... request my colour Format ?

Before you can create a Console or a Surface, you have to decide on the colour format you want to use in your routines. PTC offers you two different formats:

Accordingly, there are two different constructors offered to create format objects of the right type:

If you want an indexed colour mode, you have to use the first constructor. The converse is true as well, for direct modes use the second constructor. Note that at the moment only 8 bit indexed modes are supported, so if you want an indexed colour mode, make sure you pass 8 and nothing else to the constructor. In the second constructor, r, g, and b stand for the bitmasks of the colour components, that is the mask you use on a pixel of that format to select a single colour component. Look below for some common bitmasks.

By the way, for those who aren't too experienced with C++ yet, the a=0 in the second constructor means that this parameter may be omitted when creating a Format object and will be set to 0 by default then. Enough talking, here are some examples:

I still owe you the most common bitmasks, here they are:

Pitfalls

3.2 ... open a Console ?

There are quite a few ways to open a Console, even some platform specific ones, but those will not be discussed here (and are not normally needed). The first thing to do is to get a Console object:

Console console;

You can now open the Console using one of the open routines. I will just show you the cross-platform compatible way, there might be more detailed opening functions available to fine tune your program for a target platform. Check out the PTC documentation for you platform (which might or might not be included). Anyway, we will use the following open routine:

void open(const char title[],int width,int height,Format &format);

The parameters you pass are the string to appear on the title bar if your program is executed in a window (always pass a string, even if you compile under DOS), the width and the height of the video mode you want to initialise, and the format you want the video mode to have. Here is an example:

Format format(32,0xFF0000,0xFF00,0xFF);

Console console;
console.open("Hello PTC world",320,200,format);

This will instruct PTC to try and initialise a 320x200 Truecolour mode. Note that there is no guarantee that PTC will be able to initalise that mode. Normally, PTC will fall back to the next worse mode if the requested mode is not available. If you want to draw directly to the video memory later, you might care whether PTC did in fact initialise the mode you wanted or if it fell back to a different mode. You can check by looking at the Console's format and comparing it to the format you requested.

if(console.format()!=format) throw Error("I wanted exactly the format I requested. Now I'm angry");

Normally, however, you will use a surface and leave the drawing to PTC so you shouldn't worry what video mode got initialised.
 
 

3.3 ... create a Surface ?

Nothing is easier than creating a Surface to draw into. All you need to use is the constructor of the Surface class:

Surface(int width,int height,const Format &format);

So, if you want an offscreen surface of the same size as the Console in the previous example, you would use:

Surface surface(320,200,format);

You should use the same format for the Console and the Surface. Anything else would be a bit weird (although it is possible).
 
 

3.4 ... draw my stuff ?

First of all, you have to decide how you want to render your things, that is:

What should normally do is draw into an offscreen surface. Let's assume you created a Surface object in 32 bit truecolour mode, the actual screen mode is - whatever, it doesn't matter. Now you want to get your hands on the pixels, use the lock() function to get a pointer to the Surface's pixel data - it is a void* pointer so you have to cast it to whatever you want it to be:

int32 *dest;  // Truecolour this time!
dest=(int32 *)surface.lock();
...
surface.unlock(); // Unlock the surface

As opposed to a Console, a Surface guarantees linear memory, so you do not have to use a pitch (look below) to get to the next scanline, just hardcode the width you requested. So to put a pixel at (x/y) in a 320x200 surface, you can hardcode 320 as the width:

int32 *dest=surface.lock();
*(dest+y*320+x)=0xffffff;
surface.unlock();

Draw whatever you want into your surface. When you're finished, nothing will be on the screen yet, you have to copy your beautifully rendered surface to the console - after unlocking, etc:

surface.copy(console);
console.update();

This will copy your Surface to the Console and convert the pixel formats as needed. Note the call to console.update(), this is necessary so that things actually get shown on the screen, don't leave it out.

If you decide to draw directly to the console, then good luck. You will have to pray that your video mode is available and your application will only run in that specific video mode. (It still makes sense in some cases, for instance you might want to support only 8 bit indexed mode FOR WHATEVER REASON and disallow PTC to convert that mode to what's available). In this case, you use the lock() function of the Console object you allocated, in 8 bit indexed mode:

char8 *dest;
dest=(char8 *)console.lock();

When you are finished, you have to unlock the console again! And, while we are at it, beware of the following major

Big fat Pitfall

When you draw directly to the Console, PTC cannot guarantee you that the video memory is one big linear block of pixels. What PTC does guarantee is that every scanline is a linear block of pixels. However, to get from one scanline to the next, you have to use the pitch returned by the Console. The pitch is the amount of bytes you have to jump to get from the start of one scanline to the next. Here is some example code that fills the screen with random pixels in 8 bit colour mode:

char8 *dest;
dest=(char8 *)console.lock();

for(int j=0;i<console.height();j++)
{ for(int i=0;i<console.width();i++)
  *(dest+j*console.pitch()+i)=random(255);
}

console.unlock(); // Release console lock
console.update(); // update the console (don't leave that one out)

Pitfalls

This is the part of your code where you WILL make mistakes. There are some mistakes which are made quite frequently, TAKE CARE to avoid them:



4. Where is ... ?

4.1. ... PTC ?

The home page for OpenPTC is http://www.gaffer.org/ptc/

4.2 ... the API specification ?

All documentation is available from the PTC website (see above).
 


5. What the f@$* ?

So you have a question that has not been answered.. :) Well, there are quite a few ways to actually get an answer to your questions (please follow them in that order):

Questions that turn out to be common problems will be included in this document in the future.
 


Written by Christian Nentwich (brn@eleet.mcb.at) and updated to OpenPTC 1.0 by Glenn "Gaffer" Fiedler.
Please do send me any feedback or additions you may have.