BDirectWindow¶
The BDirectWindow
class gives your code direct access to the
graphics frame buffer on the video card. Unlike BWindowScreen
,
BDirectWindow
can be used in both full-screen and window
modes—you can create a BDirectWindow
that looks just like a
normal window, but lets your code draw into it by directly accessing the
frame buffer.
In addition, BDirectWindow
lets you switch between
full-screen exclusive mode and windowed modes without breaking down and
rebuilding the object. A simple call to the
SetFullScreen()
function does the job.
Note
if you want your direct window to be full-screen but don’t want exclusive
mode, just resize the window to fill the entire screen; since full-screen
exclusive mode (as set by calling
SetFullScreen()
) won’t let other windows draw
in front of your direct window, you can’t have menus in a full-screen
exclusive mode direct window.
Another difference between BDirectWindow
and
BWindowScreen
is that BDirectWindow
lets you
access all the BWindow
functions; you can literally treat your
BDirectWindow
just like another window. There are two
caveats:
Warning
Don’t draw into the direct window from its own thread; you should spawn another thread for drawing into the direct window.
And use the
DirectConnected()
function to synchronize the interaction betweenBDirectWindow
and your drawing thread. Also, if you choose to useBWindow
orBView
API inside aBDirectWindow
, be sure you don’t block theDirectConnected()
function.
Not all video cards support window mode; use the
SupportsWindowMode()
function if you need to
know whether or not window mode is available.
Getting Connected (and Staying That Way)¶
The key to the BDirectWindow
class is the
DirectConnected()
function, which your code
must implement. This function is called whenever a change that your drawing
code may need to be aware of occurs.
When your DirectConnected()
function is called,
it’s passed a pointer to a direct_buffer_info structure, as
follows:
typedef struct {
direct_buffer_state buffer_state;
direct_driver_state driver_state;
void* bits;
void* pci_bits;
int32 bytes_per_row;
uint32 bits_per_pixel;
color_space pixel_format;
buffer_layout layout;
buffer_orientation orientation;
uint32 _reserved[9];
uint32 _dd_type_;
uint32 _dd_token_;
uint32 clip_list_count;
clipping_rect window_bounds;
clipping_rect clip_bounds;
clipping_rect clip_list[1];
} direct_buffer_info;
Field |
Description |
---|---|
buffer_state |
Indicates what change is occurring in the direct buffer access privileges. It can have one of the following values:
For any of these notifications, your
You can get further information about what changed by testing against other flags in the buffer_state field:
|
driver_state |
Indicates changes in the state of the graphics card on which your direct window is displayed. There are two possible values:
|
bits |
Is a pointer to the frame buffer in your own team’s memory space. |
pci_bits |
Is a pointer to the frame buffer in the PCI memory space; this value is typically needed to control DMA. |
bytes_per_row |
Is the number of bytes used to represent a single row of pixels in the frame buffer. |
bits_per_pixel |
Is the number of bits actually used to store a single pixel, including reserved, unused, or alpha channel bits. This value is usually a multiple of eight. |
pixel_format |
Is the format used to encode a pixel, as defined in the color_space type in GraphicsDefs.h. |
layout,orientation,reserved,dd_type, and_dd_token |
Are all reserved for future use and must not be used. |
window_bounds |
Is a rectangle that defines the full content area of the window, in screen coordinates. You can convert these coordinates into frame buffer addresses using the values in the bits , bytes_per_row , and bits_per_pixel fields. |
clip_bounds |
Is the bounding rectangle of the visible part of the content area of the window, in screen coordinates. This rectangle is the smallest rectangle that contains all the rectangles in the clip_list, described below. |
clip_list_count |
Is the number of rectangles in the clip_list. |
clip_list |
Is a list of rectangles that together define the visible region of the content area of the window, in screen coordinates |
The clipping_rect structure is:
typedef struct {
int32 left;
int32 top;
int32 right;
int32 bottom;
} clipping_rect;
Note that, as always, these edges are inclusive; for example, if left is 5 and top is 3, the pixel at (5,3) is included in the rectangle’s contents.
The data in the direct_buffer_info structure is only valid until DirectConnected() returns, so if you need to reference any of the information later, you should make a copy of the fields you need.
Warning
If your DirectConnected()
implementation
doesn’t handle a request within three seconds, the Application Server will
intentionally crash your application under the assumption that it’s
deadlocked. Be sure to handle requests as quickly as possible.
Window Mode vs. Full Screen Mode¶
There are some differences in how BDirectWindow
behaves
depending on whether it’s in window mode or full-screen exclusive mode.
In window mode, the BDirectWindow
behaves almost exactly like
a BWindow
—so much so that you can use a
BDirectWindow
in any situation you’d normally use a
BWindow
. The window_bounds rectangles are the same
size and shape as the window itself, as you’d expect. If exclusive window
mode is available ( SupportsWindowMode()
returns true), DirectConnected()
will be called as described above, thereby providing the means to directly
access the frame buffer. If the graphics card doesn’t support exclusive
access to the frame buffer while in window mode,
DirectConnected()
will never be called, and you
can only use BWindow
and BView
APIs to work in
the window.
In full-screen exclusive mode, the window_bounds are actually the size and shape of the entire screen, even if the screen isn’t the same size as the direct window you created. You have to handle the difference yourself.
Full-screen exclusive mode also guarantees that your window will always be the focus, always be in front, and will always stay full-screen (the application server will resize the window for you if the screen resolution changes). Since no other windows can come in front of a full-screen exclusive direct window, any Interface Kit objects that use a window to display their contents won’t work; this includes any type of menu.
If you want your BDirectWindow
to be full-screen, but still
compatible with menus or other windows, create it as a non-exclusive
window, then use the following code:
BScreen screen(this);
MoveTo(0,0);
ResizeTo(screen.width, screen.height);
This will make the non-exclusive direct window fill the entire screen. Keep in mind that in this case, other windows may appear in front of yours, and if the screen resolution changes, you will have to resize the window yourself if you want to continue to fill the entire screen.
Using a Direct Window¶
Let’s put together a simple class, derived from BDirectWindow
,
that demonstrates the basics of drawing into a direct window.
class DirectSample : public BDirectWindow {
public:
DirectSample(BRect frame);
~DirectSample();
virtual bool QuitRequested();
virtual void DirectConnected(direct_buffer_info *info);
uint8* fBits;
int32 fRowBytes;
color_space fFormat;
clipping_rect fBounds;
uint32 fNumClipRects;
clipping_rect* fClipList;
bool fDirty; // needs refresh?
bool fConnected;
bool fConnectionDisabled;
BLocker* locker;
thread_id fDrawThreadID;
};
The DirectSample class implements a constructor as well as the
QuitRequested()
and
DirectConnected()
functions.
Some variables are added to the class for cacheing information about the frame buffer.
Field |
Description |
---|---|
fBits |
Will contain a pointer to the frame buffer’s bitmap. |
fRowBytes |
Will contain the number of bytes per row of screen data. |
fFormat |
Will contain the pixel format (such as |
fBounds |
will contain the bounds rectangle for the window. |
fNumClipRects |
Will contain the number of rectangles in the clip rectangle list. |
fClipList |
Is the actual list of clip rectangles, and will be allocated on-the-fly as needed. |
fDirty |
Will be true if the window needs to be redrawn. |
fConnected |
Is true if the window is connected to the frame buffer. |
fConnectionDisabled |
Is true if the window is in the process of being closed. |
locker |
Is a |
fDrawThreadID |
Contains the thread_id of the drawing thread, which is responsible for drawing the contents of the window. |
The specifics of what these variables are and why the information contained
in them is maintained will be discussed when we get to the
DirectConnected()
and
DrawingThread() functions.
The constructor for the DirectSample class looks like this:
DirectSample::DirectSample(BRect frame)
: BDirectWindow(frame, "DirectWindow Sample",
B_TITLED_WINDOW,
B_NOT_RESIZABLE|B_NOT_ZOOMABLE)
{
fConnected = false;
fConnectionDisabled = false;
locker = new BLocker();
fClipList = NULL;
fNumClipRects = 0;
AddChild(new SampleView(Bounds()));
if (!SupportsWindowMode()) {
SetFullScreen(true);
}
fDirty = true;
fDrawThreadID = spawn_thread(DrawingThread, "drawing_thread",
B_NORMAL_PRIORITY, (void *) this);
resume_thread(fDrawThreadID);
Show();
}
This code establishes the direct window by deferring to
BDirectWindow
. Then the fConnected and
fConnectionDisabled flags are initialized to indicate that the
window isn’t connected yet, but the connection isn’t in the process of
being torn down by the DirectSample destructor. The locker is
created, and the clip rectangle list is initialized to a NULL
pointer, with a count of 0.
Then it adds a child view that occupies the entire window. The primary
purpose of this view in this sample is to set the view color to
B_TRANSPARENT_32_BIT
, to prevent the application server
from erasing the window with a default color.
If the video card doesn’t support window mode, we call
SetFullScreen()
to switch the direct window
into full-screen exclusive mode. This guarantees that you’ll get connected
with direct screen access (in a window if possible, otherwise in
full-screen exclusive mode). If you don’t use
SetFullScreen()
, and window mode isn’t
supported, DirectConnected()
will never be
called, and you won’t have direct screen access.
Then the fDirty flag is set to true, which indicates
that the window needs to be updated, and the drawing thread is started; the
drawing thread will handle all actual drawing into the window. The argument
passed to the drawing thread is a pointer to the DirectSample
window itself. You should always use a separate thread for drawing into a
BDirectWindow
.
Finally, Show()
is called to make the direct window
visible.
The destructor needs to make sure there’s no chance someone will try to draw while the window is being destructed:
DirectSample::~DirectSample() {
int32 result;
fConnectionDisabled = true; // Connection is dying
Hide();
Sync();
wait_for_thread(fDrawThreadID, &result);
free(fClipList);
delete locker;
}
The first thing the destructor does is set the
fConnectionDisabled flag to true, which indicates
that the window is in the process of being destroyed, and that future calls
to DirectConnected()
or the drawing thread
should be ignored. The window is then hidden by calling
Hide()
. Finally, Sync()
is
called to block until the window is actually hidden.
wait_for_thread()
waits until the drawing thread terminates.
The drawing thread (as we’ll see shortly) is designed to terminate when the
fConnectionDisabled flag is true.
Then the clip rectangle list is freed and the locker deleted.
The QuitRequested()
function is implemented as
usual.
The DirectConnected()
function is called
whenever a change occurs that affects how your code should access the frame
buffer:
void DirectSample::DirectConnected(direct_buffer_info *info) {
if (!fConnected && fConnectionDisabled) {
return;
}
locker->Lock();
switch(info->buffer_state & B_DIRECT_MODE_MASK) {
case B_DIRECT_START:
fConnected = true;
case B_DIRECT_MODIFY:
// Get clipping information
if (fClipList) {
free(fClipList);
fClipList = NULL;
}
fNumClipRects = info->clip_list_count;
fClipList = (clipping_rect *)
malloc(fNumClipRects*sizeof(clipping_rect));
if (fClipList) {
memcpy(fClipList, info->clip_list,
fNumClipRects*sizeof(clipping_rect));
fBits = (uint8 *) info->bits;
fRowBytes = info->bytes_per_row;
fFormat = info->pixel_format;
fBounds = info->window_bounds;
fDirty = true;
}
break;
case B_DIRECT_STOP:
fConnected = false;
break;
}
locker->Unlock();
}
DirectConnected()
begins by checking the
fConnected and fConnectionDisabled flags; the code in
DirectConnected()
is only run if the connection
is opened (fConnected is true) or if we want to start
it again (fConnectionDisabled is false). This
arrangement prevents the DirectConnected()
function from trying to reconnect if the destructor has started running.
Otherwise, the locker is locked, to prevent
DirectConnected()
and the drawing thread from
colliding.
If the buffer state is B_DIRECT_START
, the
fConnected flag is set to true. This keeps track of
the fact that the application server has given permission to draw directly
into the region of the frame buffer controlled by the direct window.
If the buffer state is B_DIRECT_START
or
B_DIRECT_MODIFY
(in which case the
direct_buffer_info structure describes changes to the frame
buffer), any previously-existing clip rectangle list is deleted, then we
cache the information that interests us and set the fDirty flag
to true (to indicate that the display needs to be redrawn to
reflect the changed graphics settings).
The clip list is also cached by saving the number of rectangles in the list in the fNumClipRects field and by making a copy of the clip list into a newly malloc()d block of memory.
If the state is B_DIRECT_STOP
, the fConnected
flag is set to false, to indicate that we shouldn’t draw into
the frame buffer anymore.
Finally, the locker is unlocked, which lets the drawing thread start running again.
Now let’s have a look at DrawingThread(); this function serves as the drawing thread, and is a global function:
int32 DrawingThread(void* data) {
DirectSample* w;
w = (DirectSample*) data;
while (!w->fConnectionDisabled) {
w->locker->Lock();
if (w->fConnected) {
if (w->fFormat == B_CMAP8 && w->fDirty) {
int32 y;
int32 width;
int32 height;
int32 adder;
uint8 *p;
clipping_rect *clip;
int32 i;
adder = w->fRowBytes; // Stash locally for this pass
for (i=0; i<w->fNumClipRects; i++) {
clip = &(w->fClipList[i]);
width = (clip->right - clip->left)+1;
height = (clip->bottom - clip->top)+1;
p = w->fBits+(clip->top*w->fRowBytes)+clip->left;
y = 0;
while (y < height) {
memset(p, 0x00, width);
y++;
p+=adder;
}
}
}
w->fDirty = false;
}
w->locker->Unlock();
// Use BWindow or BView APIs here if you want
snooze(16000);
}
return B_OK;
}
DrawingThread() starts by casting the argument, data, into a pointer to the DirectSample window into which it will be drawing.
The while
loop that follows will continue to run as long as the
fConnectionDisabled flag is true—in other words, it
will keep looping as long as the connection is enabled.
The drawing loop itself begins by locking the locker to ensure that
DirectConnected()
doesn’t change anything while
we’re working, then checking to be sure the connection is opened
(fConnected is true). If the connection is open, we
verify that format of the window is still 8-bit color and that the display
needs to be updated. If the display needs updating and the pixel format is
still B_CMAP8
, the drawing code begins.
The fRowBytes field of the DirectSample window is
cached in a local variable called adder. Then each rectangle in
the clip list is drawn, one at a time, using a for
loop.
A pointer to the clip rectangle to be drawn is stored in clip, and the width and height of the rectangle are computed. Then p is set to be a pointer to the first pixel in the frame buffer that’s contained by the clip rectangle. Since 8-bit color pixels each occupy exactly one byte of video memory, this pixel’s address can be computed by taking the base fBits pointer, adding the number of bytes per row times the number of rows between the top of the screen and the first row in the clip rectangle, then adding the number of bytes between the left edge of the screen and the left edge of the clip rectangle, as seen in the line:
p = w->fBits+(clip->top*w->fRowBytes)+clip->left;
Then a while
loop is used to iterate over each line in the clip
rectangle, by ranging the variable y from 0 to the height of the
clip rectangle. memset() is used to clear the row to black, which is
represented by the byte value 0x00. y is incremented by one for
each pass through the loop, to count the rows being drawn for each
iteration, and the pointer p is incremented by adder to move down
to the beginning of the next row in the clip rectangle.
Once each clip rectangle has been drawn, the for
loop exits, and the
fDirty flag is set to false to indicate that the
screen is up-to-date. Once that’s done, the locker is unlocked, which lets
DirectConnected()
do its thing if it’s called.
To avoid using an unreasonable amount of processing time,
snooze()
is called to give up CPU time to other threads.
If you want to use calls to BWindow
or BView
API
in your drawing thread, you should do so just after unlocking the window.
When the thread terminates (which will only happen when the connection is
disabled), the drawing thread returns B_OK
.
This drawing function is designed to draw nothing unless it’s necessary;
DirectConnected()
will set the fDirty
flag when something happens to cause the screen to need a refresh, and
other code elsewhere in the application could also set the fDirty
flag to indicate that the screen should be redrawn.
Since we’re taking over drawing the contents of the window, we need to tell the application server not to draw anything. This is done by adding the following line to the constructor for the SampleView:
SetViewColor(B_TRANSPARENT_32_BIT);
This is very important: if you don’t remember to do this, you’ll have all kinds of synchronization problems when the application server and your drawing code try to draw into the window at the same time.
Note that this sample code doesn’t really do anything useful (if all you
want to do is have a black window moving around, don’t use
BDirectWindow
—use a regular BWindow
, throw a
BView
into it, and use SetViewColor()
to
make the view black; it’ll be faster and more efficient because it will use
hardware graphics acceleration if it’s available). However, it serves as a
simple example of how to establish a connection to let your own drawing
code directly access the screen. Just replace the code inside the drawing
loop with something more useful (like a nifty real-time animation).