[Alephmodular-devel] TS: Display Abstraction Draft
Status: Pre-Alpha
Brought to you by:
brefin
From: Br'fin <br...@ma...> - 2003-02-28 04:53:54
|
This document is only coming together slowly. But here's the full document as it currently exists, including the bits and pieces I've been posting recently. I recently added some thoughts on file arrangement and CDisplay to this version of the document. -Jeremy Parsons $Id: ... $ Note: This document is still evolving. But is being used to guide the process and is being updated to reflect final code. Technical Specification: Display Hardware Abstraction First there was screen.h. But it had some drawbacks and limitations. A mild macintosh bias in soem of the interfaces (Which is probably stronger in the functions that provide buffers to be drawn upon) But the primary problems relate to legacy. Also, screen is reponsible for viewport effects as well. We do not wish to handle these, but the lower level display abstractions. Older computers rarely had a screen bigger than 640x480 and Marathon itself, while it was geared to also run on slower machines, didn't have anything in mind for addressing way faster machines. Way larger screen estate. Nor did it ever expect windows to offer their own back buffer support. We need to offer a CDisplay class that performs the following: Manages either a window or fullscreen as a legitimate display target. And allows toggling between the two. Manages corresponding buffers that are passed to code to draw upon Manages implementation of screen based effects, including fades and pallets Owns the visibility of the mouse A note on buffers: Existing code assumes that it will always be dealing with an appropriately sized buffer. So Low Res mode will be drawing in a teeny display buffer, and if you're playing 640x480 with a HUD, then the buffer will be 640x(480-HUD height) And things like the interface tended to be drawn directly to the screen window. We are going to need the ability to pass code a larger than expected buffer with appropriate offsets and limits such that the rendering code will only use a portion of it. Current code currently uses a bitmap_definition structure to abstract the data. This is used both for screen rendering AND for shape information, both walls and RLE transparent frames. There are potentially three distinct kinds of bitmaps. A solid bitmap (screen, walls) and two forms of RLE bitmap encoding (M1 and M2 shapes) GUI Support: Windows or Fullscreen Selected at initialization, but toggleable. Windows events Only occur if window mode is on Display will need to support is_display_event (akin to is_dialog_event) handle_display_event manages window updates, repositioning click_callback Clicks in the window need to be passed back to controlling app callback can be changed many times. For instance, during gameplay, you can have a noop_click_callback that does zilch Sizes Tries to fit requested screen sizes into available screen space. mouse/menu hiding/revealing Game Support: get_current_buffer Returns the current back buffer for drawing on Returns a bitmap_definition that has been prepared for us set_gamma set_fade_effect swap_buffers duplicate_buffer ensures that all available buffers matchup Handling screen sizes Windows You may not request a window equal to or bigger than the current resolution We do no resolution switching in windowed mode. We do no screen color mode switching in windowed mode 256, Thousands, Millions should operate, but only affect the window, not the screen Full screen Display manager should handle multiple screens Minimum support, allow screen selection Bonus support, use multiple screens at once We most likely need support for multiple rendering passes/partial rendering for this. (ie, render rectangle x,y,h,w of effective view rectangle total_x, total_y, total_h, total_w) Display provides a list of available screen sizes and can answer bit depth questions for specific sizes GUI requests a specific size (height/width) and depth we find the largest available size that fits the request requested rectangle is offset to be centered in display area Requests to erase a buffer to a color apply to entire buffer Displays CDisplay is a singleton that controls the communication between the game's shell and the host operating system. You want to know about available resolutions, pick a screen, or do a fade? Here's the platform abstraction to speak to. The details about screen management and resolution switching will be hashed out later. Screen effects such as fades are a two part process. An in-game element does the actual fade state management and animation while CDisplay does the actual setting of palettes. Methods for drawing to the screen: get_working_buffer() Returns the current back buffer. Most likely this will be some kind of clipped representation of the true buffer. flush_working_buffer() When you're all done drawing and want your handiwork to show up on the screen, call flush_working_buffer. For instance, under an implementation that uses double buffering, this would perform an actual buffer swap. Other implementations may already be using an element's back buffer and just need to have the screen be refreshed with the updated contents. This effectively invalidates the buffer from get_working_buffer, so you should get call get_working_buffer again instead of trusting the existing buffer. Buffers A buffer is a complete description of a surface that one can draw upon. It is an abstract base class with platform specific implementations. For instance, under Macintosh, a buffer would wrap around a GWorldPtr/CGraftPtr/GrafPtr. Typical usage would be: Request current buffer from display system (it is limited as in dimensions, display system knows this) Lock down the buffer Get a bitmap from the buffer Render operations on the bitmap Release the bitmap Perform drawing operations on the buffer (access to platform native elements available?) Unlock the buffer Ask display system to swap buffers A buffer helps support higher level options such as copying portions and displaying text using OS specific calls. world_pixels would be a buffer. Buffer attributes Height/Width Bit Depth Clut (8 bit depth only?) Here is a set of methods for buffers suggested by how existing Marathon code uses world_pixels: Locking pixels. (std::auto_ptr<CBuffer::PixelLock> get_pixel_lock) When dealing with buffers on Macintosh, one must lock down pixels before doing certain operations. Such as rendering onto the bitmap or using copybits. With an auto_ptr based mechanism, the lock is automatically freed once out of scope. A corrolary of this is that directly requesting a buffer's bitmap should make sure the corresponding bitmap has locked the buffer and has control of the lock. Trying to double lock or doubly unlock a buffer should assert. Instantiating and updating the internal buffer. Both myUpdateGWorld and myNewGWorld are called, depending on whether or not world_pixels already exists. Updates included depth, bounds, clut and associated screen device and flags. This may be protected functionality handled by the display abstraction. Since we are adding funtionality to the buffer (like by being able to use pre-existing buffers as if we had allocated them, such as the natural back buffer of a window) these may not have direct correlations to the outside world anymore. Accessing pixels. (std::auto_ptr<CBitmap> or facsimile) Gets a bitmap preconfigured by the buffer. Trying to access the pixels with another access on the pixels outstanding is a failure and should assert. Clipping requests (std::auto_ptr<CBuffer::ClipLock> lock_clipping_region(left/top/right/bottom)) Specifies boundaries for drawing operations. For instance, during map drawing you may wish to draw wildly all over the place. But only the details that actually fall within the clipping area should be displayed. This is apt to be a protected operation performed for you by a clipping buffer. Boundary requests: aka GetPortBounds Buffers will either be allocated for you. (Ask the Display manager for the buffer to use!) or by knowingly using the platform specific buffer calls. There will be cause to have clipped buffers. That is, a buffer that has its own height and width, but which is actually a window into a larger buffer. For instance, the raw display buffer could be your screen at 1024x768 and the outermost buffer would need to point to this. But for game purposes, it only cares about a 640x480 space for the entire display area. And on top of that it typically only uses 640x320 for the game world, and the remaining space for the HUD. A clipped buffer performs two operations for you. It is automatically clipped. And its origin is shifted to the top/left of the clipping region. CBuffer hierarchy CBuffer Root Class CPlatformBuffer : public CBuffer CBuffer class the is root for platform specific buffer classes. For instance, direct calls for clipping would be within this class's interface. CBuffer_Carbon would be a descendant of this. CClippedBuffer : public CBuffer CBuffer class that does origin translation and clipping effects to act as a subset of its root buffer. world_pixels usage: game_window_macintosh.cpp used within draw_panels HUD is drawn in back buffer copy bits called to send it to the screen preprocess_map_mac.cpp world_pixels is used as the offscreen buffer for saved game preview pictures :/ screen.cpp world_pixels is setup during initialize_screen world_pixels provides the pixels for use in render_screen world_pixels clut is updated to sync with screen in change_screen_clut world_pixels is used in render_computer_interface world_pixels is used in render_overhead_map world_pixels is copied from in update_screen (and used as source for quadruple_screen!) screen_drawing.cpp _set_port_to_gworld encapsulates swapping to world_pixels for gWorld foo. Bitmaps A bitmap, in contrast to a buffer, is a low level object that explicitly describes the pixels associated with a buffer. It knows very little, but is a stream of bytes in memory with precalculated row-addresses for jumping quickly to a scan line. RLE encoded shapes are also implemented as a bitmap that is owned by a collection. world_pixels_structure would be a bitmap. Here is the current layout of a bitmap. enum /* bitmap flags */ { _COLUMN_ORDER_BIT= 0x8000, _TRANSPARENT_BIT= 0x4000 }; const unsigned int FILE_SIZEOF_bitmap_definition = 30; struct bitmap_definition { int16 width, height; /* in pixels */ int16 bytes_per_row; /* if ==NONE this is a transparent RLE shape */ int16 flags; /* [column_order.1] [unused.15] */ int16 bit_depth; /* should always be ==8 */ int16 unused[8]; pixel8 *row_addresses[1]; //serialization friend AIStream& operator>>(AIStream&, struct bitmap_definition&); friend AOStream& operator<<(AOStream&, struct bitmap_definition&); }; /* ---------- prototypes/TEXTURES.C */ /* assumes pixel data follows bitmap_definition structure immediately */ pixel8 *calculate_bitmap_origin(struct bitmap_definition *bitmap); /* initialize bytes_per_row, height and row_address[0] before calling */ void precalculate_bitmap_row_addresses(struct bitmap_definition *texture); void map_bytes(uint8 *buffer, uint8 *table, int32 size); void remap_bitmap(struct bitmap_definition *bitmap, pixel8 *table); We could work within this to provide working on a subset of the bitmap. Such work would create create the following needs. Such a workd would also only apply to a non RLE bitmap. width is width of subset height is height of subset offset_column, offset_row is added for subsets bytes_per_row is unchanged flags is unchanged bit_depth is unchanged row_addresses Is unchanged for shapes for a subset row_address[0] points to the resultant byte due to offsetting. This position is base_address+(offset_row*bytes_per_row)+offset_column. Adding bytes_per_row to this will hit each subsequent row with the handy offsets already applied. What is interesting to note is that making a class of bitmap_definition is fairly hard. Between my requirements that limit our ability to subclass and derive. ... Strike that, looks like subclassing can be handled 'nicely' When loading collections, the first pass of shapes allocates size based upon sizeof bitmap_definition, and then copies the existing stream into place. We can intercedede in this by doing an 'in place new' (new(void *) T(val); ) that allocates and constructs the bitmap at the desired point. Roughly this would look something like void *source= (raw_collection + OffsetTable[k]); void *destination= (NewCollection + NewCollLocation); int32 length= OffsetTable[k+1] - OffsetTable[k]; shape_bitmap_definition *bitmap= new (destination) shape_bitmap_definition(source, length); *(NewOffsetPtr++) = NewCollLocation; NewCollLocation += AdjustToPointerBoundary(bitmap->sizeof()); Note that it is an inplace new. The arguments are a spot in memory and and a length to process. The sizeof method performs calculation to determine the actual length of the the bitmap + its offset pointers + its actual data Remember that something that frees up a collection should call delete on each bitmap just in case for the future :) On a similar sort of note, a screen based bitmap can be pre-allocated, then created in place with one of its elments holding an auto_ptr to own its own allocation buffer. So now, the base code can deal with bitmap_definition, and higher level code can deal with the specifics of a shape_bitmap_definition and a display(screen?)_bitmap_definition, which is mostly useful for construction in either case. Mmm, might want to add the control of its own managment to shape_bitmap_definition as well for someone wanting to create-edit bitmaps. If the memory doesn't need to be freed or is freed elsewhere, then the auto_ptr can be left not holding anything. DrawingContext A DrawingContext would own all of the high-level operations for operating on a buffer. For instance, using line primitives and displaying text. Some systems, such as Macintosh, use a method of drawing commands that works as follows. Store the current drawing context Use the desired graphics port as the context perform drawing operations Swap the ports back to where they were before. Would this be better encapsulated as: std::auto_ptr<DrawingContext> context= CBuffer.get_drawing_context() context.draw_line... context.draw_text (context automatically freed) Or std::auto_ptr<DrawingContextScope> scoped= DrawingContext.get_drawing_scope(CBuffer) DrawingContext.draw_line... DrawingContext.draw_text (scope automatically freed) Or something else? File organization details: Initial work will proceed in portable_files.h, files_macintosh.cpp, Support/CFiles.cpp, Support/CFileTypes.h, Support/CFileTypes.cpp, and CFileDesc_Carbon.h. At a later point in time when files are reorganized, this will become The files will be reorganized as follows Support/CBitmap.cpp (was textures.cpp) Support/CBitmap.h (was textures.h) Graphics/CBuffer.cpp Graphics/CBuffer.h Graphics/CDisplay.cpp Graphics/CDisplay.h Graphics/Carbon/CBuffer_Carbon.cpp Graphics/Carbon/CBuffer_Carbon.h Graphics/Carbon/CDisplay_Carbon.cpp Graphics/Carbon/CDisplay_Carbon.h Why is CBitmap in Support? Well, because Shapes themselves aren't quite Graphics themselves. Graphics itself covers everything needed to display data to a screen and the interim steps to go from data to rendering. Conceptually, a server should need *nothing* from the graphics directory. *BUT* But, the shapes file contains timing and sound data related to animations and firing. The core of the game could care less about the sounds, but the animation system would give a darn about that. And the game core would definitly care about 'spawn monster projectile 10 ticks after it decides to fire.' I admit that right now there is no seperation between game core and animation, but the shapes file is still required by the game core. |