Skip to content
Haiku OSDeep Dive Published Updated 6 min readViews unavailable

Haiku BView DrawBitmapAsync: Nonblocking Draws and Bitmap Lifetime

Use Haiku's asynchronous bitmap drawing calls without mistaking a nonblocking return for message batching or completed rendering.

BView::DrawBitmapAsync() is useful when a view needs to enqueue several image draws without waiting for the app_server after each one. The word async describes the client-to-server drawing path; it does not mean that a view can be called from any thread, that the bitmap can be destroyed immediately, or that the pixels are already visible when the method returns.

The safe model is simple: issue drawing commands while the view has its normal window ownership and lock, keep each referenced BBitmap valid and unchanged until the server has processed the commands that use it, then synchronize only at a boundary where completion matters. That avoids per-draw reply waits without turning asynchronous dispatch into a lifetime race.

For content that is stable for the duration of drawing, enqueue the related images together from Draw(). The update rectangle is a cheap way to skip work that cannot affect this update; normal clipping remains the final limit on painting.

void PreviewView::Draw(BRect updateRect)
{
    if (fBackground == nullptr || fBadge == nullptr)
        return;

    const BRect imageFrame(12, 12, 267, 171);
    if (!imageFrame.Intersects(updateRect))
        return;

    DrawBitmapAsync(fBackground, fBackground->Bounds(), imageFrame);
    DrawBitmapAsync(fBadge, BPoint(20, 20));
}

This sketch assumes the two bitmap objects are owned by the view or another longer-lived owner, and that Draw() is running through the normal Interface Kit update path. It does not create a worker thread or retain pointers to temporary bitmaps. The drawing methods return void; a null bitmap, unattached view, or invalid rectangle can be ignored by the implementation rather than reported as an application-level error. Validate assets during setup and keep the view’s rendering state explicit.

When the same static image is used on every update, asynchronous calls avoid the wait performed by the synchronous convenience call for each individual draw. They do not necessarily batch protocol writes: the pinned View.cpp calls _FlushIfNotInTransaction() after DrawBitmapAsync(), so the shown calls can flush separately outside an explicit transaction. They do not make the server process commands in parallel, guarantee a faster frame, or remove the cost of bitmap scaling, clipping, and compositing. Measure the actual rendering path before treating the asynchronous variant as a performance improvement.

A queued bitmap is still a live resource

The public header exposes both DrawBitmapAsync() and synchronous DrawBitmap() overloads. In Haiku’s current implementation, an asynchronous draw message carries the bitmap’s server token and the source/destination rectangles to the app_server. A synchronous DrawBitmap() wrapper calls its asynchronous counterpart and then calls Sync().

That implementation detail matters for ownership. Do not enqueue a draw using a stack-local or short-lived BBitmap and destroy it as soon as DrawBitmapAsync() returns. The request refers to a server-side bitmap identity; queued work still has to be handled by the other process. Keep the bitmap alive until the server has processed the draw, and do not overwrite the pixel data while a consumer may still be reading it. If a frame producer wants to reuse storage, establish a completion boundary first or use a separate buffer whose ownership is not being changed.

The pinned bitmap cleanup code sends an AS_DELETE_BITMAP message on a separate app-server link. The window cleanup path explicitly synchronizes with the server because pending view messages and bitmap deletion travel through different server-side paths. This is why object lifetime cannot be inferred solely from the fact that a draw call returned. Keep resources owned by the view/window generation that queued them; before destroying or repurposing them outside the normal draw cycle, synchronize with the owning window while both objects are still valid.

For direct pixel access, BBitmap::LockBits() and UnlockBits() govern access to bitmap bits. They are not a replacement for ordering an asynchronous draw against later buffer reuse. Decide which component owns the pixels at each point in the pipeline, and do not let a decoder, worker, and app_server draw path mutate the same frame without a synchronization design.

Flush and Sync are different boundaries

Flush() forwards buffered window commands to the app_server, but it does not wait for a reply confirming that they have been processed. Sync() sends a synchronization message and waits for the server’s response. In the pinned Interface Kit implementation, BView::Sync() delegates to the owner window, and BWindow::Sync() waits for the app_server reply. Treat it as a blocking barrier, not a free notification.

Use one barrier after a group of issued operations when the caller needs to know the server has consumed them, for example before releasing or replacing the associated bitmap resources. Avoid synchronizing after every image when the goal is to avoid per-image reply waits; doing so restores a wait to every iteration. This is distinct from batching writes in one protocol transaction. The synchronous DrawBitmap() overload is a convenient choice when a caller needs the simple one-call completion behavior.

Completion means the app_server processed prior drawing commands. It is not proof that a monitor has scanned out the resulting pixels, that a compositor presented a frame, or that a screenshot from another process observes the same moment. Use the appropriate window update or capture mechanism for those separate requirements.

Keep threading and redraw ownership ordinary

Asynchronous dispatch does not relax the Interface Kit lock contract. A BView belongs to its window’s looper and should be accessed while the owning window is locked, or from the normal drawing callback that is already executing under that ownership. A worker thread should publish immutable image data through an explicit handoff, then ask the window thread to update the model and invalidate the affected region. Do not call DrawBitmapAsync() from a decoder thread because it has a pointer to the view.

Likewise, an asynchronous draw is not a retained scene graph. A later expose or invalidation can request a redraw, so the model and bitmap assets must remain available for Draw() to reconstruct the view. Keep drawing deterministic from the current model and update rectangle; do not rely on a previous app_server command as the only record of what should appear.

Test both normal and teardown paths. Draw multiple images in one update, invalidate the same region again, resize the window, replace an image, and close the window while a worker has a pending frame. Verify every bitmap outlives queued commands, all pixel writes follow the selected locking policy, and teardown stops producers before releasing view-owned assets. If correctness depends on when those commands have been processed, put a single explicit Sync() at that boundary and document why it is needed.

Related:

Sources:

Comments