Framework

This folder contains the base framework used by the samples. It offers sample base classes, encapsulates common functionality for e.g. loading assets (images, models, shaders), wraps common Vulkan objects and implements frequently used concepts like a cache and a scene graph. The framework also implements platform support for Windows, Linux, MacOS and Android.

It can be used as a guideline for writing advanced Vulkan applications.

Before trying to implement common functions, consider checking if the framework doesn’t already provides what you are looking for.

Sample base classes

The framework provides two different sample base classes. When creating new samples, you can choose between one of them:

High level base sample class

This base class abstracts away most of the Vulkan API calls and as such makes heavy use of the Vulkan object wrapper classes of the framework. Writing samples with the base class is less verbose.

API sample base class

This base class uses less abstraction, letting you work more explicitly with the api.

Support for Vulkan-Hpp

While the framework itself primarily uses the C-Interface for Vulkan, both the high level and the API sample base class also come with Vulkan-Hpp variants, letting you write samples using the C++ Vulkan language bindings instead.

Commonly used framework concepts

Enabling extensions

Vulkan is an extensible api. New features are usually exposed through either instance or device extensions. Extensions can be enabled in the constructor of both the high level and api base sample class:

MySample::MySample()
{
    add_instance_extension(VK_SOME_INSTANCE_EXTENSION_NAME);
    add_device_extension(VK_SOME_DEVICE_EXTENSION_NAME);
}
The framework uses the Volk meta-loader, which will automatically load extension function pointers for all enabled extensions. There is no need to manually get extension pointer functions.

Changing the Vulkan api version

By default all samples create a Vulkan 1.0 instance. Higher versions can be requested in the constructor of a sample:

MySample::MySample()
{
    set_api_version(VK_API_VERSION_1_2);
}

Requesting GPU features

Most extensions also require enabling corresponding feature flags. This can be done by overriding the request_gpu_features function of the base class:

void MySample::request_gpu_features(vkb::PhysicalDevice &gpu)
{
    // Get a reference to the feature structure required for an extension
    auto &requested_extension_feature = gpu
    request_extension_features<VkPhysicalDeviceSomeExtensionFeaturesKHR>(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SOME_EXTENSION_FEATURES_KHR);

    // Enable a selected feature
    requested_extension_feature.featureName = VK_TRUE;
}

Extending the graphical user interface

The framework includes a graphical user interface based on Dear ImGui. This can be used by samples to display values and add controls like buttons, dropdowns, etc.

To add additional elements to the UI of a sample, you override the respective function from the base class:

Samples based on the high level base class need to override the draw_gui function:

void MySample::draw_gui()
{
    if (ImGui::Checkbox("Enable Option", &option_enabled))
    {
        ...
    }
}

Samples based on the api base class need to override the on_update_ui_overlay function:

void MyApiSample::on_update_ui_overlay(vkb::Drawer &drawer)
{
     if (drawer.checkbox("Enable option", &option_enabled))
    {
        ...
    }
}

Loading models

The framework supports glTF models and includes a loader for this format.

The high level base class works with a single glTF scene loaded at startup. The scene is part of the base class, and there is no need to explicitly draw it:

bool MySample::prepare(const vkb::ApplicationOptions &options)
{
    scene = load_scene("filename.gltf");
}

With the api base class are explicitly declared, loaded and rendered:

// my_sample.h
class MyApiSample : public ApiVulkanSample
{
    std::unique_ptr<vkb::sg::SubMesh> modelA;
    std::unique_ptr<vkb::sg::SubMesh> modelB;
    ...
}

// my_sample.cpp
bool MyApiSample::prepare(const vkb::ApplicationOptions &options)
{
    modelA = load_model("filenameA.gltf");
    modelB = load_model("filenameB.gltf");
}

void MyApiSample::build_command_buffers()
{
    vkBeginCommandBuffer(...);
    ...
    draw_model(modelA, draw_cmd_buffers[i]);
    ...
    draw_model(modelB, draw_cmd_buffers[i]);
    ...
    vkEndCommandBufer(...);
}

Loading images

The framework supports the KTX GPU container format and includes a loader for this format. As a container format, KTX supports different image formats ranging from basic RGBA images to compressed formats.

texture = load_texture("rgba_texture.ktx", vkb::sg::Image::Color);

Images (textures) loaded like this can then be used as descriptors later on:

VkDescriptorImageInfo  image_descriptor = create_descriptor(texture);

Loading shaders

The framework supports loading textual GLSL shaders. These shaders are then compiled to SPIR-V at runtime so Vulkan can ingest them.

With the high level base class, shaders are attached to the scene graphics' render pipeline:

vkb::ShaderSource vert_shader("vs.vert");
vkb::ShaderSource frag_shader("fs.frag");
auto              scene_subpass = std::make_unique<vkb::ForwardSubpass>(get_render_context(), std::move(vert_shader), std::move(frag_shader), *scene, *camera);

auto render_pipeline = vkb::RenderPipeline();
render_pipeline.add_subpass(std::move(scene_subpass));

set_render_pipeline(std::move(render_pipeline));

While in the api base class, this is again more explicitly by creating shader modules used at pipeline creation time:

std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages;
...
shader_stages[0] = load_shader("vs.vert", VK_SHADER_STAGE_VERTEX_BIT);
shader_stages[1] = load_shader("fs.frag", VK_SHADER_STAGE_FRAGMENT_BIT);
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipeline));