Skip to content
FreeDOSDeep Dive Published Updated 7 min readViews unavailable

VBE Linear Framebuffers: Map Physical Video Memory Before Drawing

Select a VBE linear-framebuffer mode, map its physical base through the DOS extender, and honor pitch, pixel masks, and memory limits.

VESA BIOS Extensions (VBE) made high-resolution graphics practical for DOS applications, but the linear-framebuffer path is not simply “take the reported address and cast it to a pointer.” A VBE mode-information block reports PhysBasePtr, a physical address for linear video memory. A protected-mode DOS program must ask its DPMI host or extender to map that physical region into its address space before drawing. It must also use the correct row pitch and pixel layout reported for the selected mode.

The reliable sequence is capability discovery, mode inspection, explicit linear-mode selection, physical mapping, bounds-aware rendering, and restoration. If any stage is skipped, common symptoms include a black screen, corrupted pixels, writes into unrelated memory, or code that works in an emulator but faults on a real DPMI host.

Discover a mode, then inspect its actual properties

First call VBE function 4F00h to retrieve controller information and validate the signature and returned status. Enumerate mode numbers from the controller’s mode list, then call 4F01h for each candidate. Do not hard-code a mode number as though it represented the same resolution and pixel format on every adapter. Check the returned mode attributes for support, graphics mode, and linear-framebuffer capability; inspect resolution, planes, bits per pixel, memory model, color masks, and the linear bytes-per-scanline field when the version provides it.

VBE function 4F02h sets the selected mode. For a mode supporting a linear framebuffer, set the documented linear-framebuffer selection bit in BX while preserving the mode number and any other required flags. Check that the function returns AX=004Fh; a failed BIOS call is not a mode selection. Save the previous video state through the appropriate VBE save/restore operation or retain enough state to restore the text mode expected by the application.

The following pseudocode shows the ordering without pretending that a real-mode DOS pointer can map arbitrary physical memory:

controller = VBE_4F00(); require controller.signature == "VESA"
info = VBE_4F01(mode); require supported(info) and has_linear_framebuffer(info)
require info.PhysBasePtr != 0
VBE_4F02(mode | LINEAR_FRAMEBUFFER); require status == 004Fh
mapping = DPMI_map_physical(info.PhysBasePtr, required_span)
draw_using(mapping, info.linear_pitch, info.pixel_masks)

The DPMI_map_physical name is illustrative; use the exact call and pointer type specified by the extender. The VBE 3.0 specification describes DPMI physical mapping for protected-mode clients. A plain real-mode program cannot dereference a 32-bit physical address above its addressable range by merely loading a segment register.

Physical address, linear pointer, and pitch are different values

PhysBasePtr is a physical address. It is not a selector, DOS segment, C pointer, or guarantee that a page mapping already exists. A DPMI host maps the physical pages into a linear range; the protected-mode client then uses the returned linear address according to its selector and memory model. Keep each value in a distinct variable and use the mapping handle or unmap procedure required by the host.

The physical span to map must cover the surface the program intends to access. A conservative calculation uses pitch × visible_height, with overflow checks, and then rounds the mapping length to the page granularity required by the DPMI host. Do not substitute width × bytes_per_pixel for pitch. Scanlines can contain padding, and VBE 3.0 distinguishes the linear pitch from the banked-mode pitch. For multi-page rendering, account for the full page count and any display-start offsets rather than mapping only one visible frame and then writing beyond it.

Pixel format must also come from the mode block. A 16-bit mode may use different red, green, and blue mask positions; a 24-bit mode may store three bytes in an order different from the application’s assumption; a 32-bit mode can have unused or reserved bits. Use the reported memory model and color-field sizes/positions to pack pixels. Confirm whether a reserved channel should be set or left zero from the specification and target behavior. Never interpret BitsPerPixel=32 as proof of a universally portable 0x00RRGGBB layout.

Mapping and rendering hazards

