Author Topic: TUTORIAL #2 - Background Rasters  (Read 4444 times)

0 Members and 1 Guest are viewing this topic.

Offline stormbringer

  • Time moves by fast, no second chance
  • Amiga 1200
  • ****
  • Posts: 453
  • Karma: 73
    • View Profile
    • www.retro-remakes.net
TUTORIAL #2 - Background Rasters
« on: June 13, 2009 »
Let's move to something nicer now...

One of the cool features of these platforms of the past is that the programmer had the ability to directly access the machine registers, especially the registers of the graphic controller (the good old graphics card).

The ability to directly access the graphic registers allowed programmers to change things on the fly (such as colors, scrolling values, bitplane pointers, etc). On the Commodore C64 this feature was used so much that the makers of the Amiga pushed the possibility to control the display on the fly to a higher level.

Since the Amiga was a much more complex machine than the C64, they added a special co-processor to control (among other things) the display registers. The most used capability of this co-processors called the Copper, was to change the colors on the fly. Programmers used that feature to simulate the presence of hundreds of colors on screen (while the machine only was capable of displaying 32 true colors, 64 with some specific mode and 4096 with some very specific and hard to use mode, called HAM, for Hold and Modify). The copper was essentially controlled through so-called copper lists, simple programs that could be compared with their new age cousins: pixel/vertex shaders!

This tutorial here presents a simple way to display static background "raster colors" using a poor man's copper list type of thing.

Here I introduce the use of some new routines for managing the copperlist. The actual code is in the file copper_list_bkg.c and the definitions are in the file copper_list_bkg.h.

Let's have a look at the definition of our "copperlist".

First of all we define a structure for each entry in our list. Each entry has the line number where we change the color, and 3 values for RGB (0..255)

Code: [Select]
/*

  one entry in the copperlist

*/
typedef struct _CopperListBkgEntry
{
CuInt32 y_line;
CByte r,g,b;
} CopperListBkgEntry, *pCopperListBkgEntry;


Then we define our "copperlist" itself. Essentially a structure holing a pointer on our list of entries and the number of entries:

Code: [Select]
/*

  the copperlist object holding information about the list to draw and the number of entries in the list

*/
typedef struct _CopperListBkgObject
{
pCopperListBkgEntry list;
CuInt32 count;
} CopperListBkgObject, *pCopperListBkgObject;


And finally here is the code that draws the nice color lines on screen using OpenGL of course:

Code: [Select]
/*

  copper_list_bkg_draw()

  draw copper split line

*/
CBool copper_list_bkg_draw(pCopperListBkgObject obj, CuInt32 winWidth, CuInt32 winHeight, CuInt32 left, CuInt32 top, CuInt32 scaleFactor)
{
/* set 2d mode */
gltk_tools_set_2d_mode(winWidth,winHeight);

/* turn off lighting, texturing, blending, etc. we want to simulate background copper lists, so just draw lines of color
*/
glDisable(GL_LIGHTING);
glDisable(GL_TEXTURE_1D);
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDisable(GL_ALPHA_TEST);
glDisable(GL_SCISSOR_TEST);

/* since the spacing in terms of lines can vary between 2 entries, we use quads to quickly paint rectangles of color
in the worst case, the smallest rectangle will be 1 line height (times the scale factor), in the best case it will take the whole screen

modern computers are very efficient at drawing coloured rectangles, so large lists do not slow down here
*/
glBegin(GL_QUADS);

/* has colors? */
if (obj->list && obj->count)
{
CuInt32 c,y;

/* get first entry in the list and take it's start y line
*/
y=obj->list[0].y_line;

/* draw quads for the remaining entries in the list */
for (c=1; c < obj->count; c++)
{
CuInt32 yNew;

/* here we simulate the behavior of the copper. we prevent drawing backwards (bottom to top).
The copper obbey the scanline mechanism of the electron beam, going from top to bottom.
*/
if ((obj->list[c].y_line*scaleFactor) < y)
{
yNew += scaleFactor;
}
else
{
yNew=obj->list[c].y_line*scaleFactor;
}

/* set the color for our rectangle
*/
glColor4f(
((CFloat32)obj->list[c-1].r)/256.0f,
((CFloat32)obj->list[c-1].g)/256.0f,
((CFloat32)obj->list[c-1].b)/256.0f,
1.0f
);

/* draw it from the very left to the very right accoring to the given limits and the height picked from the list
*/
glVertex2i(0,top+y);
glVertex2i(winWidth,top+y);
glVertex2i(winWidth,top+yNew);
glVertex2i(0,top+yNew);

y=yNew;

}

/* we also simulate the copper's behavior here by extending the last entry of our list down to the bottom of the screen
*/
glColor4f(
((CFloat32)obj->list[c-1].r)/256.0f,
((CFloat32)obj->list[c-1].g)/256.0f,
((CFloat32)obj->list[c-1].b)/256.0f,
1.0f
);
glVertex2i(0,top+y);
glVertex2i(winWidth,top+y);
glVertex2i(winWidth,winHeight);
glVertex2i(0,winHeight);
}

glEnd();

/* done */
return(TRUE);
}

