Vulkan-Samples/framework at main · KhronosGroup/Vulkan-Samples

GitHub

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.

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); }Note

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.1 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:

voidMySample::request_gpu_features(vkb::PhysicalDevice &gpu) { // Get a reference to the feature structure required for an extensionauto &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:

voidMySample::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:

voidMyApiSample::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:

boolMySample::prepare(const vkb::ApplicationOptions &options) { scene = load_scene("filename.gltf"); }With the api base class are explicitly declared, loaded and rendered:

// my_sample.hclassMyApiSample : publicApiVulkanSample { std::unique_ptr<vkb::sg::SubMesh> modelA; std::unique_ptr<vkb::sg::SubMesh> modelB; ... }// my_sample.cppboolMyApiSample::prepare(const vkb::ApplicationOptions &options) { modelA = load_model("filenameA.gltf"); modelB = load_model("filenameB.gltf"); } voidMyApiSample::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));