Offline debugging in Vulkan with VK_EXT_debug_marker and RenderDoc

Intro

The Vulkan validation layers included with the LunarG SDK are a must for debugging applications at run-time, and every Vulkan developer should get used to them as soon as possible. They are crucial for getting applications validated against the specification and ensure portability across different implementations.

But what they can’t catch are logical errors, so even with all validation layers enabled and all errors eliminated you still may not see what you where expecting and need an additional layer of debugging your rendering step-by-step.

This is where offline graphics debugging applications like RenderDoc come into play. RenderDoc is able to capture a frame of your application, including all API calls, objects and the complete pipeline state and displays all of that information within a nice UI.

While that’s already pretty useful for debugging purposes, Vulkan 1.0.12 introduced the new extension “VK_EXT_debug_marker” which is similar to OpenGL’s GL_KHR_Debug extension and adds the ability to name and tag objects and insert debug regions and markers to be displayed by an offline debugger.

This short tutorial will show you what new functionality this extension introduces and also includes a practical chapter, along with an open source C++ example, to demonstrate it’s usage in a Vulkan application and the graphics debugger.

Prerequisites

Enable capture in debugging application

Before starting to use the new extension, make sure you have at least one registered Vulkan layer with support for VK_EXT_debug_maker. The layer (along with the extension on this layer) should be enabled by the offline debugging application.

If you’re using RenderDoc open the “Capture executable” tab and check for a warning message:

renderdoc_capture_warning

If you see this warning, RenderDoc’s capture layer has not yet been registered with the loader. Click on the warning and confirm the next dialog. After that the layer should be registered and you can start using RenderDoc with Vulkan. If you are on Windows  you can check the list of layers registered by external applications (called “implicit layers”) by checking the HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\ImplicitLayers registry key.

Vulkan headers

VK_EXT_debug_marker has been added with Vulkan header revision 12. So if you wan to use it in your own application grab the latest header or LunarG SDK. This tutorial is based on an example from my Vulkan C++ examples that includes a recent header.

Initialization

Now that we have the layer of a debugging application registered, we can start using it in our application. Note that the extension is only present if the application is run inside of the debugger.

Check extension availability

The first step is to check if the extension is available on the device we want to use for debugging. This is only true of the application is run from the debugger that has enabled it’s layer:

Enable extension

VK_EXT_debug_marker is a device extension (see specs) and as such has to be enabled during device creation:

Note that it’s important to check if the extension is present first, as otherwise device creation may fail.

Acquire function pointers

Before we can use the new functions added by the debug marker extension, we need to acquire device function pointers for them:

 

New functionality

Once the extension has been enabled on the device and the function pointers have been retrieved, we can start using the new functionality offered by the extension in our application.

Naming objects

The extension allows you to name all the different object types available in Vulkan. This includes images, samplers, all sorts of buffers, pipelines, synchronization objects, pools and much more. These names can then be displayed by an offline debugging application to help keeping track of them.

Naming information is set using vkDebugMarkerSetObjectName, which takes in a VkDebugMarkerObjectNameInfoEXT structure that contains the type of object to be named as well as it’s handle. Object names can be set anywhere in your code after the object has been created. All object identifiers in Vulkan are stored as 64-bit handles, so it’s safe to cast all objects to be named as 64-bit unsigned integers:

As with most Vulkan functions you should encapsulate the above code into a function that hides away the boilerplate:

If you want to save even more boilerplate you could use some overloads, templates or dedicated functions (e.g. setSamplerName, setPipelineName) that preset the objectType member of the name info structure (see e.g. vulkandebug.cpp from my example base code).

Tagging objects

It’s also possible to add multiple tags to your objects with arbitrary data:

Debug markers and regions

In addition to naming and tagging objects the extension also adds the ability to place debug markers inside command buffers. These can be used to mark points of interest and highlight specific areas inside of the command buffer.

Note that contrary to naming objects, debug markers (and regions) have to placed inside of an active command buffer, between vkBeginCommandBuffer and vkEndCommandBuffer.

In addition to object names, debug markers can also pass color information to the debugging app for better visualization.

Debug markers

Debug markers can be placed anywhere inside an active command buffer and don’t have to be encapsulated by a debug marker region:

Debug marker regions