Naturally there are other ways to do this. But with this first example we start to use the *hardware acceleration* of OpenGL. On the C64 or Amiga, special circuitry was present to do such things. Here we instruct OpenGL to draw quickly through its hardware capabilities.


You have probably noticed that in this simple routine there is a call to a function called gltk_tools_set_2d_mode(). This function can be found in the file gltk_tools.c with the prototype definition in the file fltk_tools.h.

It simply sets the drawing mode in a regular 2d mode with the 0,0 coordinate being at the left/top corner of the window/screen, with x increasing from left to right and y increasing from top to bottom.

NOTE: there is no such thing as a 2D mode or a 3D mode in OpenGL. OpenGL works always with a projection matrix. Of course the gltk_tools_set_2d_mode() function tweaks the OpenGL projection matrix to simulate a 2D world. I won't discuss the details of this simple function now. It will all be explained in a more advanced tutorial about the GLTK code.

Now in the main.c file, in the paint method we simply make a call to the function that draws the rasters:

Code: [Select]
/*

  intro_on_paint()

  this method is used to paint the graphic elements on screen

*/
CError intro_on_paint(pCVoid userData)
{
/* clean the screen
*/
/* black background */
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

/* clear depth buffer with default value */
glClearDepth(1.0f);

/* disable the depth test */
glDisable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);

/* disable any clippin */
glDisable(GL_SCISSOR_TEST);


/* draw the copper list
*/
copper_list_bkg_draw(
&copperBkg_obj,
screenProperties.width,
screenProperties.height,
0,
0,
2 /* scale factor change this, depending on which resolution you want to simulate */
);

/* done */
return(0);
}

