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

Haiku BDirectWindow: Framebuffer Access and Buffer Lifecycle

Use Haiku BDirectWindow only for measured low-level rendering needs, tracking clipping, pixel layout, mode changes, buffer state, and fallbacks.

BDirectWindow is a specialized BWindow subclass that provides direct access to the graphics framebuffer for workloads that need it. Its DirectConnected() callback reports when direct access starts, stops, or changes, together with a direct_buffer_info structure describing the buffer and visible region. This is a much lower-level contract than drawing through BView and app_server; a game or visualization should adopt it only after measuring that ordinary drawing is insufficient.

Direct access is not a permanent pointer to a stable full-screen pixel array. The driver can move or resize the buffer, the window can become clipped or invisible, the display mode can change, and the window can move to another display. The callback’s buffer_state and driver_state fields exist to communicate such changes. Code that caches bits, bytes_per_row, pixel_format, or a clip list forever will eventually paint incorrectly or access invalid memory.

Why direct access is exceptional

Ordinary Interface Kit drawing lets Haiku manage clipping, redraw, compositing, and coordination with the window server. A direct window bypasses part of that path and gives the application responsibility for low-level pixel layout and visibility. That can reduce overhead for certain rendering patterns, but it also couples the app to the current graphics driver and surface state.

Start with a regular BView and profile the actual workload. Direct mode is justified only when a measured bottleneck remains and the app can correctly handle display mode changes, clipping updates, and fallback behavior. A fast benchmark that ignores occlusion, window movement, or multi-display changes is not evidence that the implementation is robust.

Do not confuse BDirectWindow with drawing a BBitmap offscreen or with a screensaver’s DirectConnected() callback. A direct window is a Game Kit API for a window-owning application. Screensaver modules have a host-managed BScreenSaver lifecycle and their own direct-drawing hooks. Similar callback names do not make the ownership or lifecycle contracts interchangeable.

Interpret the callback state every time

direct_buffer_info contains the buffer_state, driver_state, framebuffer pointers, row stride, bits per pixel, pixel format, window bounds, clip bounds, and clip rectangle list. Use the state flags to distinguish access starting, stopping, buffer movement/resizing, clipping modification, and driver or mode changes. Update the rendering snapshot in response to each relevant callback instead of checking only the first B_DIRECT_START.

The visible region is a list of rectangles, not necessarily one uninterrupted rectangle. A window partly covered by another window or partly outside the screen can require painting only selected spans. Clip using the supplied region data and screen-coordinate bounds; do not assume the full content area is visible. When direct access stops, do not continue writing into the old framebuffer pointer.

bytes_per_row is the memory stride between rows and may exceed the number of pixel bytes implied by visible width. bits_per_pixel and pixel_format define interpretation; they are not license to assume a particular RGB byte order or alpha layout. Use the reported pixel format and implement only formats the application explicitly supports. If the driver changes format, rebuild conversion/raster state or leave direct mode.

Older direct_buffer_info documentation describes a pci_bits pointer for low-level DMA use, but the current public header reserves that ABI slot rather than exposing a named pci_bits member. Ordinary applications should use only the documented CPU-visible bits pointer and must not reinterpret a reserved field as a usable address. Never attempt device DMA or write to a presumed PCI framebuffer pointer merely because older documentation mentions it.

Synchronize state transitions and drawing

DirectConnected() can arrive as the graphics environment changes, independently of the main application’s normal drawing cadence. Keep the callback bounded: capture the state needed for rendering, invalidate any cached pointers or geometry, and signal the render loop. Do not perform file I/O, network calls, expensive allocation, or long waits in the callback.

The render thread and callback need a synchronization design. A pointer and its matching stride, pixel format, and clip list form one snapshot; reading fields from different callbacks can produce an inconsistent combination. Copy the necessary values into an application-owned structure under a short lock or publish an immutable snapshot atomically. Never let a renderer use a snapshot after a stop/move transition invalidated it.

The BDirectWindow class has internal direct-lock management in current source. Do not invent or call private locking methods. Follow the documented public callback and window locking behavior, and keep the BWindow looper responsive. If a direct renderer needs to serialize access to a shared buffer, define an application-level protocol that does not deadlock the window’s own lock.

Write pixels with measured bounds

Use the reported row stride rather than width * bytesPerPixel. Use BRect bounds and clip rectangles with Haiku’s coordinate conventions, taking care about inclusive right/bottom coordinates where the relevant drawing API uses them. A one-pixel arithmetic mistake can write past a row boundary. Validate width, height, stride, and pixel format before calculating offsets, and use integer types wide enough to avoid overflow.

Even if the current adapter reports a familiar 32-bit format, keep a fallback if a future mode or driver differs. A reasonable fallback is to stop direct rendering and continue with ordinary BView drawing or a bitmap-backed path. Communicate degraded performance, not broken output, if the platform cannot provide the direct format the app requires.

Do not assume a direct window is always full screen. SetFullScreen() and IsFullScreen() are explicit API operations, but full-screen transitions can change mode and buffer state. Check the result of the requested transition and wait for the resulting callback state before rendering into new geometry. Preserve a way for the user to leave full-screen mode even if direct rendering fails.

Recovery and resource boundaries

If the display mode changes, the driver changes, the window moves to another monitor, or the buffer is resized, refresh all geometry and format information. If the callback reports access stopped, cease rendering and switch to a safe fallback. Do not spin waiting for a buffer to return; the display may remain in a state where direct access is unavailable.

On shutdown, stop the render worker and release application-owned snapshots only after the worker has ceased using them. Destroying the window while a worker writes to a stale buffer is a use-after-free or device corruption risk. Use a stop signal, synchronize with the worker, and then tear down the window and bitmap resources.

Test more than the first frame

Test direct start/stop, window uncover and cover, resize, move between displays, display mode changes, sleep/wake if supported by the hardware, and driver reset scenarios. Run under both the native display driver and a virtual machine, but do not infer real-hardware compatibility from virtual graphics. Test unusual resolutions, non-square surfaces, and any supported pixel formats.

Compare direct mode with the normal Interface Kit path using measured frame times and CPU usage. Record Haiku revision, driver, adapter, pixel format, resolution, clipping scenario, and display topology. If direct mode offers no meaningful measured gain, remove the complexity. If the failure only occurs after a state transition, include the relevant buffer_state/driver_state sequence and callback timing.

For a useful transition log, record an application-generated sequence number, callback state flags, reported bounds, stride, format, and whether rendering was enabled before and after the callback. Do not log raw framebuffer addresses in a public bug report; they add little diagnostic value and may expose process-specific details. A log should make it possible to distinguish a driver update from a renderer that continued using an old snapshot. Keep the logging path bounded so diagnostics do not make a timing-sensitive display callback stall.

Test rejection paths as carefully as the supported path. If the received format is not one the renderer implements, disable direct writes before attempting conversion; if the clip information is empty, draw nothing rather than treating an empty region as full visibility. Bounds and stride arithmetic should be validated before pointer arithmetic, including on unusually wide or tall modes. Exercise an explicit return to ordinary BView painting and confirm that the window remains usable after direct access is revoked. This turns fallback into a tested operating mode rather than a paragraph in the design document.

BDirectWindow is a performance escape hatch with an explicit state machine. Reliable code treats each callback as a new snapshot, honors clip rectangles and stride, stops using buffers on revocation, synchronizes workers, and retains a normal drawing fallback. Those practices keep hardware-specific performance code from becoming a fragile dependency on one display mode.

Related:

Sources:

Comments