While debug markers can be used to mark points of interest, debug marker regions can be used to group and organize API calls. Markers set inside a region will be contained within it and the regions also support nesting, which is useful for complex scenarios.

In practice

C++ example application

For the practical part of this tutorial I wrote a simple C++ application that will be used to demonstrate the usage of the new extension along with an offline debugging application.

Sources: https://github.com/SaschaWillems/Vulkan/tree/master/debugmarker (as a part of my C++ Vulkan example repository)

Binaries:  windowslogoWindows (64-bit, 7-zip) and linuxlogoLinux (64-bit, tar.gz)

The example creates multiple pipelines and uses two render passes. The first one is an off-screen pass that renders the glow parts of the scene to a low resolution frame buffer that is copied to a texture target. The second pass renders the scene with multiple draw calls using a simple toon shader and then draws a full-screen quad using the off-screen texture to apply a glow effect:

debugmarker

It also uses the example base class text overlay to display presence of the debug marker extension. If run outside of a debugger it should state that the extension is not present.

The example encapsulates the debug marker functionality within a namespace:

 

RenderDoc

RenderDoc is a stand-alone graphics debugger that has been released alongside the LunarG Vulkan SDK, and as such is one of the first to support Vulkan. It’s developer (Baldur Karlsson) also wrote the specification for the debug marker extension.

As the debugger is constantly evolving it’s advised to either use the latest build from here, or (better) compile a current version from the sources. For this example we do the latter. If you aren’t familiar with RenderDoc yet, you can check out it’s documentation over here.

Capture

Start up RenderDoc and select the example application’s binary for capturing. You may also want to queue a capture under “Actions” so that RenderDoc automatically captures the second frame (if not, use F12 to capture at any point):

renderdoc_capture_start

Press “capture” to start the application and the capture. If everything was set up correctly, the text overlay in the example should state the the extension is active and you can either wait for the queued capture to be triggered or trigger it yourself.

As an alternative you can also load a capture (.rdc) that contains the whole frame. But note that captures may not be compatible across different Vulkan API levels.

Capture log: renderdoc_icon debugmarker_log.rdc (Note that this may not work with future RenderDoc versions!)

Visualization

Now close the application (after a frame has been captured). RenderDoc will load the frame capture and display the final result of the render pass along with all API calls for that frame. Note that the UI may differ depending on how (or if) you have set up your layout. One thing you can immediately spot are the debug marker regions set inside the example applications.

If you use the latest RenderDoc version built from source you get them displayed with their colors passed from the example:

renderdoc_full

The event browser will display all calls of the captured frame, while the API calls pane displays all calls made between the currently selected and previous draw call, including the state of that API call (e.g. regions for buffer copies).

This also means you can replay a whole frame by moving through the event browser step by step:

renderdoc_framedraw

Debug marker regions

Most noticeable are the debug marker regions, as they add an important layer of structuring to the event browser including colors. Comparing a frame without debug marker regions and colors to one with these enabled makes the advantages obvious:

renderdoc_events_noregions  renderdoc_events_regions

The debug regions are also visualized on the frame’s timeline:

renderdoc_regions_timeline

Here is the stripped down code from the example that sets up the nested debug marker regions for rendering the scene:

Debug markers

As the example only uses one vertex and index buffer for the whole scene, offsetting the indices for rendering the separate scene parts, it uses debug markers to annotate what part of the scene is submitted by the next draw call. If your application uses one buffer per mesh you could instead name that buffer:

 

Source:

Named objects

Unnamed objects show up with numerical IDs, making it hard to tell what they actually refer to in complex scenarios. Putting names on these things make it easy to track and identify the Vulkan objects in different parts of the pipeline displayed by RenderDoc.

For example buffers in the vertex input pipeline state:

renderdoc_buffers_noname  renderdoc_buffers_named

Source:

Shader modules in the fragment shader pipeline state:

renderdoc_shaders_named

Source:

The final verdict

With the recently added VK_EXT_debug_marker extension and offline debugging tools like RenderDoc, debugging your actual render code in Vulkan is now even easier than before. While the validation layers will help you getting your code validated against the specifications and avoiding error, offline debugging allows you to check the render composition and resource usage in detail, and with the ability to define your own debug regions and markers and name the different Vulkan objects, debugging even complex apps should now be a lot easier.