Of course in the WinMain() function (your program's entry point), right after we set the screen properties, we initialize the "copperlist" structure like this:

Code: [Select]
/* init our copper list here
*/
/* number of entries in our list */
copperBkg_obj.count=C_SIZE_OF(copperBkgList)/C_SIZE_OF(copperBkgList[0]);
/* address of the list */
copperBkg_obj.list=copperBkgList;



And finally the whole main.c file looks now like this:

Code: [Select]
/* RETRO-REMAKES TUTORIAL #2 - Background Rasters

Written by Stormbringer (stormbringer@retro-remakes.net)

This tutorial shows how to display some old-school background rasters using OpenGL
*/

/*

  include the GLTK (Graphics Library Toolkit)

*/
#include "..\gltk\gltk.h"




/* routines
*/

/* background copperlist manager
*/
#include "..\routines\copper_list_bkg.h"






/* objects
*/

/* the copper list object
*/
CopperListBkgObject copperBkg_obj;






/* raster table

the format is quite simple

1st value means the line number

2nd,3rd and 4th values are the 8bit RGB values for the color (0..255)

this table simulates the way a copper list (a list of instructions given to a *magic* co-processor found in the Amiga
circuitry that controlled all the other co-processors. One of it's main uses was to change values of some graphic registers on the fly.
Programmers on Amiga use this feature to create the illusion of hundreds of colors on screen by changing them every line or so.
Exceptional gradients and visual effects were achieved through this mechanism.

here we just display some static colors in a nice gradient...
*/
CopperListBkgEntry copperBkgList[]=
{
0,0,0,0,
81,0,0,17,
86,0,0,34,
91,0,0,51,
96,0,0,68,
100,0,0,85,
104,0,0,102,
108,0,0,119,
111,0,0,136,
114,0,0,153,
117,0,0,170,
119,0,0,187,
121,0,0,204,
122,0,0,221,
123,0,0,238,
124,0,0,255,
125,0,17,255,
126,0,34,255,
127,0,51,255,
128,0,68,255,
129,0,85,255,
130,0,102,255,
131,0,119,255,
132,0,136,255,
133,0,153,255,
134,0,170,255,
135,0,187,255,
136,0,204,255,
137,0,221,255,
138,0,238,255,
139,0,255,255,

158,153,153,153,
160,136,136,136,
161,119,119,119,
162,102,102,102,


174,0,136,136,
176,0,119,136,
178,0,102,136,
180,0,85,136,
182,0,68,136,
184,0,51,136,
186,0,34,136,
188,0,17,136,
190,0,0,136,
192,0,0,119,
194,0,0,102,
196,0,0,85,
199,0,0,68,
202,0,0,51,
205,0,0,34,
209,0,0,17,
218,0,0,0,
};



/*

  our screen "object" that holds the properties of our screen/window

*/
GLTKScreenProperties screenProperties;







/*

  intro_on_mouse_left_down()

  user pressed the left mouse button

*/
CError intro_on_mouse_left_down(pCVoid userData)
{
/* quit */
gltk_screen_quit();

/* done */
return(0);
}






/*

  intro_on_vsync()

  this method works like a timer interrupt. we will use it to track time changes and update the animation of or elements, etc

*/
CError intro_on_vsync(pCVoid userData, CFloat64 deltaTime)
{

return(0);
}







/*

  intro_on_paint()

  this method is used to paint the graphic elements on screen

*/
CError intro_on_paint(pCVoid userData)
{
/* clean the screen
*/
/* black background */
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

/* clear depth buffer with default value */
glClearDepth(1.0f);

/* disable the depth test */
glDisable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);

/* disable any clippin */
glDisable(GL_SCISSOR_TEST);


/* draw the copper list
*/
copper_list_bkg_draw(
&copperBkg_obj,
screenProperties.width,
screenProperties.height,
0,
0,
2 /* scale factor change this, depending on which resolution you want to simulate */
);

/* done */
return(0);
}




/*

  entry point

*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
/* hide cursor */
gltk_screen_show_cursor(FALSE);

/* init screen structure */
memset(&screenProperties,0,C_SIZE_OF(screenProperties));

/* name of the  */
screenProperties.name="My new cracktro";

/* define window dimensions
*/
screenProperties.width=640;
screenProperties.height=480;

/* request vsync

NOTE: in order for this option to work fine, you need to set your graphics driver Vertical Sync option on "Application Controlled"
*/
screenProperties.vsync=TRUE;

/* request refresh rate

NOTE: this will only work 100% in full screen mode, if the graphics card is capable of it
*/
screenProperties.vsyncFPS=60;

/* flag for window mode/full screen mode
*/
screenProperties.fullScreen=TRUE;

/* our method for painting the screen
*/
screenProperties.paintMethod=intro_on_paint;

/* our method for managing the time (where we change animation, etc)
*/
screenProperties.vsyncMethod=intro_on_vsync;

/* our method to intercept the left mouse button
*/
screenProperties.leftMouseDown=intro_on_mouse_left_down;


/* init our copper list here
*/
/* number of entries in our list */
copperBkg_obj.count=C_SIZE_OF(copperBkgList)/C_SIZE_OF(copperBkgList[0]);
/* address of the list */
copperBkg_obj.list=copperBkgList;



/* open the screen/window using the give properties
*/
if (gltk_screen_open(&screenProperties))
{
/* run the main loop, intercept system events, etc
*/
gltk_screen_run_loop(&screenProperties);


/* close screen window
*/
gltk_screen_close(&screenProperties);
}


/* exit and return code to system */
return(0);
}

Please note that we added another include at the top of the file:

Code: [Select]
/* background copperlist manager
*/
#include "..\routines\copper_list_bkg.h"

This header file gives access to the copperlist structures and paint function.

NOTE2: It is important that the graphical stuff is initialized before you attempt to open the screen/window. Once the window is created, it quite immediately receives a paint event, resulting in a direct call to our paint method. If what we want to draw is not properly initialized, it can result in ugly crashes or visual bugs.
« Last Edit: September 22, 2009 by Shockwave »
We once had a passion
It all seemed so right
So young and so eager
No end in sight
But now we are prisoners
In our own hearts
Nothing seems real
It's all torn apart