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.
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
;
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 B_CMAP8 for 8-bit
indexed color graphics mode). Our sample program will only work in this
mode. |
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
BLocker that will be used to ensure mutual exclusion when
the frame buffer or buffer information data we've cached is being
manipulated. |
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
;
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
:
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
;
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
();
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).