Vulkan, OpenGL, EGL, GBM, and presentation

Compare graphics APIs, context creation, buffers and presentation paths.

14 min readChapter 123 of 168Intermediate → advanced
In this chapter

Before you start#

Read Mesa and driver loading and libdrm and graphics resources. This chapter examines Vulkan Loader v1.3.290 and Mesa 24.2.0 source as independent historical baselines, with Linux v6.12 as the kernel bridge. They are not a tested version matrix. No lab changes GPU ownership or a display.

Learning goals#

Distinguish loader, ICD, Vulkan device, queue, command buffer and swapchain; compare EGL/GBM concepts; follow a specific WSI failure cleanup; and explain synchronization and buffer-sharing assumptions.

Source walkthrough#

Vulkan creation and presentation
Vulkan creation and presentation1. Application to Loader / ICD: Create instance; enumerate physical devices. 2. Application to Loader / ICD: Create device, queues, commands and memory. 3. Application to Presentation system: Create surface and request a swapchain. 4. Application to Loader / ICD: Submit work with synchronization. 5. Loader / ICD to Presentation system: Present an image when its dependencies allow. 6. Presentation system to Application (return): Present status or error.ApplicationLoader / ICDPresentation systemCreate instance;enumerate physicaldevicesCreate device, queues,commands and memoryCreate surface and request a swapchainSubmit work withsynchronizationPresent an image whenits dependencies allowPresent status or error
  1. ApplicationLoader / ICD

    Create instance; enumerate physical devices

  2. ApplicationLoader / ICD

    Create device, queues, commands and memory

  3. ApplicationPresentation system

    Create surface and request a swapchain

  4. ApplicationLoader / ICD

    Submit work with synchronization

  5. Loader / ICDPresentation system

    Present an image when its dependencies allow

  6. Presentation systemApplication

    Return. Present status or error

Time runs downward. Dashed arrows show returns.

Read the text version
  1. Application → Loader / ICD: Create instance; enumerate physical devices
  2. Application → Loader / ICD: Create device, queues, commands and memory
  3. Application → Presentation system: Create surface and request a swapchain
  4. Application → Loader / ICD: Submit work with synchronization
  5. Loader / ICD → Presentation system: Present an image when its dependencies allow
  6. Presentation system → Application (return): Present status or error
Conceptual model. Simplified API relationships; WSI, synchronization and hardware behavior depend on the chosen implementation.

The Vulkan loader discovers drivers (installable client drivers, or ICDs), establishes dispatch and can insert layers such as validation or tracing when configured. A manifest describes a driver library and metadata; it does not prove the library can be opened or a usable GPU exists. Layers intercept or augment API calls according to their contracts and configuration. The loader, the ICD, the API headers and a platform window system are separately versioned components. See Khronos Vulkan Loader v1.3.290 loader.c.

In the pinned loader's loader_icd_scan, Apache-2.0, the following branch handles an allocation failure after attempting to add a discovered ICD:

c
        if (VK_ERROR_OUT_OF_HOST_MEMORY == icd_res) {
            res = icd_res;
            goto out;

The scan preserves the host-memory error and jumps to its cleanup/exit path. The surrounding code handles incompatible drivers and library-load failures differently. A scan may find multiple manifests, skip an unusable driver and continue, or fail; it is incorrect to infer a working device from a JSON filename alone. The loader's actual selection, enabled layers and environment must be recorded for a runtime investigation.

A Vulkan instance represents application-level access to the Vulkan implementation. Physical-device enumeration returns handles describing available devices and their capabilities. A logical device is created from a selected physical device with requested features and queue families; it owns or parents many objects used for execution. A queue accepts submissions; a command buffer records commands with explicit lifecycle states and must not be modified or freed while its use violates the API's pending-state rules. Device memory and images/buffers are distinct objects, with binding, alignment and memory-type requirements. Semaphores, fences and pipeline barriers establish different execution and memory dependencies. They do not all mean "wait until the screen shows the frame."

A surface represents a platform presentation target; a swapchain manages images presented to that surface under a window-system integration (WSI) implementation. In the Vulkan API contract, successful vkQueuePresentKHR enqueues a presentation request; it does not say that the display has scanned out the image. An image becomes available for another use through swapchain acquisition rules, not merely because the call returned. In Mesa 24.2.0, wsi_CreateSwapchainKHR, MIT-style, asks the platform interface to create a swapchain, then allocates per-image fence storage. Its error branch contains:

c
   if (!swapchain->fences) {
      swapchain->destroy(swapchain, alloc);
      return VK_ERROR_OUT_OF_HOST_MEMORY;
   }

If the fence-array allocation fails, the already-created swapchain is destroyed with the selected allocator before reporting failure. The preceding interface call can itself return an error, in which case this later allocation is not attempted. This is a specific Mesa WSI path; platform implementations, extensions and driver behavior can differ. A successful swapchain creation also does not guarantee that a later acquire or present succeeds after a window resize, surface loss or device error.

A concrete presentation timeline#

Imagine an application creates instance I, selects physical device P, creates logical device D and a queue Q, then creates surface S and swapchain C with three images. It acquires image 0, waits on the required availability signal, records commands that render into it, submits those commands to Q with the required dependencies, and presents image 0 after rendering is complete. Later it acquires an available image again; it must not assume that image 0 is immediately reusable just because the present call returned. The presentation engine and API synchronization determine availability.

Three-image swapchain order
Three-image swapchain order1. Application to Swapchain C and WSI: vkAcquireNextImageKHR. 2. Swapchain C and WSI to Application (return): image index 0 (availability signalled). 3. Application to Queue Q: vkQueueSubmit: wait acquire, signal render done. 4. Application to Queue Q: vkQueuePresentKHR image 0 (after render). 5. Queue Q to Swapchain C and WSI: present request enqueued. 6. Swapchain C and WSI to Application (return): returns: request accepted, not yet on screen.ApplicationQueue QSwapchain C and WSIvkAcquireNextImageKHRimage index 0 (availability signalled)vkQueueSubmit: waitacquire, signal renderdonevkQueuePresentKHR image0 (after render)present request enqueuedreturns: request accepted, not yet on screen
  1. ApplicationSwapchain C and WSI

    vkAcquireNextImageKHR

  2. Swapchain C and WSIApplication

    Return. image index 0 (availability signalled)

  3. ApplicationQueue Q

    vkQueueSubmit: wait acquire, signal render done

  4. ApplicationQueue Q

    vkQueuePresentKHR image 0 (after render)

  5. Queue QSwapchain C and WSI

    present request enqueued

  6. Swapchain C and WSIApplication

    Return. returns: request accepted, not yet on screen

Time runs downward. Dashed arrows show returns.

Read the text version
  1. Application → Swapchain C and WSI: vkAcquireNextImageKHR
  2. Swapchain C and WSI → Application (return): image index 0 (availability signalled)
  3. Application → Queue Q: vkQueueSubmit: wait acquire, signal render done
  4. Application → Queue Q: vkQueuePresentKHR image 0 (after render)
  5. Queue Q → Swapchain C and WSI: present request enqueued
  6. Swapchain C and WSI → Application (return): returns: request accepted, not yet on screen
Conceptual model. Authored teaching model of Vulkan API ordering for one three-image swapchain, not a captured presentation trace. The Vulkan specification and the platform WSI govern real synchronization.

If the window is resized, an acquire or present can report an out-of-date or suboptimal condition depending on the interface and timing. The application must handle the returned result and recreate or adapt the swapchain as appropriate, while ensuring in-flight work no longer uses objects being destroyed. If creating the new swapchain reaches the Mesa allocation failure above, its partially created swapchain is destroyed on that branch. An application-level handle must not be treated as valid after creation failed. This is a conceptual timeline from source and API roles, not an observed presentation or GPU test.

Vulkan's synchronization model is explicit about many dependencies, but applications can still use extensions or external synchronization mechanisms that interact with implicit synchronization elsewhere. A fence is commonly used to observe completion from the CPU; a semaphore can coordinate queue operations; a pipeline barrier describes ordering and memory dependencies within the relevant command context. Correct stage/access scopes, image layouts and queue ownership matter. A buffer reference, a signaled fence and an image presented to a compositor are different facts. Consult the versioned Vulkan specification for the exact API contract used by a real application.

EGL, OpenGL, and GBM are distinct objects#

OpenGL exposes a stateful rendering API associated with a context. EGL connects rendering APIs to platform displays, contexts and surfaces; the platform can be Wayland, X11, a device-oriented environment or another supported path. A context can be made current to a thread under EGL's rules, and surfaces provide draw/read targets. OpenGL's implicit state and synchronization model differs from Vulkan's explicit command submission, even when both ultimately use the same kernel driver. Destroying a surface, unbinding a context and closing a DRM fd are distinct lifecycle steps.

GBM (Generic Buffer Management) supplies buffer-object and surface interfaces used by some graphics stacks. A GBM device, a GBM buffer object and a KMS framebuffer are not interchangeable. A GBM buffer object can describe format, dimensions, stride and modifier and can sometimes export a DMA-BUF fd; import and scanout depend on implementation and compatibility. src/gbm/main/gbm.c defines gbm_bo_create and gbm_bo_destroy. A compositor may instead receive buffers through a different platform protocol and copy or import them. GBM is not mandatory for every Vulkan or EGL application.

A shared buffer can pass through renderer, compositor and display controller with no copy only if their device, format, modifier and synchronization requirements align. A successful DMA-BUF export is not a zero-copy guarantee. A compositor can composite into another buffer, cross-GPU sharing can need migration or copying, and a fallback can use software. Explicit fences or sync objects and implicit synchronization have different availability and contracts across drivers and protocols. Version numbers alone cannot prove that all required extensions, kernel capabilities and hardware features are enabled. Query each component and handle unsupported features.

Failure, threads, and cleanup#

Creating an instance can succeed while physical-device enumeration yields no suitable device. Device creation can fail for unsupported features or resource exhaustion. A command submission can report device loss; a surface can become invalid independently. An application must check every result and free only objects that were successfully created, using the allocator and parent-device relationships required by the API. A resource cannot be freed while an in-flight queue operation still uses it. Waiting for idle is one possible coarse strategy when appropriate, but it has cost and can itself fail after device loss; it is not a substitute for a designed lifetime model.

Thread safety is also object-specific. Applications must follow Vulkan's external synchronization requirements for objects they access concurrently; a loader dispatch table does not serialize all application threads. EGL context-current rules are different. If a presentation callback or event arrives after an application has destroyed the associated window state, its own event-loop and object references must prevent use-after-free. No finite source walkthrough establishes all driver, window-system and platform edge cases.

Loader, layers, and ICD boundaries#

The loader parses driver manifests, opens candidate libraries and obtains required entry points under its interface rules. It can also construct a layer chain when layers are enabled. A layer can validate or intercept calls, but installing a validation layer does not guarantee that an application enabled it or that every runtime error is observable. An ICD is responsible for its implementation's device behavior; the loader is not itself the physical GPU driver. Manifest discovery, library loading, instance creation and physical-device enumeration are separate checkpoints with separate failure results.

Who owns what in the Vulkan stack
Application
owns instance, devices, queues, surface and swapchain objects
Vulkan loader
parses manifests, loads ICDs, builds the dispatch chain; not the GPU driver
Validation or tracing layers
optional interception when enabled; they do not prove errors are absent
ICD (driver)
implements physical devices, logical devices, queues and command submission
WSI
creates swapchains and presents images for the chosen platform
Kernel DRM and hardware
validate and execute device work under kernel policy
Conceptual model. Conceptual layering for the Vulkan loader v1.3.290 and Mesa 24.2.0 WSI paths named in this chapter. Layers and the ICD vary by installed configuration; this is not a captured trace.

In the pinned loader_icd_scan, an out-of-host-memory result aborts the scan through cleanup. An incompatible or unloadable driver follows a different branch and can be skipped while scanning other candidates. A machine can therefore have several JSON manifests and still expose no usable physical device, or expose a software ICD rather than the expected hardware driver. Changing loader environment variables can alter discovery and should be confined to an own process in a controlled environment. The read-only lab does not load any manifest or execute driver code.

Queue and image state over three frames#

Suppose swapchain C has images 0, 1 and 2. The application acquires image 0, records commands referencing it, and submits those commands with a wait on the acquire semaphore and a signal for presentation. It may then work on image 1, provided its own object and synchronization rules are satisfied. Image 0's presentation is queued after its rendering dependency. The CPU seeing a successful queue submission does not mean the GPU has finished; a fence can report completion of the associated work, and acquisition/presentation rules determine when an image can be reused. The application must track image layouts, access and queue-family ownership as required by the selected API and platform.

If surface S becomes out of date after image 1 is acquired, a later operation can report an out-of-date or suboptimal result. The application must not treat every old image as immediately free while commands or presentation still reference it. A recreation strategy can preserve the old swapchain until its users are finished, then destroy it under the API's rules. If the new swapchain's per-image allocation fails in the cited Mesa code, that newly created partial swapchain is destroyed before the error returns. These statements distinguish application-level ownership from the implementation's internal cleanup.

Sharing and format negotiation#

An image's dimensions, pixel format, memory layout, modifier, color interpretation and synchronization state all matter when crossing API or process boundaries. An exported fd can keep backing storage alive, but it does not carry every assumption required to interpret the image correctly. The receiver must negotiate supported formats and modifiers and use the applicable external-memory and synchronization interfaces. A compositor might directly scan out a compatible buffer, composite it with others, or copy it to a compatible allocation; all are possible outcomes. The presence of a DMA-BUF fd is not a measurement of which outcome occurred.

GBM is one way to obtain buffers suitable for certain Mesa/Linux paths. Its device, buffer object and surface lifetimes are separate from an EGL surface or a Vulkan swapchain. The KMS framebuffer created for scanout is another object with its own identifier and release sequence. An implementation can use different window-system integration without GBM, and a software renderer can use host memory. API version, loader version, Mesa version, enabled extensions, kernel driver and hardware capabilities must all be considered; the chosen historical source tags do not establish a supported feature matrix.

Result codes and object cleanup are part of the API#

A successful instance creation establishes an instance, not a promise that every requested device feature exists. Physical-device properties and extension support must be queried before selecting queues and features. Device creation can fail if the requested combination is unsupported; in that case the application still owns its earlier instance and surface objects and must clean them up under their respective parent relationships. Conversely, a successful logical device does not guarantee that a particular surface supports the desired presentation mode or image format.

In the cited Mesa WSI function, the platform interface first creates a swapchain and returns a result. Only after success does common code allocate its fence array. If that allocation fails, calling the swapchain's destroy method is an implementation cleanup of a partially created object; the application receives an error rather than a valid handle to use. A later surface loss or device loss follows different paths. A source-only timeline should identify the precise failure stage instead of assuming every error runs the same cleanup label.

A queue submission can complete while presentation remains pending, and a presentation request can be accepted before the display has shown the image. The application tracks the synchronization objects and image acquisition state required by its chosen WSI. It should also avoid assuming that a swapchain image is a host-mappable linear buffer: memory type, layout and implementation determine access. A CPU readback requires appropriate transfer, synchronization and cache-coherency handling, and no such readback occurs in the metadata lab.

A failed frame is not one universal failure#

If instance creation fails, the application has no valid instance to destroy, though it must release any independent host allocations it made. If instance creation succeeds but no physical device supports the requested queue and surface, the instance is still an acquired object that needs cleanup. If swapchain creation fails, the application must follow the API result and cannot use an output handle as though creation succeeded. If a queue later reports device loss, resource recovery and teardown are different from simply recreating an out-of-date surface. Naming the stage makes an error report actionable.

A window system can resize or remove a surface while rendering is in flight. The application should retain the resources needed by outstanding work until their use has completed under the API, then destroy objects in a valid order. A compositor can also copy or delay a submitted frame; a queue fence is not automatically a presentation-completion notification. The worked three-image timeline supplies a source-based model of those distinct states without asserting that the metadata lab created a window.

Practice#

Source-only route: read loader_icd_scan in Vulkan Loader v1.3.290 loader/loader.c and wsi_CreateSwapchainKHR in Mesa 24.2.0 src/vulkan/wsi/wsi_common.c. For the three-image example, list the objects already created when fence-array allocation fails and identify the source call that destroys the partial swapchain. Then explain why no device or screen was exercised by reading those files.

Read-only metadata:

sh
python3 - <<'PY'
from pathlib import Path
for name in ('/usr/share/vulkan/icd.d', '/usr/share/vulkan/explicit_layer.d'):
    p = Path(name)
    try: print(name, 'manifest files:', len(list(p.glob('*.json'))) if p.is_dir() else 0)
    except OSError as e: print(name, 'unavailable:', e.strerror)
PY

An empty directory, missing path or restricted view is valid. A manifest count does not load an ICD, enumerate a physical device, validate extensions or present an image. No GPU node is opened and no display is changed. Deliver an instance/device/queue/surface/swapchain lifetime diagram and the WSI failure timeline.

Knowledge check#

  1. What does the loader's out-of-host-memory branch do?
  2. Why does Mesa destroy the swapchain if its fence-array allocation fails?
  3. What is the difference between a physical device and a logical device?
  4. Why is a GBM buffer object not the same as a KMS framebuffer?
  5. Does returning from present prove that a swapchain image can immediately be overwritten?

Answers#

  1. It preserves the error result and exits through the scan's cleanup path.
  2. The platform swapchain was already created, so the partial object must be destroyed before reporting failure.
  3. The physical device describes an implementation/device and capabilities; the logical device is an application-created execution context with selected features and queues.
  4. A GBM BO describes buffer storage; a KMS framebuffer is a separate display object referencing compatible backing storage and layout.
  5. No. The application must follow acquire, present and synchronization rules before reusing the image.

Checkpoint#

Save the object-lifetime diagram, source failure path and manifest count or limitation. Pass when you distinguish loader, ICD, device, queue, surface and swapchain, and can explain why buffer sharing and synchronization do not automatically imply zero-copy display. Re-read the cited functions if needed. This is practice, not certification.

Sources#

A little further beneath the surface.

Check your understanding with the checkpoint. Marking a chapter as read saves a bookmark, not a proficiency assessment.