[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.
|