Before starting to read you should be familiar with C language and Atari XBIOS in general. You should know how Atari Falcon looks too .

note: Hardware accelerated XBIOS functions used in demo and following article aren't present in last version of CTPCI/Radeon drivers from December 2010, they will be available in the next release(date unknown).
It's worth to mention that the last version of drivers had incorrectly implemented Vsync() function(vertical sync) what prevents making double buffering at all. Currently data copying from local bus to CTPCI is very slow. BURST mode will be added for SDRAM to CTPCI communication (yes, only one directional), it may appear in future firmware updates, but there is no information what speed up we can expect.

Here is video capture of a program that I wrote to illustrate hardware accelerated functions accessible from XBIOS level. It was recorded from real machine.
Here is it's full source code which is a base of discussion for this article.

After this little warm up we can proceed little further...


Before trying to use Graphics card we have to check it's availability. Here is the simple function which does all the stuff:

checkHardware() function (rad_help.c)
int checkHardware(void){
//if any ot these tests will fail, we are not dealing with ctpci equipped ct60
long mch=0,vdo=0,ct60=0,pci=0,modecode=0;

if(Getcookie(C__VDO, &vdo)==C_FOUND){
    if (vdo!=0x30000) {
      logd("No Atari Falcon VDO found. Exiting...\n");  
      return 0;
    }         //check VDO falcon
} else{ 
    logd("No VDO cookie found. Exiting...\n");  
  return 0;

if(Getcookie(C__MCH, &mch)==C_FOUND){
  if (mch!=0x30000){ 
       logd("This machine isn't Falcon 030. Exiting...\n");  
    return 0;           //check if falcon
} else{ 
   logd("MCH cookie not found! Exiting...\n");  
  return 0; //nope
if(Getcookie(C_CT60, &ct60)!=C_FOUND){
  logd("CT60 not found! Exiting...\n");  
  return 0;
}                       //check ct60

if(Getcookie(C__PCI, &pci)!=C_FOUND){
  logd("CTPCI bus not found! Exiting...\n");  
 return 0;
}                        //check pci bios

//check graphics card availability
if((unsigned long)Physbase()block_op;     /* mode operation */
  fill.blk_color = fillColor; 		/* background fill color */
  fill.blk_x = destRect->Xpos; 		/* x pos in total screen */
  fill.blk_y = destRect->Ypos; 		/* y pos in total screen */
  fill.blk_w = destRect->width; 	/* width  */
  fill.blk_h = destRect->height; 	/* height */

Basically we need to set up SCRFILLMEMBLK structure and pass it to Vsetscreen() with CMD_FILLMEM flag. In this case we are setting second buffer to black. We can also set other block operation as well for different effects. See the full enum list in ctpci_defs.h.

The first thing that you should notice is that Radeon functions doesn't operate on addresses/pointers. You have to specify x and y coordinates in memory (starting point) and width/height of target rectangle without worrying about buffer color depth. So if we have 640x480 resolution and we want clear rectangle of the same size in logical and physical screen then for physical screen we would use (x=0,y=0,w=640,h=480) and for logical(after the physical screen) we would use (x=0,y=480,w=640,h=480). This will work similar for calculating sources and destination for other functions (block copying and so on).
For educational purposes, the content of logical screen is copied with CMD_COPYPAGE, which is another hardware accelerated function. CMD_COPYPAGE copies whole video memory buffer to another depending on current video mode (physical screen to logical or logical to physical).

// copy logical screen to physical buffer in hardware

We could do the same thing by calling hwFillScreenRadeon() for second time, but with different parameters targeting physical screen buffer (x=0,y=0,w=640,h=480).

After all of this everything is ready and we can draw, so we can enter our main loop and swap buffers with CMD_FLIPPAGE. VSync() is *mandatory* because you will get flickering:

while (bQuit){

    //check input
    //buffer swap