BDirectWindow

Derived from: BWindow

Declared in: be/kit/game/DirectWindow.h

Library: libgame.so


Overview

[method summary]

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.

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:

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 between BDirectWindow and your drawing thread. Also, if you choose to use BWindow or BView API inside a BDirectWindow, be sure you don't block the DirectConnected() 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;

buffer_state indicates what change is occurring in the direct buffer access privileges. It can have one of the following values:

Constant Description
B_DIRECT_START Your BDirectWindow has just received direct screen access to the part of the screen described by the direct_buffer_info structure.
B_DIRECT_STOP Your direct screen access privileges have been suspended. None of the other fields in the direct_buffer_info structure are valid.
B_DIRECT_MODIFY A change has occurred to the direct screen access buffer; your drawing code needs to take whatever action is necessary to adjust to the new state.

You will always receive a B_DIRECT_START notification when your BDirectWindow is first connected to the screen, followed by any number of B_DIRECT_MODIFY notifications (it's possible you won't receive any at all). When you return from DirectConnected() after handling B_DIRECT_START or B_DIRECT_MODIFY, your application guarantees to the application server that your code will abide by the frame buffer configuration specified by the direct_buffer_info structure until another B_DIRECT_MODIFY notification is received (or B_DIRECT_STOP occurs).

You'll receive a B_DIRECT_STOP notification when the window is closed, hidden, or if moved, or if a resolution or color depth change occurs. Once your DirectConnected() function returns from handling this notification, you guarantee to the application server that your code won't touch the frame buffer anymore.

For any of these notifications, your DirectConnected() function shouldn't return until you can guarantee to the application server that your code will abide by the frame buffer configuration it received.

You can get further information about what changed by testing against other flags in the buffer_state field:

Constant Description
B_BUFFER_MOVED The content of your window has been moved, either by a call to MoveTo() or MoveBy() or by the user manually dragging the window. The contents of the window are always moved relative to the top-left corner of the window.
B_BUFFER_RESET The entire direct access buffer has been reset. This can happen if the user changes the depth or resolution of the screen, or if the window had previously been hidden and has been made visible again.
B_BUFFER_RESIZED The content area of your window has been resized.
B_CLIPPING_MODIFIED The visible region of the content area of your window changed. This doesn't imply anything about the position of the window or the size of the content area of the window--it simply means that the part of the window that's visible has changed shape.

The driver_state field indicates changes in the state of the graphics card on which your direct window is displayed. There are two possible values:

Constant Description
B_MODE_CHANGED The resolution or depth of the graphics card has changed.
B_DRIVER_CHANGED The window was moved onto another monitor.

The bits field is a pointer to the frame buffer in your own team's memory space.

The pci_bits field 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>.

The layout, orientation, _reserved, _dd_type_, and _dd_token_ fields 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. 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.

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. The clip_list is a list of rectangles that together define the visible region of the content area of the window, in screen coordinates

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.


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.

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:
            fBits = (uint8 *) info->bits;
            fRowBytes = info->bytes_per_row;
            fFormat = info->pixel_format;
            fBounds = info->window_bounds;
            fDirty = true;
            
            // Get clipping information
            
            if (fClipList) {
               free(fClipList);
               fClipList = NULL;
            }
            fNumClipRects = info->clip_list_count;
            fClipList = (clipping_rect *)
                  malloc(fNumClipRects*sizeof(clipping_rect));
            memcpy(fClipList, info->clip_list,
                  fNumClipRects*sizeof(clipping_rect));         
            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).


Hook Functions

DirectConnected()

The DirectConnected() hook function is called when the connection to the screen has been made, a change has occurred to the size or format of the frame buffer, a change has occurred to the position, size, or shape of the visible part of the content area of the window, or the connection to the screen is terminated.


Constructor and Destructor


BDirectWindow()

      BDirectWindow(BRect frame, const char *title, window_type type, uint32 flags, 
         uint32 workspace = B_CURRENT_WORKSPACE)
      BDirectWindow(BRect frame, const char *title, window_look look,
         window_feel feel, uint32 flags,
         uint32 workspace = B_CURRENT_WORKSPACE)

Creates and returns a new BDirectWindow object. This is functionally equivalent to the BWindow constructor, except the resulting BDirectWindow supports direct window operations.

You will probably want to set up a flag to keep track of whether or not the direct window's connection to the screen is viable. In the constructor, you should set this flag (let's call it fConnectionDisabled) to false, which indicates to both DirectConnected() and your drawing thread that the window is not in the process of being deconstructed. The destructor would then set this flag to true before terminating the connection to avoid the unlikely possibility of the connection trying to restart while the BDirectWindow is being dismantled.

You'll also need other flags or semaphores (or benaphores) to manage the interaction between the BDirectWindow and your drawing thread.