The video region is device memory, not ordinary cacheable RAM. Mapping attributes matter. Use the extender’s documented physical-memory mapping operation and do not invent cache flags or assume write combining. Avoid reading the framebuffer unless the mode and hardware support it; read-modify-write loops can be slow or have device-specific effects. Prefer sequential writes and keep rendering buffers in normal memory when transformations need repeated reads.

Validate all arithmetic before computing an address. For pixel (x,y), a general packed-pixel address is base + y × pitch + x × bytes_per_pixel, but planar and packed-pixel models differ. Confirm the coordinate bounds first, check multiplication and addition for overflow, and ensure the full pixel width stays within the mapped length. A malformed width or pitch can wrap a 32-bit offset and turn an apparently valid draw into arbitrary memory writes.

A banked VBE mode is a distinct fallback, not a linear mode with a different pointer. Banked access uses a window and a granularity/size contract; code must select windows as the address advances. If linear mapping is unavailable, choose a supported banked path or decline the mode. Do not continue with PhysBasePtr=0 or silently draw through a guessed VGA aperture.

Row addressing and integer safety

Before drawing, compute row_bytes from the reported linear pitch, not the visible width. For packed pixels, the minimum visible row span is width × bytes_per_pixel; require the reported pitch to be large enough for that layout before using it. A larger pitch can include padding. Use checked arithmetic for base + y × pitch + x × bytes_per_pixel, and reject overflow before adding to the mapped pointer. A corrupt width or pitch can wrap a 32-bit offset and turn a valid-looking draw into an out-of-bounds write.

The whole-screen span is not necessarily the allocation size. If a program draws only one page, map enough for the rows and pixel bytes it accesses. If it uses multiple pages or changes display start, map and validate each region. The mode block’s number of image pages can be limited by available memory; it does not justify allocating a theoretical maximum. A failed mapping should be reported as an address-space/host failure, not misdiagnosed as an unsupported display mode.

Mapping is an OS operation

The DPMI mapping function returns a linear mapping for a physical range, subject to host-specific resources and constraints. Keep its returned address and mapping handle until rendering ends, then release the mapping if the service requires it. Ordinary DOS malloc reserves application memory, not device pages. Likewise, copying PhysBasePtr into a far pointer truncates its address and does not create page-table entries. Some extenders expose a wrapper rather than raw DPMI function 0800h; use that wrapper’s parameter widths and page-alignment rules.

Lifecycle and diagnostics

Store the original text or graphics mode and restore it on normal exit and handled errors. A crash can leave the display in a mode that makes a machine look hung, so keep a recovery key or watchdog path when practical. If the program changes palette, display start, or scanline length, restore those states as well; resetting only the mode may not reset every vendor-specific state.

For each failure, log the VBE version, mode number, attribute bits, resolution, pitch, pixel masks, PhysBasePtr, DPMI host and mapping result. Separate VBE mode-set failure from DPMI mapping failure and rendering bounds errors. Verify with a pattern containing distinct primary colors, alternating scanlines, and a border at the last valid pixel. Compare computed row starts against the reported pitch and ensure the final write stays below the mapped span.

Acceptance checks

Test at least one linear mode and one mode without linear support. Confirm that the mode list is terminated and each mode block is zero-initialized to the required size before the BIOS call. Validate the 004Fh success result for every VBE operation, verify the selected mode actually uses the LFB, and confirm the DPMI mapping covers the calculated span. Render and restore under the specific extender and emulator or physical adapter the application supports. A successful BIOS mode switch alone does not prove that protected-mode memory mapping succeeded.

The important boundary is ownership: VBE tells the application what the adapter offers; DPMI maps the physical aperture; the program owns arithmetic and pixel layout. Keeping those contracts separate is what turns a fragile “pointer to video memory” trick into a portable DOS graphics path.

Unmap only after the last rendering operation and after any worker has stopped. A stale pointer to an unmapped aperture is just as invalid as one created from an unmapped physical address. If the host cannot safely restore text mode after an error, preserve a documented recovery sequence and test it on the exact extender. A recognizable pattern with distinct colors and alternating scanlines can verify that row starts advance by the reported linear pitch before more elaborate rendering hides a stride error.

Related:

Sources:

Comments