Site Tools


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:

  • ScreenContext screen : The core object we initialize to setup the display and get back all the other necessary objects needed for rendering. Initialized with CreateDisplay and passed at the end of the frame to DisplayScreen

  • Item bitmapItems[SCREEN_PAGES] : We don't use it yet in this tutorial, but it's the fundamental object one passes to DrawCels to start rendering quad cels on the screen. It's also used for a lot of other things in some functions, like enabling/disabling the interpolation or super-clipping. We will see those in future tutorials. There is one object assigned for each screen page.

  • Bitmap *bitmaps[SCREEN_PAGES] : This object gives us access to the vram, in case we decide to directly software render (something we won't need to do most of the times if we learn to draw cels). This object also assigned for each page separately.

Finally, 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);

On a final note, I am not explaining everything here, for example how the videoram data structure 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 belonging 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)
    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)
    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.

  • ioi_Command : That's pretty straightforward, it's an enum for the SPORT operation and there is FLASHWRITE_CMD to blit a single color and SPORTCMD_COPY to copy from a fullscreen bitmap to the screen. If you look further there is also SPORTCMD_CLONE to copy a repeated pattern but this is so restricted and weird and won't be needed now, that I will talk briefly about it at the end.
  • ioi_Offset : This is important in the case of FLASHWRITE_CMD as it holds the single color (or rather the 32bit value for a two near pixels, will explain soon) to blit. In the case of SPORTCMD_COPY, set it always to 0xffffffff to blit the exact bitmap we will later select to the screen without any processing. This works as a mask and if you set lower values you could reduce some bits on the color channels during blitting.
  • ioi_CmdOptions : This is the exception to the above, and in the case of FLASHWRITE_CMD, this value is now a mask for the sent color. SPORTCMD_COPY doesn't have to bother with this.
  • ioi_Recv.iob_Buffer : This is the same in both transfer modes, and you are passing simply bitmaps[visibleScreenPage]→bm_Buffer which is the bitmap destination buffer of the visible screen.
  • ioi_Recv.iob_Len : This is also the same in both cases, the total length of the screen bitmap in bytes. This is 320*240*2 bytes (because the vram is 16bit) in our case, but could be a different value, however there are some limitations to be divisble by 2048 bytes which I will briefly discuss later. For our simple purposes it can be the full screen size.
  • ioi_Send.iob_Buffer : In the case of SPORTCMD_COPY, we obviously need to also pass a pointer to the source buffer to copy. In the rest of this tutorial I'll show you how to load and convert such an image in an array that we will pass here after.
  • ioi_Send.iob_Len : And finally the length of the copied image, just like we did with ioi_Recv.iob_Len. SCREEN_SIZE_IN_BYTES btw is a value I defined somewhere else in my code, not part of the API.

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:

  • When to use Vsync : In the next chapter I might attempt to setup a vsync io to restrict framerate at 50 or 60fps (depending on the system). But there is a catch. Whenever you are doing a SPORTCMD_COPY IO, you get vsync for free. If you make the mistake to also call for vsync by yourself, vsync happens twice and you drop at 30 (or 25) fps max. That's not the case for FLASHWRITE_CMD where it's unrestricted. In my framework (not included in this tutorial code yet), depending on whether I decide to clear the screen with a single color or a bitmap, I enable or disable my own vsync code to have it synced at 50-60fps for all. You might also need to disable the background copy and only use single color clear in the case you are doing benchmarks where you want to have the framerate to not lock at the screen refresh but exceed.
  • ioi_Offset is 32bit : Because of the nature of the hardware, even if a pixel is 16bit in the 3DO videoram, a 32bit value is passed for FLASHWRITE_CMD (and also for all types in the pixel mask). This is going to draw two pixels and they will be vertically arranged (because of the zig zag videoram structure which I will describe in a future chapter). So, if you pass a totally random value, you except to see two alternating color scanlines. For that reason, the function setBackgroundColor I provide in the tutorial code, will take a 16bit value and shift it and create the full 32bit value for single color fill to pass to the struct. I also have an example where I do set random value so that you see what I mean by that. For our purposes we just need to pass a proper 32bit value where low and high 16bit part will be the same.
  • Size of iob_Len : This is restricted to a value that has to be divided exactly by 2k in my experiments, but the docs recommend to call GetPageSize(MEMTYPE_VRAM) for that (it could return a different value, maybe in future hardware like the 3DO M2?). Page Size is not the total size of the videoram, but the 2k chunk size for some unknown (hardware?) reason. The fullscreen 320*240*16bpp size happens to be divided directly by 2k so it's fine.
  • No easy way to hardware scroll : Initially as I was looking at the SPORT IO, I would assume that by changing the ioi_Recv.iob_Buffer to point at later addresses, I could achieve hardware scrolling. There is a restriction to this and from what I remember it has to be restricted by vram page boundaries. There are ways to abuse the offset with certain tiled graphics to fake some kind of repeating pattern scroll, or use the mask with a noise texture and among with altering the mask values achieve a TV noise effect (something that I think was done in GEX the same way), but those are specialized tricks and I might dedicate a tutorial much later in the future. But for now it's enough to blit a static background or fill a color and there are better ways using the CEL for 2d scrolling.

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

tutorials/optimus/screen_init_and_fast_clear_using_sport.txt · Last modified: 2022/10/02 19:41 (external edit)