This commit is contained in:
xsl
2025-09-04 10:54:47 +08:00
commit 6bc8f61b18
1808 changed files with 208268 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
////
- Copyright (c) 2023-2024, The Khronos Group
-
- 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.
-
////
ifndef::tooling_samplespath[:tooling_samplespath:]
== Tooling Samples
The goal of these samples is to demonstrate usage of tooling functions and libraries that are not directly part of the api.
=== xref:./{tooling_samplespath}profiles/README.adoc[Profiles Library]
Use the https://github.com/KhronosGroup/Vulkan-Profiles[Vulkan Profiles library] to simplify instance and device setup.
The library defines a common baseline of features, extensions, etc.
+37
View File
@@ -0,0 +1,37 @@
# Copyright (c) 2022-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.
#
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
add_sample_with_tags(
ID ${FOLDER_NAME}
CATEGORY ${CATEGORY_NAME}
AUTHOR "Sascha Willems"
NAME "Using Vulkan profiles"
DESCRIPTION "Shows how to use a Vulkan profile to simplify device creation"
SHADER_FILES_GLSL
"profiles/glsl/profiles.vert"
"profiles/glsl/profiles.frag"
SHADER_FILES_HLSL
"profiles/hlsl/profiles.vert.hlsl"
"profiles/hlsl/profiles.frag.hlsl"
DXC_ADDITIONAL_ARGUMENTS "-fspv-extension=SPV_EXT_descriptor_indexing"
SHADER_FILES_SLANG
"profiles/slang/profiles.vert.slang"
"profiles/slang/profiles.frag.slang")
+158
View File
@@ -0,0 +1,158 @@
////
- Copyright (c) 2022-2024, 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.
-
////
= Using Vulkan profiles
ifdef::site-gen-antora[]
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/tooling/profiles[Khronos Vulkan samples github repository].
endif::[]
This sample demonstrates the usage of the https://github.com/KhronosGroup/Vulkan-Profiles[Vulkan Profiles Library].
Profiles define a common requirement baseline of properties, features, extensions, etc.
to make Vulkan applications more portable.
Instead of having to check all of these in your app one by one at runtime, you use the profiles library to check if the selected device supports all the requirements of that given profile.
If that's the case, you then use the same library to create the device and/or instance.
The library then takes care of enabling all the required features, extensions, etc. saving a lot of the otherwise common boilerplate.
We'll show this in a sample that makes use of descriptor indexing.
The sample uses the `VP_KHR_roadmap_2022` profile that enables a baseline of features and extensions for "newer mid-to-high-end devices shipping in 2022 or shortly thereafter across mainstream smartphone, tablet, laptops, console and desktop devices.".
Details on what this profile contains can be found link:https://docs.vulkan.org/spec/latest/appendices/roadmap.html#roadmap-2022[here].
== Without profiles
Without using the profiles library, and with an api version that doesn't have this as a core feature, one would have to manually enable features and extensions at device and/or instance creation.
This could look something like this:
[,cpp]
----
VkPhysicalDeviceFeatures enabled_features{};
VkPhysicalDeviceVulkan11Features enabled_features_11{};
VkPhysicalDeviceVulkan12Features enabled_features_12{};
enabled_features.fullDrawIndexUint32 = VK_TRUE;
enabled_features.imageCubeArray = VK_TRUE;
enabled_features.independentBlend = VK_TRUE;
enabled_features.ampleRateShading = VK_TRUE;
enabled_features.rawIndirectFirstInstance = VK_TRUE;
enabled_features.depthClamp = VK_TRUE;
enabled_features.depthBiasClamp = VK_TRUE;
enabled_features.samplerAnisotropy = VK_TRUE;
enabled_features.occlusionQueryPrecise = VK_TRUE;
enabled_features.fragmentStoresAndAtomics = VK_TRUE;
enabled_features.shaderStorageImageExtendedFormats = VK_TRUE;
enabled_features.shaderUniformBufferArrayDynamicIndexing = VK_TRUE;
enabled_features.shaderSampledImageArrayDynamicIndexing = VK_TRUE;
enabled_features.shaderStorageBufferArrayDynamicIndexing = VK_TRUE;
enabled_features.shaderStorageImageArrayDynamicIndexing = VK_TRUE;
...
enabled_features_11.samplerYcbcrConversion = VK_TRUE;
enabled_features_11.pNext = &enabled_features_12;
...
enabled_features_12.samplerMirrorClampToEdge = VK_TRUE;
enabled_features_12.descriptorIndexing = VK_TRUE;
enabled_features_12.shaderUniformTexelBufferArrayDynamicIndexing = VK_TRUE;
...
VkDeviceCreateInfo device_create_info = {};
device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_create_info.pNext = &enabled_features_11;
VkResult result = vkCreateDevice(...);
if (!result) {
app_terminate('Could not create device, reason:' + vk_result_str(result));
}
----
With each additional device feature this code would get longer and longer, as physical device features need to be chained via the `pNext` member.
== With profiles
Using the profile library and a profile that requires all the above features and extension this can be simplified a lot:
[,cpp]
----
// Profile to enable
const VpProfileProperties profile_properties = {VP_KHR_ROADMAP_2022_NAME, VP_KHR_ROADMAP_2022_SPEC_VERSION};
// Instance creation
VkBool32 profile_supported;
vpGetInstanceProfileSupport(nullptr, &profile_properties, &profile_supported);
if (!profile_supported) {
app_terminate("The selected profile is not supported!");
}
VkInstanceCreateInfo create_info{};
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
create_info.ppEnabledExtensionNames = enabled_extensions.data();
create_info.enabledExtensionCount = static_cast<uint32_t>(enabled_extensions.size());
VpInstanceCreateInfo instance_create_info{};
instance_create_info.pEnabledFullProfiles = &profile_properties;
instance_create_info.enabledFullProfileCount = 1;
instance_create_info.pCreateInfo = &create_info;
result = vpCreateInstance(&instance_create_info, nullptr, &vulkan_instance);
// Device creation
std::vector<const char *> enabled_extensions;
enabled_extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
VkDeviceCreateInfo create_info{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
create_info.pNext = gpu.get_extension_feature_chain();
create_info.pQueueCreateInfos = &queue_create_info;
create_info.queueCreateInfoCount = 1;
create_info.enabledExtensionCount = static_cast<uint32_t>(enabled_extensions.size());
create_info.ppEnabledExtensionNames = enabled_extensions.data();
VkBool32 profile_supported;
vpGetPhysicalDeviceProfileSupport(instance->get_handle(), gpu.get_handle(), &profile_properties, &profile_supported);
if (!profile_supported) {
app_terminate("The selected profile is not supported (error at creating the device)!");
}
VpDeviceCreateInfo deviceCreateInfo{};
deviceCreateInfo.pCreateInfo = &create_info;
deviceCreateInfo.pEnabledFullProfiles = &profile_properties;
deviceCreateInfo.enabledFullProfileCount = 1;
VkResult result = vpCreateDevice(gpu.get_handle(), &deviceCreateInfo, nullptr, &vulkan_device);
----
This will enable all features and extensions defined in the selected profile, including descriptor indexing. By passing `pCreateInfo` of the instance and/or device you can also enable additional extensions not part of the profile.
Inspecting the device created using a profile in a graphics debugger like https://renderdoc.org/[RenderDoc] we can see that the profile library did the following based on the link:https://docs.vulkan.org/spec/latest/appendices/roadmap.html#roadmap-2022[profile] we selected:
Set the appropriate Vulkan version at instance creation:
image::./renderdoc_instance.png[RenderDoc instance Api version]
Enabled device features and setup the pNext chain:
image::./renderdoc_device_1.png[RenderDoc device pNext]
And also enabled all required extensions (incl. explicitly requested ones):
image::./renderdoc_device_2.png[RenderDoc device extensions]
=== Conclusion
Using profiles makes setting up a baseline of extensions and features a lot easier. It also saves lots of code as you no longer have to worry about enabling extensions, setting up their feature structs and properly chaining the `pNext` structures.
+744
View File
@@ -0,0 +1,744 @@
/* Copyright (c) 2022-2025, Sascha Willems
* Copyright (c) 2024-2025, Arm Limited and Contributors
*
* 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.
*/
/*
* Using Vulkan profiles from the LunarG SDK for device and instance setup
*/
#include "profiles.h"
#include "common/error.h"
#include "common/vk_common.h"
#include "core/command_pool.h"
#include "core/queue.h"
#include "fence_pool.h"
// The Vulkan Profiles library is part of the SDK and has been copied to the sample's folder for convenience
#include "vulkan_profiles.hpp"
// This sample will use the Khronos roadmap 2022 profile which requires Vulkan 1.3
// For details on what this profile requires/enables, see https://docs.vulkan.org/spec/latest/appendices/roadmap.html#roadmap-2022
const VpProfileProperties profile_properties = {VP_KHR_ROADMAP_2022_NAME, VP_KHR_ROADMAP_2022_SPEC_VERSION};
Profiles::Profiles()
{
title = "Vulkan Profiles";
}
Profiles::~Profiles()
{
if (has_device())
{
// Clean up used Vulkan resources
// Note : Inherited destructor cleans up resources stored in base class
for (auto &tex : textures)
vkFreeMemory(get_device().get_handle(), tex.memory, nullptr);
vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr);
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
vkDestroyDescriptorSetLayout(get_device().get_handle(), base_descriptor_set_layout, nullptr);
vkDestroyDescriptorSetLayout(get_device().get_handle(), sampler_descriptor_set_layout, nullptr);
}
}
// This sample overrides the device creation part of the framework
// Instead of manually setting up all extensions, features, etc. we use the Vulkan Profiles library to simplify device setup
std::unique_ptr<vkb::core::DeviceC> Profiles::create_device(vkb::PhysicalDevice &gpu)
{
// Check if the profile is supported at device level
VkBool32 profile_supported;
vpGetPhysicalDeviceProfileSupport(get_instance().get_handle(), gpu.get_handle(), &profile_properties, &profile_supported);
if (!profile_supported)
{
throw std::runtime_error{"The selected profile is not supported (error at creating the device)!"};
}
// If the profile is supported, we can start setting things up and use the profiles library for that
// Simplified queue setup (only graphics)
uint32_t selected_queue_family = 0;
const auto &queue_family_properties = gpu.get_queue_family_properties();
const float default_queue_priority{0.0f};
VkDeviceQueueCreateInfo queue_create_info{};
queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_create_info.queueCount = 1;
queue_create_info.pQueuePriorities = &default_queue_priority;
for (uint32_t i = 0; i < static_cast<uint32_t>(queue_family_properties.size()); i++)
{
if (queue_family_properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
{
queue_create_info.queueFamilyIndex = i;
selected_queue_family = i;
break;
}
}
std::vector<const char *> enabled_extensions;
enabled_extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
VkDeviceCreateInfo create_info{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
create_info.pNext = gpu.get_extension_feature_chain();
create_info.pQueueCreateInfos = &queue_create_info;
create_info.queueCreateInfoCount = 1;
create_info.enabledExtensionCount = static_cast<uint32_t>(enabled_extensions.size());
create_info.ppEnabledExtensionNames = enabled_extensions.data();
// Create the device using the profiles library
VpDeviceCreateInfo deviceCreateInfo{};
deviceCreateInfo.pCreateInfo = &create_info;
deviceCreateInfo.pEnabledFullProfiles = &profile_properties;
deviceCreateInfo.enabledFullProfileCount = 1;
VkDevice vulkan_device;
VkResult result = vpCreateDevice(gpu.get_handle(), &deviceCreateInfo, nullptr, &vulkan_device);
if (result != VK_SUCCESS)
{
throw vkb::VulkanException{result, "Could not create device with the selected profile. The device may not support all features required by this profile!"};
}
// Post device setup required for the framework
auto device = std::make_unique<vkb::core::DeviceC>(gpu, vulkan_device, get_surface());
device->add_queue(0, queue_create_info.queueFamilyIndex, queue_family_properties[selected_queue_family], true);
vkb::allocated::init(*device); // prepare the memory allocator
device->create_internal_command_pool();
device->create_internal_fence_pool();
return device;
}
// This sample overrides the instance creation part of the framework
// Instead of manually setting up all properties we use the Vulkan Profiles library to simplify instance setup
std::unique_ptr<vkb::core::InstanceC> Profiles::create_instance()
{
// Initialize Volk Vulkan Loader
VkResult result = volkInitialize();
if (result)
{
throw vkb::VulkanException(result, "Failed to initialize volk.");
}
// Check if the profile is supported at instance level
VkBool32 profile_supported;
vpGetInstanceProfileSupport(nullptr, &profile_properties, &profile_supported);
if (!profile_supported)
{
throw std::runtime_error{"The selected profile is not supported (error at creating the instance)!"};
}
// Even when using profiles we still need to provide the platform specific get_surface() extension
std::vector<const char *> enabled_extensions;
enabled_extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
for (const char *extension_name : window->get_required_surface_extensions())
{
enabled_extensions.push_back(extension_name);
}
VkInstanceCreateInfo create_info{};
#if (defined(VKB_ENABLE_PORTABILITY))
uint32_t instance_extension_count;
VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, nullptr));
std::vector<VkExtensionProperties> available_instance_extensions(instance_extension_count);
VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, available_instance_extensions.data()));
// If VK_KHR_portability_enumeration is available at runtime, enable the extension and flag for instance creation
if (std::ranges::any_of(available_instance_extensions,
[](VkExtensionProperties const &extension) { return strcmp(extension.extensionName, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) == 0; }))
{
enabled_extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
create_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
}
#endif
#if defined(PLATFORM__MACOS)
// On Apple use layer setting to enable MoltenVK's Metal argument buffers - needed for descriptor indexing/scaling
enabled_extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME);
VkLayerSettingEXT layerSetting{};
layerSetting.pLayerName = "MoltenVK";
layerSetting.pSettingName = "MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS";
layerSetting.type = VK_LAYER_SETTING_TYPE_INT32_EXT;
layerSetting.valueCount = 1;
const int32_t useMetalArgumentBuffers = 1;
layerSetting.pValues = &useMetalArgumentBuffers;
VkLayerSettingsCreateInfoEXT layerSettingsCreateInfo{};
layerSettingsCreateInfo.sType = VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT;
layerSettingsCreateInfo.settingCount = 1;
layerSettingsCreateInfo.pSettings = &layerSetting;
create_info.pNext = &layerSettingsCreateInfo;
#endif
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
create_info.ppEnabledExtensionNames = enabled_extensions.data();
create_info.enabledExtensionCount = static_cast<uint32_t>(enabled_extensions.size());
// Note: We don't explicitly set an application info here so the one from the profile is used
// This also defines the api version to be used
// Create the instance using the profiles library
VpInstanceCreateInfo instance_create_info{};
instance_create_info.pEnabledFullProfiles = &profile_properties;
instance_create_info.enabledFullProfileCount = 1;
instance_create_info.pCreateInfo = &create_info;
VkInstance vulkan_instance;
result = vpCreateInstance(&instance_create_info, nullptr, &vulkan_instance);
if (result != VK_SUCCESS)
{
throw vkb::VulkanException{result, "Could not create instance with the selected profile. The instance may not support all features required by this profile!"};
}
volkLoadInstance(vulkan_instance);
return std::make_unique<vkb::core::InstanceC>(vulkan_instance);
}
void Profiles::generate_textures()
{
// Generate random textures to be sourced from a single descriptor
// Image info is same for all textures
const int32_t dim = 2;
VkImageCreateInfo image_info = vkb::initializers::image_create_info();
image_info.format = VK_FORMAT_R8G8B8A8_UNORM;
image_info.extent = {dim, dim, 1};
image_info.mipLevels = 1;
image_info.arrayLayers = 1;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkImageViewCreateInfo image_view = vkb::initializers::image_view_create_info();
image_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
image_view.format = VK_FORMAT_R8G8B8A8_UNORM;
image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_view.subresourceRange.baseMipLevel = 0;
image_view.subresourceRange.levelCount = 1;
image_view.subresourceRange.baseArrayLayer = 0;
image_view.subresourceRange.layerCount = 1;
auto staging_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), image_info.extent.width * image_info.extent.height * sizeof(uint32_t), nullptr);
textures.resize(32);
for (size_t i = 0; i < textures.size(); i++)
{
VK_CHECK(vkCreateImage(get_device().get_handle(), &image_info, nullptr, &textures[i].image));
VkMemoryAllocateInfo memory_allocation_info = vkb::initializers::memory_allocate_info();
VkMemoryRequirements memory_requirements;
vkGetImageMemoryRequirements(get_device().get_handle(), textures[i].image, &memory_requirements);
memory_allocation_info.allocationSize = memory_requirements.size;
memory_allocation_info.memoryTypeIndex = get_device().get_gpu().get_memory_type(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VK_CHECK(vkAllocateMemory(get_device().get_handle(), &memory_allocation_info, nullptr, &textures[i].memory));
VK_CHECK(vkBindImageMemory(get_device().get_handle(), textures[i].image, textures[i].memory, 0));
image_view.image = textures[i].image;
VK_CHECK(vkCreateImageView(get_device().get_handle(), &image_view, nullptr, &textures[i].image_view));
// Generate a random texture
std::random_device rnd_device;
std::default_random_engine rnd_engine(rnd_device());
std::uniform_int_distribution<short> rnd_dist(0, 255);
const size_t buffer_size = dim * dim * 4;
uint8_t *buffer = staging_buffer.map();
for (size_t i = 0; i < dim * dim; i++)
{
buffer[i * 4] = static_cast<uint8_t>(rnd_dist(rnd_engine));
buffer[i * 4 + 1] = static_cast<uint8_t>(rnd_dist(rnd_engine));
buffer[i * 4 + 2] = static_cast<uint8_t>(rnd_dist(rnd_engine));
buffer[i * 4 + 3] = 255;
}
staging_buffer.unmap();
staging_buffer.flush();
auto cmd = get_device().get_command_pool().request_command_buffer();
cmd->begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
vkb::image_layout_transition(cmd->get_handle(), textures[i].image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
VkBufferImageCopy copy_info{};
copy_info.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
copy_info.imageExtent = image_info.extent;
vkCmdCopyBufferToImage(cmd->get_handle(), staging_buffer.get_handle(), textures[i].image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy_info);
vkb::image_layout_transition(cmd->get_handle(), textures[i].image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
cmd->end();
auto const &graphicsQueue = get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0);
graphicsQueue.submit(*cmd, VK_NULL_HANDLE);
graphicsQueue.wait_idle();
}
// Create immutable sampler for the textures
VkSamplerCreateInfo sampler_info = vkb::initializers::sampler_create_info();
sampler_info.magFilter = VK_FILTER_NEAREST;
sampler_info.minFilter = VK_FILTER_NEAREST;
sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler_info.mipLodBias = 0.0f;
sampler_info.compareOp = VK_COMPARE_OP_NEVER;
sampler_info.minLod = 0.0f;
sampler_info.maxLod = 0.0f;
sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler_info, nullptr, &sampler));
}
void Profiles::generate_cubes()
{
// Generate cubes with randomized per-face texture indices (as base for descriptor indexing)
std::vector<VertexStructure> vertices;
std::vector<uint32_t> indices;
// Generate random per-face texture indices
std::random_device rndDevice;
std::default_random_engine rndEngine(rndDevice());
std::uniform_int_distribution<int32_t> rndDist(0, static_cast<uint32_t>(textures.size()) - 1);
// Generate cubes with random per-face texture indices
const uint32_t count = 6;
for (uint32_t i = 0; i < count; i++)
{
// Get a random texture index that the shader will sample from via the vertex attribute
const auto texture_index = [&rndDist, &rndEngine]() {
return rndDist(rndEngine);
};
// Push vertices to buffer
float pos = 2.5f * i - (count * 2.5f / 2.0f);
const std::vector<VertexStructure> cube = {
{{-1.0f + pos, -1.0f, 1.0f}, {0.0f, 0.0f}, texture_index()},
{{1.0f + pos, -1.0f, 1.0f}, {1.0f, 0.0f}, texture_index()},
{{1.0f + pos, 1.0f, 1.0f}, {1.0f, 1.0f}, texture_index()},
{{-1.0f + pos, 1.0f, 1.0f}, {0.0f, 1.0f}, texture_index()},
{{1.0f + pos, 1.0f, 1.0f}, {0.0f, 0.0f}, texture_index()},
{{1.0f + pos, 1.0f, -1.0f}, {1.0f, 0.0f}, texture_index()},
{{1.0f + pos, -1.0f, -1.0f}, {1.0f, 1.0f}, texture_index()},
{{1.0f + pos, -1.0f, 1.0f}, {0.0f, 1.0f}, texture_index()},
{{-1.0f + pos, -1.0f, -1.0f}, {0.0f, 0.0f}, texture_index()},
{{1.0f + pos, -1.0f, -1.0f}, {1.0f, 0.0f}, texture_index()},
{{1.0f + pos, 1.0f, -1.0f}, {1.0f, 1.0f}, texture_index()},
{{-1.0f + pos, 1.0f, -1.0f}, {0.0f, 1.0f}, texture_index()},
{{-1.0f + pos, -1.0f, -1.0f}, {0.0f, 0.0f}, texture_index()},
{{-1.0f + pos, -1.0f, 1.0f}, {1.0f, 0.0f}, texture_index()},
{{-1.0f + pos, 1.0f, 1.0f}, {1.0f, 1.0f}, texture_index()},
{{-1.0f + pos, 1.0f, -1.0f}, {0.0f, 1.0f}, texture_index()},
{{1.0f + pos, 1.0f, 1.0f}, {0.0f, 0.0f}, texture_index()},
{{-1.0f + pos, 1.0f, 1.0f}, {1.0f, 0.0f}, texture_index()},
{{-1.0f + pos, 1.0f, -1.0f}, {1.0f, 1.0f}, texture_index()},
{{1.0f + pos, 1.0f, -1.0f}, {0.0f, 1.0f}, texture_index()},
{{-1.0f + pos, -1.0f, -1.0f}, {0.0f, 0.0f}, texture_index()},
{{1.0f + pos, -1.0f, -1.0f}, {1.0f, 0.0f}, texture_index()},
{{1.0f + pos, -1.0f, 1.0f}, {1.0f, 1.0f}, texture_index()},
{{-1.0f + pos, -1.0f, 1.0f}, {0.0f, 1.0f}, texture_index()},
};
for (auto &vertex : cube)
{
vertices.push_back(vertex);
}
// Push indices to buffer
const std::vector<uint32_t> cubeIndices = {
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23};
for (auto &index : cubeIndices)
{
indices.push_back(index + static_cast<uint32_t>(vertices.size()));
}
}
index_count = static_cast<uint32_t>(indices.size());
auto vertex_buffer_size = vkb::to_u32(vertices.size() * sizeof(VertexStructure));
auto index_buffer_size = vkb::to_u32(indices.size() * sizeof(uint32_t));
// Create buffers
// For the sake of simplicity we won't stage the vertex data to the gpu memory
vertex_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
vertex_buffer_size,
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VMA_MEMORY_USAGE_CPU_TO_GPU);
vertex_buffer->update(vertices.data(), vertex_buffer_size);
index_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
index_buffer_size,
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
VMA_MEMORY_USAGE_CPU_TO_GPU);
index_buffer->update(indices.data(), index_buffer_size);
}
void Profiles::build_command_buffers()
{
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
VkClearValue clear_values[2]{};
clear_values[0].color = default_clear_color;
clear_values[1].depthStencil = {0.0f, 0};
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
render_pass_begin_info.renderPass = render_pass;
render_pass_begin_info.renderArea.offset.x = 0;
render_pass_begin_info.renderArea.offset.y = 0;
render_pass_begin_info.renderArea.extent.width = width;
render_pass_begin_info.renderArea.extent.height = height;
render_pass_begin_info.clearValueCount = 2;
render_pass_begin_info.pClearValues = clear_values;
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
{
render_pass_begin_info.framebuffer = framebuffers[i];
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport = vkb::initializers::viewport(static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f);
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
VkRect2D scissor = vkb::initializers::rect2D(static_cast<int32_t>(width), static_cast<int32_t>(height), 0, 0);
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &base_descriptor_set, 0, nullptr);
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
VkDeviceSize offsets[1] = {0};
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, vertex_buffer->get(), offsets);
vkCmdBindIndexBuffer(draw_cmd_buffers[i], index_buffer->get_handle(), 0, VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(draw_cmd_buffers[i], index_count, 1, 0, 0, 0);
draw_ui(draw_cmd_buffers[i]);
vkCmdEndRenderPass(draw_cmd_buffers[i]);
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
}
}
void Profiles::draw()
{
ApiVulkanSample::prepare_frame();
// Command buffer to be submitted to the queue
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
// Submit to queue
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
ApiVulkanSample::submit_frame();
}
void Profiles::setup_descriptor_pool()
{
std::vector<VkDescriptorPoolSize> pool_sizes = {
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast<uint32_t>(textures.size()))};
VkDescriptorPoolCreateInfo descriptor_pool_create_info =
vkb::initializers::descriptor_pool_create_info(
static_cast<uint32_t>(pool_sizes.size()),
pool_sizes.data(),
3);
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
}
void Profiles::setup_descriptor_set_layout()
{
// We separate the descriptor sets for the uniform buffer + image and samplers, so we don't need to duplicate the descriptors for the former
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings{};
VkDescriptorSetLayoutCreateInfo descriptor_layout_create_info{};
descriptor_layout_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
// Mark second slot as variable for descriptor indexing
VkDescriptorSetLayoutBindingFlagsCreateInfoEXT descriptor_set_layout_binding_flags{};
descriptor_set_layout_binding_flags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT;
descriptor_set_layout_binding_flags.bindingCount = 2;
std::vector<VkDescriptorBindingFlagsEXT> descriptor_binding_flags = {
0,
VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT_EXT};
descriptor_set_layout_binding_flags.pBindingFlags = descriptor_binding_flags.data();
descriptor_layout_create_info.pNext = &descriptor_set_layout_binding_flags;
// Set layout for the uniform buffer and the image
set_layout_bindings = {
// Binding 0 : Vertex shader uniform buffer
vkb::initializers::descriptor_set_layout_binding(
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
VK_SHADER_STAGE_VERTEX_BIT,
0),
// Binding 1 : Fragment shader combined image and sampler
vkb::initializers::descriptor_set_layout_binding(
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
VK_SHADER_STAGE_FRAGMENT_BIT,
1,
static_cast<uint32_t>(textures.size()))};
descriptor_layout_create_info.bindingCount = static_cast<uint32_t>(set_layout_bindings.size());
descriptor_layout_create_info.pBindings = set_layout_bindings.data();
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &base_descriptor_set_layout));
// Set layout for the samplers
set_layout_bindings = {
// Binding 0: Fragment shader sampler
vkb::initializers::descriptor_set_layout_binding(
VK_DESCRIPTOR_TYPE_SAMPLER,
VK_SHADER_STAGE_FRAGMENT_BIT,
0,
static_cast<uint32_t>(textures.size()))};
descriptor_layout_create_info.bindingCount = static_cast<uint32_t>(set_layout_bindings.size());
descriptor_layout_create_info.pBindings = set_layout_bindings.data();
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout_create_info, nullptr, &sampler_descriptor_set_layout));
// Pipeline layout
// Set layout for the base descriptors in set 0 and set layout for the sampler descriptors in set 1
std::vector<VkDescriptorSetLayout> set_layouts = {base_descriptor_set_layout, sampler_descriptor_set_layout};
VkPipelineLayoutCreateInfo pipeline_layout_create_info =
vkb::initializers::pipeline_layout_create_info(
set_layouts.data(),
static_cast<uint32_t>(set_layouts.size()));
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
}
void Profiles::setup_descriptor_set()
{
// We separate the descriptor sets for the uniform buffer + image and samplers, so we don't need to duplicate the descriptors for the former
VkDescriptorSetAllocateInfo descriptor_set_alloc_info{};
// Descriptors set for the uniform buffer and the image
descriptor_set_alloc_info =
vkb::initializers::descriptor_set_allocate_info(
descriptor_pool,
&base_descriptor_set_layout,
1);
VkDescriptorSetVariableDescriptorCountAllocateInfoEXT variableDescriptorCountAllocInfo = {};
uint32_t variableDescCounts[] = {static_cast<uint32_t>(textures.size())};
variableDescriptorCountAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT;
variableDescriptorCountAllocInfo.descriptorSetCount = 1;
variableDescriptorCountAllocInfo.pDescriptorCounts = variableDescCounts;
descriptor_set_alloc_info.pNext = &variableDescriptorCountAllocInfo;
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &descriptor_set_alloc_info, &base_descriptor_set));
VkDescriptorBufferInfo buffer_descriptor = create_descriptor(*uniform_buffer_vs);
// Combined image descriptor for the texture
VkDescriptorImageInfo image_descriptor{};
image_descriptor.imageView = textures[0].image_view;
image_descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
image_descriptor.sampler = sampler;
std::vector<VkWriteDescriptorSet> write_descriptor_sets(2);
// Binding 0 : Vertex shader uniform buffer
write_descriptor_sets[0] = vkb::initializers::write_descriptor_set(
base_descriptor_set,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
0,
&buffer_descriptor);
// Binding 1 : Fragment shader sampled image
// Put all images into a single array
std::vector<VkDescriptorImageInfo> texture_descriptors(textures.size());
for (size_t i = 0; i < textures.size(); i++)
{
texture_descriptors[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
texture_descriptors[i].sampler = sampler;
texture_descriptors[i].imageView = textures[i].image_view;
}
// Unlike an array texture, these are addressed like typical arrays
write_descriptor_sets[1] = {};
write_descriptor_sets[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write_descriptor_sets[1].dstBinding = 1;
write_descriptor_sets[1].dstArrayElement = 0;
write_descriptor_sets[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write_descriptor_sets[1].descriptorCount = static_cast<uint32_t>(textures.size());
write_descriptor_sets[1].pBufferInfo = 0;
write_descriptor_sets[1].dstSet = base_descriptor_set;
write_descriptor_sets[1].pImageInfo = texture_descriptors.data();
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, nullptr);
}
void Profiles::prepare_pipelines()
{
VkPipelineInputAssemblyStateCreateInfo input_assembly_state =
vkb::initializers::pipeline_input_assembly_state_create_info(
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
0,
VK_FALSE);
VkPipelineRasterizationStateCreateInfo rasterization_state =
vkb::initializers::pipeline_rasterization_state_create_info(
VK_POLYGON_MODE_FILL,
VK_CULL_MODE_NONE,
VK_FRONT_FACE_CLOCKWISE,
0);
VkPipelineColorBlendAttachmentState blend_attachment_state =
vkb::initializers::pipeline_color_blend_attachment_state(
0xf,
VK_FALSE);
VkPipelineColorBlendStateCreateInfo color_blend_state =
vkb::initializers::pipeline_color_blend_state_create_info(
1,
&blend_attachment_state);
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
VkPipelineDepthStencilStateCreateInfo depth_stencil_state =
vkb::initializers::pipeline_depth_stencil_state_create_info(
VK_TRUE,
VK_TRUE,
VK_COMPARE_OP_GREATER);
VkPipelineViewportStateCreateInfo viewport_state =
vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisample_state =
vkb::initializers::pipeline_multisample_state_create_info(
VK_SAMPLE_COUNT_1_BIT,
0);
std::vector<VkDynamicState> dynamic_state_enables = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR};
VkPipelineDynamicStateCreateInfo dynamic_state =
vkb::initializers::pipeline_dynamic_state_create_info(
dynamic_state_enables.data(),
static_cast<uint32_t>(dynamic_state_enables.size()),
0);
// Load shaders
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{};
shader_stages[0] = load_shader("profiles", "profiles.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
shader_stages[1] = load_shader("profiles", "profiles.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
// Vertex bindings and attributes
const std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
vkb::initializers::vertex_input_binding_description(0, sizeof(VertexStructure), VK_VERTEX_INPUT_RATE_VERTEX),
};
const std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VertexStructure, pos)),
vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(VertexStructure, uv)),
vkb::initializers::vertex_input_attribute_description(0, 2, VK_FORMAT_R32_SINT, offsetof(VertexStructure, texture_index)),
};
VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
vertex_input_state.vertexBindingDescriptionCount = static_cast<uint32_t>(vertex_input_bindings.size());
vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data();
vertex_input_state.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size());
vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes.data();
VkGraphicsPipelineCreateInfo pipeline_create_info = vkb::initializers::pipeline_create_info(pipeline_layout, render_pass, 0);
pipeline_create_info.pVertexInputState = &vertex_input_state;
pipeline_create_info.pInputAssemblyState = &input_assembly_state;
pipeline_create_info.pRasterizationState = &rasterization_state;
pipeline_create_info.pColorBlendState = &color_blend_state;
pipeline_create_info.pMultisampleState = &multisample_state;
pipeline_create_info.pViewportState = &viewport_state;
pipeline_create_info.pDepthStencilState = &depth_stencil_state;
pipeline_create_info.pDynamicState = &dynamic_state;
pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
pipeline_create_info.pStages = shader_stages.data();
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipeline));
}
// Prepare and initialize uniform buffer containing shader uniforms
void Profiles::prepare_uniform_buffers()
{
// Vertex shader uniform buffer block
uniform_buffer_vs = std::make_unique<vkb::core::BufferC>(get_device(),
sizeof(ubo_vs),
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VMA_MEMORY_USAGE_CPU_TO_GPU);
update_uniform_buffers();
}
void Profiles::update_uniform_buffers()
{
// Fixed ubo with projection and view matrices
ubo_vs.projection = camera.matrices.perspective;
ubo_vs.view = camera.matrices.view;
uniform_buffer_vs->convert_and_update(ubo_vs);
}
bool Profiles::prepare(const vkb::ApplicationOptions &options)
{
if (!ApiVulkanSample::prepare(options))
{
return false;
}
camera.type = vkb::CameraType::LookAt;
camera.set_position(glm::vec3(0.0f, 0.0f, -10.0f));
camera.set_rotation(glm::vec3(0.0f));
// Note: Using reversed depth-buffer for increased precision, so Znear and Zfar are flipped
camera.set_perspective(60.0f, static_cast<float>(width) / static_cast<float>(height), 256.0f, 0.1f);
generate_textures();
generate_cubes();
prepare_uniform_buffers();
setup_descriptor_set_layout();
prepare_pipelines();
setup_descriptor_pool();
setup_descriptor_set();
build_command_buffers();
prepared = true;
return true;
}
void Profiles::render(float delta_time)
{
if (!prepared)
{
return;
}
draw();
}
void Profiles::view_changed()
{
update_uniform_buffers();
}
void Profiles::on_update_ui_overlay(vkb::Drawer &drawer)
{
drawer.text("Enabled profile: %s", profile_properties.profileName);
}
std::unique_ptr<vkb::Application> create_profiles()
{
return std::make_unique<Profiles>();
}
+88
View File
@@ -0,0 +1,88 @@
/* Copyright (c) 2022-2025, Sascha Willems
* Copyright (c) 2024-2025, Arm Limited and Contributors
*
* 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.
*/
/*
* Using Vulkan profiles from the LunarG SDK for device and instance setup
*/
#pragma once
#include "common/vk_common.h"
#include "api_vulkan_sample.h"
class Profiles : public ApiVulkanSample
{
public:
// Vertex layout for this example
struct VertexStructure
{
float pos[3];
float uv[2];
int32_t texture_index;
};
struct RandomTexture
{
VkImage image{};
VkImageView image_view{};
VkDeviceMemory memory{};
};
std::vector<RandomTexture> textures;
VkSampler sampler{};
std::unique_ptr<vkb::core::BufferC> vertex_buffer;
std::unique_ptr<vkb::core::BufferC> index_buffer;
uint32_t index_count{0};
std::unique_ptr<vkb::core::BufferC> uniform_buffer_vs;
struct
{
glm::mat4 projection;
glm::mat4 view;
} ubo_vs;
VkPipeline pipeline{VK_NULL_HANDLE};
VkPipelineLayout pipeline_layout{VK_NULL_HANDLE};
VkDescriptorSet base_descriptor_set{VK_NULL_HANDLE};
VkDescriptorSetLayout base_descriptor_set_layout{VK_NULL_HANDLE};
VkDescriptorSetLayout sampler_descriptor_set_layout{VK_NULL_HANDLE};
Profiles();
~Profiles() override;
void generate_textures();
void generate_cubes();
void build_command_buffers() override;
void draw();
void setup_descriptor_pool();
void setup_descriptor_set_layout();
void setup_descriptor_set();
void prepare_pipelines();
void prepare_uniform_buffers();
void update_uniform_buffers();
bool prepare(const vkb::ApplicationOptions &options) override;
void render(float delta_time) override;
void view_changed() override;
std::unique_ptr<vkb::core::DeviceC> create_device(vkb::PhysicalDevice &gpu) override;
std::unique_ptr<vkb::core::InstanceC> create_instance() override;
virtual void on_update_ui_overlay(vkb::Drawer &drawer) override;
};
std::unique_ptr<vkb::Application> create_profiles();
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because it is too large Load Diff