Skip to content

Commit

Permalink
Fix support for --headless. (#1090)
Browse files Browse the repository at this point in the history
* Fix support for --headless.

Now --headless will create a swap_chain and a surface, now the feature will work correctly.
Remove headless parameter from Instance::Instance and VulkanSample<>::create_instance.
VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME  added in HeadlessWindow::get_required_surface_extensions like other window types
Added an assert if headless is enabled to hello_triangle and hpp_hello_triangle

* Change flag name to headless_surface.
Use offscreen when speaking about no swapchain
Add warning when using headless on multiple GPUs
  • Loading branch information
iagoCL authored Nov 13, 2024
1 parent 76c3144 commit 1cea0da
Show file tree
Hide file tree
Showing 22 changed files with 103 additions and 89 deletions.
8 changes: 6 additions & 2 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,12 @@ vulkan_samples sample swapchain_images
# Run AFBC sample in benchmark mode for 5000 frames
vulkan_samples sample afbc --benchmark --stop-after-frame 5000
# Run bonza test offscreen
vulkan_samples test bonza --headless
# Run compute nbody using headless_surface and take a screenshot of frame 5
# Note: headless_surface uses VK_EXT_headless_surface.
# This will create a surface and a Swapchain, but present will be a no op.
# The extension is supported by Swiftshader(https://github.com/google/swiftshader).
# It allows to quickly test content in environments without a GPU.
vulkan_samples sample compute_nbody --headless_surface -screenshot 5
# Run all the performance samples for 10 seconds in each configuration
vulkan_samples batch --category performance --duration 10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ public void setArguments(String... args) {
arguments.add("--benchmark");
}
if (isHeadless) {
arguments.add("--headless");
arguments.add("--headless_surface");
}
String[] argArray = new String[arguments.size()];
sendArgumentsToPlatform(arguments.toArray(argArray));
Expand Down
4 changes: 2 additions & 2 deletions app/plugins/window_options/window_options.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (c) 2020-2022, Arm Limited and Contributors
/* Copyright (c) 2020-2024, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
Expand Down Expand Up @@ -47,7 +47,7 @@ class WindowOptions : public WindowOptionsTags
vkb::FlagCommand width_flag = {vkb::FlagType::OneValue, "width", "", "Initial window width"};
vkb::FlagCommand height_flag = {vkb::FlagType::OneValue, "height", "", "Initial window height"};
vkb::FlagCommand fullscreen_flag = {vkb::FlagType::FlagOnly, "fullscreen", "", "Run in fullscreen mode"};
vkb::FlagCommand headless_flag = {vkb::FlagType::FlagOnly, "headless", "", "Run in headless mode"};
vkb::FlagCommand headless_flag = {vkb::FlagType::FlagOnly, "headless_surface", "", "Run in headless surface mode. A Surface and swap-chain is still created using VK_EXT_headless_surface."};
vkb::FlagCommand borderless_flag = {vkb::FlagType::FlagOnly, "borderless", "", "Run in borderless mode"};
vkb::FlagCommand stretch_flag = {vkb::FlagType::FlagOnly, "stretch", "", "Stretch window to fullscreen (direct-to-display only)"};
vkb::FlagCommand vsync_flag = {vkb::FlagType::OneValue, "vsync", "", "Force vsync {ON | OFF}. If not set samples decide how vsync is set"};
Expand Down
12 changes: 5 additions & 7 deletions framework/api_vulkan_sample.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* Copyright (c) 2019-2024, Sascha Willems
* Copyright (c) 2024, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
Expand Down Expand Up @@ -49,13 +50,10 @@ bool ApiVulkanSample::prepare(const vkb::ApplicationOptions &options)
submit_info = vkb::initializers::submit_info();
submit_info.pWaitDstStageMask = &submit_pipeline_stages;

if (window->get_window_mode() != vkb::Window::Mode::Headless)
{
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &semaphores.acquired_image_ready;
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &semaphores.render_complete;
}
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &semaphores.acquired_image_ready;
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &semaphores.render_complete;

queue = get_device().get_suitable_graphics_queue().get_handle();

Expand Down
27 changes: 11 additions & 16 deletions framework/core/hpp_instance.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* Copyright (c) 2022-2024, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2024, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
Expand Down Expand Up @@ -187,7 +188,6 @@ HPPInstance::HPPInstance(const std::string &applicati
const std::unordered_map<const char *, bool> &required_extensions,
const std::vector<const char *> &required_validation_layers,
const std::vector<vk::LayerSettingEXT> &required_layer_settings,
bool headless,
uint32_t api_version)
{
std::vector<vk::ExtensionProperties> available_instance_extensions = vk::enumerateInstanceExtensionProperties();
Expand Down Expand Up @@ -232,20 +232,11 @@ HPPInstance::HPPInstance(const std::string &applicati
}
#endif

// Try to enable headless surface extension if it exists
if (headless)
{
const bool has_headless_surface = enable_extension(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME,
available_instance_extensions, enabled_extensions);
if (!has_headless_surface)
{
LOGW("{} is not available, disabling swapchain creation", VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
}
}
else
{
enabled_extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
}
// Specific surface extensions are obtained from Window::get_required_surface_extensions
// They are already added to required_extensions by VulkanSample::prepare

// If using VK_EXT_headless_surface, we still create and use a surface
enabled_extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);

// VK_KHR_get_physical_device_properties2 is a prerequisite of VK_KHR_performance_query
// which will be used for stats gathering where available.
Expand Down Expand Up @@ -445,7 +436,7 @@ vk::Instance HPPInstance::get_handle() const
return handle;
}

vkb::core::HPPPhysicalDevice &HPPInstance::get_suitable_gpu(vk::SurfaceKHR surface)
vkb::core::HPPPhysicalDevice &HPPInstance::get_suitable_gpu(vk::SurfaceKHR surface, bool headless_surface)
{
assert(!gpus.empty() && "No physical devices were found on the system.");

Expand All @@ -459,6 +450,10 @@ vkb::core::HPPPhysicalDevice &HPPInstance::get_suitable_gpu(vk::SurfaceKHR surfa
}
return *gpus[selected_gpu_index.value()];
}
if ( headless_surface )
{
LOGW("Using headless surface with multiple GPUs. Considered explicitly selecting the target GPU.")
}

// Find a discrete GPU
for (auto &gpu : gpus)
Expand Down
7 changes: 4 additions & 3 deletions framework/core/hpp_instance.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* Copyright (c) 2022-2024, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2024, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
Expand Down Expand Up @@ -54,15 +55,13 @@ class HPPInstance
* @param required_extensions The extensions requested to be enabled
* @param required_validation_layers The validation layers to be enabled
* @param required_layer_settings The layer settings to be enabled
* @param headless Whether the application is requesting a headless setup or not
* @param api_version The Vulkan API version that the instance will be using
* @throws runtime_error if the required extensions and validation layers are not found
*/
HPPInstance(const std::string &application_name,
const std::unordered_map<const char *, bool> &required_extensions = {},
const std::vector<const char *> &required_validation_layers = {},
const std::vector<vk::LayerSettingEXT> &required_layer_settings = {},
bool headless = false,
uint32_t api_version = VK_API_VERSION_1_0);

/**
Expand Down Expand Up @@ -94,9 +93,11 @@ class HPPInstance
/**
* @brief Tries to find the first available discrete GPU that can render to the given surface
* @param surface to test against
* @param headless_surface Is surface created with VK_EXT_headless_surface
* @returns A valid physical device
*/
HPPPhysicalDevice &get_suitable_gpu(vk::SurfaceKHR);
HPPPhysicalDevice &get_suitable_gpu(vk::SurfaceKHR surface, bool headless_surface);

/**
* @brief Checks if the given extension is enabled in the vk::Instance
Expand Down
26 changes: 10 additions & 16 deletions framework/core/instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ Instance::Instance(const std::string &application_nam
const std::unordered_map<const char *, bool> &required_extensions,
const std::vector<const char *> &required_validation_layers,
const std::vector<VkLayerSettingEXT> &required_layer_settings,
bool headless,
uint32_t api_version)
{
uint32_t instance_extension_count;
Expand Down Expand Up @@ -240,20 +239,11 @@ Instance::Instance(const std::string &application_nam
}
#endif

// Try to enable headless surface extension if it exists
if (headless)
{
const bool has_headless_surface = enable_extension(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME,
available_instance_extensions, enabled_extensions);
if (!has_headless_surface)
{
LOGW("{} is not available, disabling swapchain creation", VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
}
}
else
{
enabled_extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
}
// Specific surface extensions are obtained from Window::get_required_surface_extensions
// They are already added to required_extensions by VulkanSample::prepare

// Even for a headless surface a swapchain is still required
enabled_extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);

// VK_KHR_get_physical_device_properties2 is a prerequisite of VK_KHR_performance_query
// which will be used for stats gathering where available.
Expand Down Expand Up @@ -503,7 +493,7 @@ PhysicalDevice &Instance::get_first_gpu()
return *gpus[0];
}

PhysicalDevice &Instance::get_suitable_gpu(VkSurfaceKHR surface)
PhysicalDevice &Instance::get_suitable_gpu(VkSurfaceKHR surface, bool headless_surface)
{
assert(!gpus.empty() && "No physical devices were found on the system.");

Expand All @@ -517,6 +507,10 @@ PhysicalDevice &Instance::get_suitable_gpu(VkSurfaceKHR surface)
}
return *gpus[selected_gpu_index.value()];
}
if (headless_surface)
{
LOGW("Using headless surface with multiple GPUs. Considered explicitly selecting the target GPU.")
}

// Find a discrete GPU
for (auto &gpu : gpus)
Expand Down
5 changes: 2 additions & 3 deletions framework/core/instance.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,13 @@ class Instance
* @param required_extensions The extensions requested to be enabled
* @param required_validation_layers The validation layers to be enabled
* @param required_layer_settings The layer settings to be enabled
* @param headless Whether the application is requesting a headless setup or not
* @param api_version The Vulkan API version that the instance will be using
* @throws runtime_error if the required extensions and validation layers are not found
*/
Instance(const std::string &application_name,
const std::unordered_map<const char *, bool> &required_extensions = {},
const std::vector<const char *> &required_validation_layers = {},
const std::vector<VkLayerSettingEXT> &required_layer_settings = {},
bool headless = false,
uint32_t api_version = VK_API_VERSION_1_0);

/**
Expand Down Expand Up @@ -88,9 +86,10 @@ class Instance
/**
* @brief Tries to find the first available discrete GPU that can render to the given surface
* @param surface to test against
* @param headless_surface Is surface created with VK_EXT_headless_surface
* @returns A valid physical device
*/
PhysicalDevice &get_suitable_gpu(VkSurfaceKHR);
PhysicalDevice &get_suitable_gpu(VkSurfaceKHR surface, bool headless_surface);

/**
* @brief Tries to find the first available discrete GPU
Expand Down
8 changes: 3 additions & 5 deletions framework/hpp_api_vulkan_sample.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* Copyright (c) 2021-2024, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2024, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
Expand Down Expand Up @@ -46,11 +47,8 @@ bool HPPApiVulkanSample::prepare(const vkb::ApplicationOptions &options)
submit_info = vk::SubmitInfo();
submit_info.pWaitDstStageMask = &submit_pipeline_stages;

if (window->get_window_mode() != vkb::Window::Mode::Headless)
{
submit_info.setWaitSemaphores(semaphores.acquired_image_ready);
submit_info.setSignalSemaphores(semaphores.render_complete);
}
submit_info.setWaitSemaphores(semaphores.acquired_image_ready);
submit_info.setSignalSemaphores(semaphores.render_complete);

queue = get_device().get_suitable_graphics_queue().get_handle();

Expand Down
20 changes: 15 additions & 5 deletions framework/platform/headless_window.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2023, Arm Limited and Contributors
/* Copyright (c) 2018-2024, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
Expand Down Expand Up @@ -26,12 +26,22 @@ HeadlessWindow::HeadlessWindow(const Window::Properties &properties) :

VkSurfaceKHR HeadlessWindow::create_surface(Instance &instance)
{
return VK_NULL_HANDLE;
return create_surface(instance.get_handle(), VK_NULL_HANDLE);
}

VkSurfaceKHR HeadlessWindow::create_surface(VkInstance, VkPhysicalDevice)
VkSurfaceKHR HeadlessWindow::create_surface(VkInstance instance, VkPhysicalDevice)
{
return VK_NULL_HANDLE;
VkSurfaceKHR surface = VK_NULL_HANDLE;

if (instance)
{
VkHeadlessSurfaceCreateInfoEXT info{};
info.sType = VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT;

VK_CHECK(vkCreateHeadlessSurfaceEXT(instance, &info, VK_NULL_HANDLE, &surface));
}

return surface;
}

bool HeadlessWindow::should_close()
Expand All @@ -52,6 +62,6 @@ float HeadlessWindow::get_dpi_factor() const

std::vector<const char *> HeadlessWindow::get_required_surface_extensions() const
{
return {};
return {VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME};
}
} // namespace vkb
6 changes: 4 additions & 2 deletions framework/platform/headless_window.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2023, Arm Limited and Contributors
/* Copyright (c) 2018-2024, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
Expand All @@ -22,7 +22,9 @@
namespace vkb
{
/**
* @brief Surface-less implementation of a Window, for use in headless rendering
* @brief Surface-less implementation of a Window using VK_EXT_headless_surface.
* A surface and swapchain are still created but the the present operation resolves to a no op.
* Useful for testing and benchmarking in CI environments.
*/
class HeadlessWindow : public Window
{
Expand Down
11 changes: 6 additions & 5 deletions framework/rendering/hpp_render_context.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* Copyright (c) 2023-2024, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2024, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
Expand Down Expand Up @@ -95,7 +96,7 @@ void HPPRenderContext::update_swapchain(const vk::Extent2D &extent)
{
if (!swapchain)
{
LOGW("Can't update the swapchains extent in headless mode, skipping.");
LOGW("Can't update the swapchains extent. No swapchain, offscreen rendering detected, skipping.");
return;
}

Expand All @@ -110,7 +111,7 @@ void HPPRenderContext::update_swapchain(const uint32_t image_count)
{
if (!swapchain)
{
LOGW("Can't update the swapchains image count in headless mode, skipping.");
LOGW("Can't update the swapchains image count. No swapchain, offscreen rendering detected, skipping.");
return;
}

Expand All @@ -127,7 +128,7 @@ void HPPRenderContext::update_swapchain(const std::set<vk::ImageUsageFlagBits> &
{
if (!swapchain)
{
LOGW("Can't update the swapchains image usage in headless mode, skipping.");
LOGW("Can't update the swapchains image usage. No swapchain, offscreen rendering detected, skipping.");
return;
}

Expand All @@ -142,7 +143,7 @@ void HPPRenderContext::update_swapchain(const vk::Extent2D &extent, const vk::Su
{
if (!swapchain)
{
LOGW("Can't update the swapchains extent and surface transform in headless mode, skipping.");
LOGW("Can't update the swapchains extent and surface transform. No swapchain, offscreen rendering detected, skipping.");
return;
}

Expand Down Expand Up @@ -199,7 +200,7 @@ bool HPPRenderContext::handle_surface_changes(bool force_update)
{
if (!swapchain)
{
LOGW("Can't handle surface changes in headless mode, skipping.");
LOGW("Can't handle surface changes. No swapchain, offscreen rendering detected, skipping.");
return false;
}

Expand Down
5 changes: 3 additions & 2 deletions framework/rendering/hpp_render_context.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved.
/* Copyright (c) 2022-2024, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2024, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
Expand Down Expand Up @@ -40,7 +41,7 @@ class HPPRenderContext
/**
* @brief Constructor
* @param device A valid device
* @param surface A surface, nullptr if in headless mode
* @param surface A surface, nullptr if in offscreen mode
* @param window The window where the surface was created
* @param present_mode Requests to set the present mode of the swapchain
* @param present_mode_priority_list The order in which the swapchain prioritizes selecting its present mode
Expand Down
Loading

0 comments on commit 1cea0da

Please sign in to comment.