////
- Copyright (c) 2023-2025, Sascha Willems
-
- SPDX-License-Identifier: Apache-2.0
-
- Licensed under the Apache License, Version 2.0 the "License";
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
////
= Framework
// omit in toc
:pp: {plus}{plus}

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 xref:../scripts/README.adoc[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.

See link:./vulkan_sample.h[vulkan_sample.h] and link:./vulkan_sample.cpp[vulkan_sample.cpp].

=== API sample base class

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

See link:./api_vulkan_sample.h[api_vulkan_sample.h] and link:./api_vulkan_sample.cpp[api_vulkan_sample.cpp].

=== 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 https://github.com/KhronosGroup/Vulkan-Hpp[Vulkan-Hpp] variants, letting you write samples using the C{pp} Vulkan language bindings instead.

See link:./hpp_vulkan_sample.h[hpp_vulkan_sample.h] / link:./hpp_vulkan_sample.cpp[hpp_vulkan_sample.cpp] and link:./hpp_api_vulkan_sample.h[hpp_api_vulkan_sample.h] / link:./hpp_api_vulkan_sample.cpp[hpp_api_vulkan_sample.cpp].

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

[,cpp]
----
MySample::MySample()
{
    add_instance_extension(VK_SOME_INSTANCE_EXTENSION_NAME);
    add_device_extension(VK_SOME_DEVICE_EXTENSION_NAME);
}
----

NOTE: The framework uses the https://github.com/zeux/volk[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:

[,cpp]
----
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:

[,cpp]
----
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 https://github.com/ocornut/imgui[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:

[,cpp]
----
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:

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

=== Loading models

The framework supports https://www.khronos.org/gltf/[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:

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

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

[,cpp]
----
// 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 https://www.khronos.org/ktx/[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.

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

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

[,cpp]
----
VkDescriptorImageInfo  image_descriptor = create_descriptor(texture);
----

=== Loading shaders

The framework supports loading textual GLSL shaders.
These shaders are then compiled to https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html[SPIR-V] at runtime so Vulkan can ingest them.

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

[,cpp]
----
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:

[,cpp]
----
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));
----