See the sample code in Using a Direct Window for an example.


~BDirectWindow()

      ~BDirectWindow()

Frees all memory the BDirectWindow object allocated for itself. You should never delete a BDirectWindow object; call its Quit() or Close() function instead.

Your BDirectWindow destructor should begin by setting the fConnectionDisabled flag to true, to prevent DirectConnected() from attempting to reconnect to the direct window while it's being deconstructed.

Then you should call Hide() and Sync() to force the direct window to disconnect direct access:

   MyDirectWindow::~BDirectWindow {
      fConnectionDisabled = true;
      Hide();
      Sync();
      /* complete usual destruction here */
   }


Member Functions


DirectConnected()

      virtual void DirectConnected(direct_buffer_info *info)

This hook function is the core of BDirectWindow. Your application should override this function to learn about the state of the graphics display onto which you're drawing, as well as to be informed of any changes that occur.

This function is also called to suspend and resume your direct access privileges.

Your code in this function should be as short as possible, because what your DirectConnected() function does can affect the performance of the entire system. DirectConnected() should only handle the immediate task of dealing with changes in the direct drawing context, and shouldn't normally do any actual drawing--that's what your drawing thread is for.

If you have drawing that absolutely has to be done before you can safely return control to the application server (see the note below), you may do so, but your code should do the absolute minimum drawing necessary and leave everything else to the drawing thread.

DirectConnected() should only return when it can guarantee to the application server that the request specified by info will be strictly obeyed.

The structure pointed to by info goes away after DirectConnected() returns, so you should cache the information that interests you.

See Getting Connected (and Staying That Way) for more information about the direct_buffer_info structure.


GetClippingRegion()

      status_t GetClippingRegion(BRegion *region, BPoint *origin = NULL) const

Sets the specified region to match the current clipping region of the direct window. If origin is specified, each point in the region is offset by the origin, resulting in a BRegion that's localized to your application's vision of where in space the origin is (relative to the origin of the screen's frame buffer).

Although the direct_buffer_info structure contains the clipping region of a direct window, it's not in standard BRegion form. This function is provided so you can obtain a standard BRegion if you need one.

The GetClippingRegion() function can only be called from the DirectConnected() function; calling it from outside DirectConnected() will return invalid results.

If you need to cache the clipping region of your window and need a BRegion for clipping purposes, you could use the following code inside your DirectConnected() function:

   BRegion rgn;
   GetClippingRegion(&rgn);

This serves a double purpose: it obtains the clipping region in BRegion form, and it returns a copy of the region that you can maintain locally. However, it may be more efficient to copy the clipping region by hand, since the clipping rectangle list used by BDirectWindow uses integer numbers, while BRegion uses floating-point.

RETURN CODES


IsFullScreen(), SetFullScreen()

      bool IsFullScreen(void) const
      status_t SetFullScreen(bool enable)

IsFullScreen() returns true if the direct window is in full-screen exclusive mode, or false if it's in window mode.

The value returned by IsFullScreen() is indeterminate if a call to SetFullScreen() is in progress--if this is the case, you shouldn't rely on the resulting value. Instead, it would be safer to maintain a state setting of your own and use that value.

SetFullScreen() enables full-screen exclusive mode if the enable flag is true. To switch to window mode, pass false. The SupportsWindowMode() function can be used to determine whether or not the video card is capable of supporting window mode. See Window Mode vs. Full Screen Mode for a detailed explanation of the differences between these modes.

When your window is in full screen mode, it will always have the focus, and no other window can come in front of it.

SetFullScreen() can return any of the following result codes.

RETURN CODES


SetFullScreen() see IsFullScreen()


SupportsWindowMode()

      static bool SupportsWindowMode(screen_id id = B_MAIN_SCREEN_ID)

Returns true if the specified screen supports window mode; if you require the ability to directly access the frame buffer of a window (rather than occupying the whole screen), you should call this function to be sure that the graphics hardware in the computer running your application supports it. Because this is a static function, you don't have to construct a BDirectWindow object to call it:

   if (BDirectWindow::SupportsWindowMode()) {
      /* do stuff here */
   }

In particular, window mode requires a graphics card with DMA support and a hardware cursor; older video cards may not be capable of supporting window mode.

If window mode isn't supported, but you still select window mode, DirectConnected() will never be called (so you'll never be authorized for direct frame buffer access).

Even if window mode isn't supported, you can still use BDirectWindow objects for full-screen direct access to the frame buffer, but it's recommended that you avoid direct video DMA or the use of parallel drawing threads that use both direct frame buffer access and BView calls (because it's likely that such a graphics card won't handle the parallel access and freeze the PCI bus--and that would be bad).




The Be Book, in lovely HTML, for BeOS Release 4.

Copyright © 1998 Be, Inc. All rights reserved.

Last modified January 5, 1999.