Author | Justin Ronco |
Audience | Team Ab Ovo |
Date | March 02, 2003 |
Panic? | don't |
I think I want to change the interface for the backgrounds; the system we designed before break appeares to have a flaw. The "Assertable" object system is, in my opionion, really cool; however it doesn't work very well for backgrounds. Backgrounds use 64 KB of video memory (more in bitmapped modes) to store character and tile-map data. The problem, however, is that there's no telling where various pieces of information will be stored.
The Assertable object system is based on a simple (yet cool) context stacking scheme. When you need to change something in the video system, you create the appropriate new object; the new object remembers the old object so that when the new object is destroyed the old one can be reasserted. This system works very well for wrapping the video registers and things with a known size and location (palettes and the OAM).
Unlike most areas of video, background data is oddly placed and sized. Data in video memory varies in size and location. The title screen uses the entire lower 64 KB of video memory (plus some) for bitmaps. The game will likely divide this region into different sized blocks for characters (tile bitmaps) and tile-maps for the two (or more?) backgrounds in use.
Simple stacking doesn't work well for maintaining video memory. When you allocate and initialize a block of memory for a tile-map, who knows what you're overwriting. Allocating 8KB of character bitmap storage data could easily everwrite four tile-maps used by the preceding game context. How does one efficiently keep track of what is resident where so as to reassert what is needed when we step back up into a previous game state (context)?
Also, I should state that the VideoChars class devised may be too simple. The VideoChars's memory management system assumes that video memory can be divided into 5 easy segments - one for each character block. The reality appears to be, however, that there are about 34 granular (?) regions within the basic video memory area - 32 2KB chunks in the beginning followed by two 16KB chunks at the end (for sprites/bitmap graphics). The problem here being both the number of assertable regions that would need to be maintained and the fact that a single resource (say, a tile-map) can span multiple regions.
So, how do we keep track of everything under the assertable system? I say we don't. Yes, we could expand the assertable system to account for resources that can occupy for more than one state element; for instance tile-maps that occupy 16 KB vs. tile-maps that only occupy 256 bytes. I think a better solution is what I'm calling the fiefdom model.
Before beginning, let me present my general concept of the class heirarchy for our video package. The following graphic shows the class relationships.
The Fiefdom Model basically dictates that there is one assertable object class responcible for maintaining the contents of all of video memory. VideoFief is the class of objects that assert the contents of video memory. The class definition looks something as follows:
class VideoFief : public Assertable { VideoSerf* serfs[ 9 ]; public: VideoFief( ); ~VideoFief( ); /* Getting workers */ void insertSerf( VideoSerf* papa ); /* Sanity checking */ bool isValid( ) const; /* Reloading */ virtual void reassert( ); };
The VideoFief, from the outside, appears to manage video memory. From the inside, it actually uses a set of "Serfs" to do the actual memory management. A Serf is some object that provides an interface for retrieveing it's size and location information as well as for causing a reload. The base class for serfs is as follows:
class VideoSerf { public: virtual void load( ) = 0; virtual u8 getStart( ) const = 0; // which of the 48 sblocks? virtual u8 getSize( ) const = 0; // how many sblocks? };
The VideoSerf class provides an interface for all objects that might wish to own and manage a block of video memory. The interface provides the Fief a method to check for overlap and to cause reloading of data. Checking for overlap can be used to decide what needs to be reloaded when a Fief gets destructed, and can also be used to catch buggy memory layouts on our part.
VideoSerf will have a number of derived classes. In particular there will be classes for managing text and rotational (if needed) tile-maps as well as a serfs for managing character and bitmap data. The exact nature of these is still in development, but let me propound my concept of a text tile-map serf:
class TextMapSerf : public VideoSerf { u8 gridsize; // bit 0: has wide box, bit 1: has tall box u8 dst; // Where in video memory does this go public: /* ctor: the map needs to know where it goes and what it's dimensions are. The first parameter acts as a simple index into memory for where the data will be stored in memory. The second parameter indicates the desired grid size (and implicitly the memory size needed). */ TextMapSerf( u32 dst, u16 gridsize ); /* Write to tile memory. Set the status of a particular tile. I think this function might be better off if it was made protected instead of public. */ virtual void setTile( int x, int y, u16 tileinfo ); /* The basic VideoSerf functions can be handled easily. Notice, however, that load is not implemented. TextMapSerf does not itself maintain enough information to reload the tile-map. Thus, TextMapSerf can only act as a base class for a real text-map manager. */ virtual u8 getStart( ) const { return dst; } virtual u8 getSize() const { if( !gridsize ) return 1; else if( gridsize == 3 ) return 4; else return 2; } };
Backgrounds do stack well, and thus work nicely in the assertable object model. The only thing I think that really needs to be done with backgrounds is to provide an object that can reassert itself in terms of shape/size, source of character data, source of map data, and scrolling. TODO.
The class "Background" acts as the base class for all types of backgrounds. The following code shows the core interface for the Background class.
class Background : public Assertable { protected: Background( Assertable*& a ); // always a bridesmaid, never a bride public: /* Priority */ void setPriority( int p ); int getPriority( ) const; /* Maybe scrolling goes here? */ /* Character data */ void setCharData( VideoSerf* serf ); VideoSerf* getCharData( ); };
The only constructor for the Background class is protected. The intent is that Background's will only be used as base classes. Depending on what you want (and what video mode you are going to use) you would construct an object of some particular base class of Background.
The two classes derived directly from Background would be BitmapBackground and TileBackground. BitmapBackground would setup and maintain scrolling and scaling for bitmapped based backgrounds (modes 3-5). TileBackground would be used for modes 0-2. TileBackground itself is intended for use only as a base class; TextBackground and RotationalBackground derive from TileBackground and would be the actual objects constructed by the user.
class BitmapBackground : public Background { public: BitmapBackground( ); }; class TileBackground : public Background { protected: TileBackground( Assertable*& a ); public: /* Character data */ void setCharData( VideoSerf* romserf ); VideoSerf* getCharData( ); }; class TextBackground : public TileBackground { public: TextBackground( int which, TextMapSerf* serf=0 ); /* Tile data */ void setMapSerf( TextMapSerf* serf ); // size is set from serf! TextMapSerf* getMapSerf( ); }; class RotationalBackground : public TileBackground { public: RotationalBackground( int which ); /* Tile data */ void setMapSerf( RotMapSerf* serf ); // size is set from serf! RotMapSerf* getMapSerf( ); /* Wrapping */ void setWrapping( bool bWrap = true ); bool getWrapping( ) const; };