This shows you the differences between two versions of the page.
tutorials:optimus:screen_init_and_fast_clear_using_sport [2022/11/07 14:49] – created bugothecat | tutorials:optimus:screen_init_and_fast_clear_using_sport [Unknown date] (current) – external edit (Unknown date) 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== 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 %%[[https:// | ||
+ | |||
+ | | <code -> | ||
+ | #define SCREEN_PAGES 2 | ||
+ | static ScreenContext screen; | ||
+ | |||
+ | CreateBasicDisplay(& | ||
+ | </ | ||
+ | |||
+ | \\ \\ %%After we create the display, we use the ScreenContext to get back two other objects and store in pointers for later use.%%\\ \\ | ||
+ | |||
+ | | <code -> | ||
+ | static Item bitmapItems[SCREEN_PAGES]; | ||
+ | static Bitmap *bitmaps[SCREEN_PAGES]; | ||
+ | |||
+ | for(i=0; 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%% %%// | ||
+ | * \\ | ||
+ | * **Item%% %%// | ||
+ | * \\ | ||
+ | * **Bitmap%% %%// | ||
+ | * \\ | ||
+ | |||
+ | \\ %%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' | ||
+ | |||
+ | | <code -> | ||
+ | void clearScreen(ubyte test) | ||
+ | { | ||
+ | Bitmap *screenBmp = bitmaps[visibleScreenPage]; | ||
+ | memset((unsigned char*)screenBmp-> | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | \\ \\ %%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' | ||
+ | |||
+ | |||
+ | ===== 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.\\ | ||
+ | |||
+ | | <code -> | ||
+ | static Item VRAMIOReq; | ||
+ | static IOInfo ioInfo; | ||
+ | </ | ||
+ | |||
+ | The **Item VRAMIOReq** is the first item we need to initialize by calling the function // | ||
+ | |||
+ | | <code -> | ||
+ | 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 // | ||
+ | |||
+ | **The initialization to blit a single color is:** | ||
+ | |||
+ | | <code -> | ||
+ | void initSPORTwriteValue(unsigned value) | ||
+ | { | ||
+ | memset(& | ||
+ | ioInfo.ioi_Command = FLASHWRITE_CMD; | ||
+ | ioInfo.ioi_CmdOptions = 0xffffffff; | ||
+ | ioInfo.ioi_Offset = value; // background colour | ||
+ | ioInfo.ioi_Recv.iob_Buffer = bitmaps[visibleScreenPage]-> | ||
+ | ioInfo.ioi_Recv.iob_Len = SCREEN_SIZE_IN_BYTES; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | **The initialization to blit a fullscreen image is:** | ||
+ | |||
+ | | <code -> | ||
+ | void initSPORTcopyImage(ubyte *srcImage) | ||
+ | { | ||
+ | memset(& | ||
+ | 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]-> | ||
+ | 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, | ||
+ | * **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, | ||
+ | * **ioi_CmdOptions** : This is the exception to the above, and in the case of FLASHWRITE_CMD, | ||
+ | * **ioi_Recv.iob_Buffer** : This is the same in both transfer modes, and you are passing simply **bitmaps[visibleScreenPage]-> | ||
+ | * **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, | ||
+ | * **ioi_Send.iob_Len** : And finally the length of the copied image, just like we did with // | ||
+ | |||
+ | If you want to read further on these, you can also check [[https:// | ||
+ | |||
+ | And finally, at this point you can call //DoIO//, passing the // | ||
+ | |||
+ | | <code -> | ||
+ | void display() | ||
+ | { | ||
+ | DisplayScreen(screen.sc_Screens[visibleScreenPage], | ||
+ | |||
+ | visibleScreenPage = (visibleScreenPage + 1) % SCREEN_PAGES; | ||
+ | |||
+ | ioInfo.ioi_Recv.iob_Buffer = bitmaps[visibleScreenPage]-> | ||
+ | DoIO(VRAMIOReq, | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | \\ 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 // | ||
+ | |||
+ | * **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 // | ||
+ | * **Size of iob_Len** : This is restricted to a value that has to be divided exactly by 2k in my experiments, | ||
+ | * **No easy way to hardware scroll** : Initially as I was looking at the SPORT IO, I would assume that by changing the // | ||
+ | |||
+ | \\ You can find a lot of additional information in the docs btw (although I try to explain the basics here), for example in [[https:// | ||
+ | |||
+ | |||
+ | ===== 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 [[https:// | ||
+ | |< | ||
+ | |||
+ | </ | ||
+ | |||
+ | \\ | ||
+ | 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' | ||
+ | \\ | ||
+ | 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, | ||
+ | \\ | ||
+ | {{https:// | ||
+ | ===== Project files for this lesson ===== | ||
+ | |||
+ | {{: | ||
+ | |||