Lesson 1 - Screen init and fast clear using SPORT

In the previous lesson, I would clean up the screen by filling the videoram with a memset, obviously the slowest method using the CPU. A faster way would be to use the hardware quad rasterizer (the CEL engine) to draw a quad that fits exactly the screen. But we neither need to do this, as the 3DO hardware provides a more convenient way for fast clearing of the screen before the start of each frame, using the so called SPORT bus. This will either clean the screen with a single color or blit a fullscreen bitmap to the screen much faster than even a fullscreen CEL quad would. That's good news as you don't need to erase the screen with slower methods each frame and you set it up once and forget about, however it's a bit limited and won't do for scrolling background (you'd still have to use the CEL engine to blit a quad fitting the screen and some additional tricks to scroll big background areas), only for cleaning with a single color or blitting a static background image.

Before I start, I should explain briefly some things about the code in the setup tutorial. In the previous lesson I wrote the smallest program possible to just init the screen and fill it with color but didn't explain anything in the process. It's time to explain some of the stuff and also provide links to the relevant documentation as the 3DO functions are using a lot of struct objects and they might seem confusing in the naming at first. But when everything is setup you can forget about it and start rendering things anyway.

Screen Init

To initialize the screen, we pass a ScreenContext object to the function CreateBasicDisplay. This will take the display type. For compatibility we use DI_TYPE_DEFAULT, which will default to 320x240 that works on both NTSC(60hz) or PAL(50hz) but there are other options like a higher 384x288 resultion for PAL which works on my real 3DO but will fail on NTSC. We also pass the number of video pages (I think the maximum possible was 6 the last time I tried, but 2 are enough for now).

#define SCREEN_PAGES 2
static ScreenContext screen;

CreateBasicDisplay(&screen, DI_TYPE_DEFAULT, SCREEN_PAGES);


After we create the display, we use the ScreenContext to get back two other objects and store in pointers for later use.

static Item bitmapItems[SCREEN_PAGES];
static Bitmap *bitmaps[SCREEN_PAGES];

	for(i=0; i<SCREEN_PAGES; i++)
	{
		bitmapItems[i] = screen.sc_BitmapItems[i];
		bitmaps[i] = screen.sc_Bitmaps[i];
	}

It might be confusing at first with all these objects, but here is their use:


By accessing the pointer bm_Buffer attached to the Bitmap object, we can directly memset there to fill the screen. Of course, at another place in the code I am flipping visibleScreenPage every frame, remembering to use that index for every object we need to pass to the functions. A more abstract framework could be build to not bother with all these additional structs (sometimes I have to remember which does what) but for tutorial's sake it does it for now. Further documentation for the struct is here: Bitmap Item. You can also look at the whole struct in c:\3DODev\includes\graphics.h as I realize it's not shown in the docs (to learn some things, one really needs some searching in the SDK headers, documentation is not perfect).

void clearScreen(ubyte test)
{
    Bitmap *screenBmp = bitmaps[visibleScreenPage];
    memset((unsigned char*)screenBmp->bm_Buffer, test, screenBmp->bm_Width * screenBmp->bm_Width * 2);
}


Finally, at the end of each frame, I call DisplayScreen, giving as first parameter the screen Item that is current visible. The second paremeter has to be zero. I've read in the documents that this could be used for stereoscopic or interlaced screen, so interestingly enough they were planning the system for 3D stereoscopic rendering. In our case it's zero (basically NULL). We also flip-flop visibleScreenPage for double buffering. Many objects have to be tackled to setup a basic rendering framework, but once you've set it up, you can reuse the framework and forget about it.

void display()
{
    DisplayScreen(screen.sc_Screens[visibleScreenPage], 0 );

    visibleScreenPage = (visibleScreenPage + 1) % SCREEN_PAGES;
}


On a final note, I am not explaining everything here, for example how the videoram datastructure for software rendering is not totally linear, but goes in a zig zag fashion. For now I memset a random byte into it, and it's 320*240*16bpp so I give the right length to memset. In a future tutorial I will explain how software rendering works in the main video mode, however from my tests I realized that instead software rendering over a texture beloning to a CEL that you draw as a fullscreen quad might be a better solution, since it's both linear and you can set lower resolutions and bit depths for your stretched texture which could also be easier and faster to use if you ever need to software render (where in most cases you won't). I'll dedicate a chapter after explaining the basics on CEL rendering and other fundamentals.

Using the SPORT

Our second task after initializing the screen, is to setup a fast framebuffer cleaning method, right before starting to render anything meaningful to the screen. This can either be a single color screen cleaning or a static background blit, and we use certain IO functions that will do that very fast for us through the SPORT bus. In order to start doing that, we have to use two objects.

static Item VRAMIOReq;
static IOInfo ioInfo;


The Item VRAMIOReq is the first item we need to initialize by calling the function CreateVRAMIOReq.

VRAMIOReq = CreateVRAMIOReq();

The next step is to initialize the IOInfo struct before calling DoIO for a blit. The IOInfo struct is pretty involved, so if you want to have a look at all it's components open c:\3DODev\includes\io.h in your HD or read further information in the section Performing I/O. But because things are pretty involved, and this IO object is also used for a lot of other things outside of SPORT, it will be better to do it later at your own will and read the basics in this tutorial instead.


The initialization to blit a single color is:

void initSPORTwriteValue(unsigned value)
{
	memset(&ioInfo,0,sizeof(ioInfo));
	ioInfo.ioi_Command = FLASHWRITE_CMD;
	ioInfo.ioi_CmdOptions = 0xffffffff;
	ioInfo.ioi_Offset = value; // background colour
	ioInfo.ioi_Recv.iob_Buffer = bitmaps[visibleScreenPage]->bm_Buffer;
	ioInfo.ioi_Recv.iob_Len = SCREEN_SIZE_IN_BYTES;
}


The initialization to blit a fullscreen image is:

void initSPORTcopyImage(ubyte *srcImage)
{
	memset(&ioInfo,0,sizeof(ioInfo));
	ioInfo.ioi_Command = SPORTCMD_COPY;
	ioInfo.ioi_Offset = 0xffffffff; // mask
	ioInfo.ioi_Send.iob_Buffer = srcImage;
	ioInfo.ioi_Send.iob_Len = SCREEN_SIZE_IN_BYTES;
	ioInfo.ioi_Recv.iob_Buffer = bitmaps[visibleScreenPage]->bm_Buffer;
	ioInfo.ioi_Recv.iob_Len = SCREEN_SIZE_IN_BYTES;
}


Let's examine the variables and the differences between write and copy, one by one.
If you want to read further on these, you can also check SPORT Transfers to the Frame Buffer and in more detail The SPORT Device.

And finally, at this point you can call DoIO, passing the VRAMIOReq and the ioInfo. Because we are using double buffering, we need to pass the visible screen page every time, so we alter ioi_Recv.iob_Buffer to point to the next bitmap buffer before we call to do a transfer. The final display main function (called for each frame) is:

void display()
{
    DisplayScreen(screen.sc_Screens[visibleScreenPage], 0 );

    visibleScreenPage = (visibleScreenPage + 1) % SCREEN_PAGES;

	ioInfo.ioi_Recv.iob_Buffer = bitmaps[visibleScreenPage]->bm_Buffer;
	DoIO(VRAMIOReq, &ioInfo);
}

You might notice that we increase (and flip between the pages) the visibleScreenPage before doing an IO. That's because we want to avoid tearing, so when DisplayScreen is called then say vram page 0 becomes visible, but the clearing of the screen before the next frame starts happens on page 1 so that we don't notice any flickering. When we add specific code in the future, to render CELs or directly to the videoram or anything, it's going to happen just before DisplayScreen (or before even calling display() in the main loop). We might have to take care of vsync among other things too.

That reminds me of few additional points I should briefly mention:
You can find a lot of additional information in the docs btw (although I try to explain the basics here), for example in Graphics Folio Calls there is information on functions we have used (CreateVRAMIOReq and DisplayScreen), while Lib3DO functions has some auxillary functions that we might use in the future (We used CreateBasicDisplay already and there are several useful CEL functions we might need in the future). The original docs are a bit of a mesh sometimes, or sometimes informations can be found in various places but not linked together, while another way to learn is to simply search in the header files of where you have installed the SDK. But I'll try to summarize the basics in these tutorials and sometimes refer to various places at the docs too.

Loading a Bitmap

In the project folder for this lesson, I've added another tool called BMPTo3DOImage by Charles Doty(RasterSoft). In our example, this will take a 320*240 BMP image (the one I use is in 24bit color, other formats might or might not work) and convert it to a row data format (preeceded by an IMAG header) that is ready to be loaded using a Lib3DO function called LoadImage. There is an example bmp of the Andromeda galaxy in the archive and a batch file that generates the img file and copies it to the CD data folder.

backgroundBufferPtr = LoadImage("data/background.img", NULL, (VdlChunk **)NULL, &screen);

The first paremeter is the location of the image in the CD folder. The second parameter given as NULL will force the function to allocate a page aligned VRAM buffer for us. This is important for the SPORT transfer that has to happen from VRAM to VRAM. If you prefered to keep more backgrounds in memory for future display, you could pass your own pointer anywhere in memory, but then you'd have to copy the picture from memory to a page aligned buffer in VRAM (which you can also easilly get by initing more screen pages than two in the beginning). The 3rd parameter is something I'll cover in a future tutorial (I haven't even played with VDLs at this point) and it shouldn't worry you. And finally the 4th parameter is the ScreenContext object.

Finally, I've added some input to switch between different SPORT display methods. Press A, B or C to display single color, random colors (coming as horizontal stripes for reasons I'll explain in the software rendering tutorial) or the full Andromeda picture. I'll talk about input (also mouse) in a future tutorial, even though the input code is pretty straightforward (just don't forget to call InitEventUtility(1,0,LC_Observer) at startup else it won't work). Also, we do have to call OpenGraphicsFolio() in the beginning to use the Graphics Folio functions, although commenting this out also worked for me without a problem. But it's good to keep it. We will call for init other Folios (for math, audio, etc) in the next tutorials, so the InitSystem function will stay and be populated with more.



Project files for this lesson

lesson1.zip