1061 lines
37 KiB
C++
1061 lines
37 KiB
C++
/* Copyright (c) 2023-2025, Holochip Corporation
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include "full_screen_exclusive.h"
|
|
|
|
#include "filesystem/legacy.h"
|
|
#include "platform/window.h"
|
|
|
|
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
|
|
static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagsEXT message_type,
|
|
const VkDebugUtilsMessengerCallbackDataEXT *callback_data,
|
|
void *user_data)
|
|
{
|
|
(void) user_data;
|
|
|
|
if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
|
|
{
|
|
LOGE("{} Validation Layer: Error: {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage)
|
|
}
|
|
else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)
|
|
{
|
|
LOGE("{} Validation Layer: Warning: {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage)
|
|
}
|
|
else if (message_type & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT)
|
|
{
|
|
LOGI("{} Validation Layer: Performance warning: {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage)
|
|
}
|
|
else
|
|
{
|
|
LOGI("{} Validation Layer: Information: {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage)
|
|
}
|
|
return VK_FALSE;
|
|
}
|
|
#endif
|
|
|
|
bool FullScreenExclusive::validate_extensions(const std::vector<const char *> &required, const std::vector<VkExtensionProperties> &available)
|
|
{
|
|
for (auto extension : required)
|
|
{
|
|
bool found = false;
|
|
|
|
for (auto &available_extension : available)
|
|
{
|
|
if (strcmp(available_extension.extensionName, extension) == 0)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FullScreenExclusive::init_instance(const std::vector<const char *> &required_instance_extensions, const std::vector<const char *> &required_validation_layers)
|
|
{
|
|
LOGI("Initializing vulkan instance.")
|
|
|
|
if (volkInitialize())
|
|
{
|
|
throw std::runtime_error("Failed to initialize volk.");
|
|
}
|
|
|
|
uint32_t instance_extension_count;
|
|
VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, nullptr));
|
|
|
|
std::vector<VkExtensionProperties> instance_extensions(instance_extension_count);
|
|
VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, instance_extensions.data()));
|
|
|
|
std::vector<const char *> active_instance_extensions(required_instance_extensions);
|
|
|
|
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
|
|
active_instance_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
|
#endif
|
|
|
|
#if (defined(VKB_ENABLE_PORTABILITY))
|
|
active_instance_extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
|
active_instance_extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
|
#endif
|
|
|
|
// Add Instance extensions for full screen exclusive
|
|
active_instance_extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
|
|
active_instance_extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
|
active_instance_extensions.push_back(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME);
|
|
// Get the HWND application window handle and stores it in the class variable
|
|
initialize_windows();
|
|
|
|
if (!validate_extensions(active_instance_extensions, instance_extensions))
|
|
{
|
|
throw std::runtime_error("Required instance extensions are missing.");
|
|
}
|
|
|
|
std::vector<const char *> requested_instance_layers(required_validation_layers);
|
|
|
|
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
|
|
char const *validationLayer = "VK_LAYER_KHRONOS_validation";
|
|
|
|
uint32_t instance_layer_count;
|
|
VK_CHECK(vkEnumerateInstanceLayerProperties(&instance_layer_count, nullptr));
|
|
|
|
std::vector<VkLayerProperties> supported_instance_layers(instance_layer_count);
|
|
VK_CHECK(vkEnumerateInstanceLayerProperties(&instance_layer_count, supported_instance_layers.data()));
|
|
|
|
if (std::ranges::any_of(supported_instance_layers, [&validationLayer](auto const &lp) { return strcmp(lp.layerName, validationLayer) == 0; }))
|
|
{
|
|
requested_instance_layers.push_back(validationLayer);
|
|
LOGI("Enabled Validation Layer {}", validationLayer);
|
|
}
|
|
else
|
|
{
|
|
LOGW("Validation Layer {} is not available", validationLayer);
|
|
}
|
|
#endif
|
|
|
|
VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO};
|
|
app.pApplicationName = "Full Screen Exclusive";
|
|
app.pEngineName = "Vulkan Samples";
|
|
app.apiVersion = VK_API_VERSION_1_1;
|
|
|
|
VkInstanceCreateInfo instance_info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
|
instance_info.pApplicationInfo = &app;
|
|
instance_info.enabledExtensionCount = vkb::to_u32(active_instance_extensions.size());
|
|
instance_info.ppEnabledExtensionNames = active_instance_extensions.data();
|
|
instance_info.enabledLayerCount = vkb::to_u32(requested_instance_layers.size());
|
|
instance_info.ppEnabledLayerNames = requested_instance_layers.data();
|
|
|
|
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
|
|
VkDebugUtilsMessengerCreateInfoEXT debug_utils_create_info = {VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
|
|
debug_utils_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
|
|
debug_utils_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
|
|
debug_utils_create_info.pfnUserCallback = debug_callback;
|
|
|
|
instance_info.pNext = &debug_utils_create_info;
|
|
#endif
|
|
|
|
#if (defined(VKB_ENABLE_PORTABILITY))
|
|
instance_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
|
#endif
|
|
|
|
VK_CHECK(vkCreateInstance(&instance_info, nullptr, &context.instance));
|
|
volkLoadInstance(context.instance);
|
|
|
|
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
|
|
VK_CHECK(vkCreateDebugUtilsMessengerEXT(context.instance, &debug_utils_create_info, nullptr, &context.debug_callback));
|
|
#endif
|
|
}
|
|
|
|
void FullScreenExclusive::init_device(const std::vector<const char *> &required_device_extensions)
|
|
{
|
|
LOGI("Initializing vulkan device.")
|
|
|
|
uint32_t gpu_count = 0;
|
|
VK_CHECK(vkEnumeratePhysicalDevices(context.instance, &gpu_count, nullptr));
|
|
|
|
if (gpu_count < 1)
|
|
{
|
|
throw std::runtime_error("No physical device found.");
|
|
}
|
|
|
|
std::vector<VkPhysicalDevice> gpus(gpu_count);
|
|
VK_CHECK(vkEnumeratePhysicalDevices(context.instance, &gpu_count, gpus.data()));
|
|
|
|
for (uint32_t i = 0; i < gpu_count && (context.graphics_queue_index < 0); i++)
|
|
{
|
|
context.gpu = gpus[i];
|
|
|
|
uint32_t queue_family_count;
|
|
vkGetPhysicalDeviceQueueFamilyProperties(context.gpu, &queue_family_count, nullptr);
|
|
|
|
if (queue_family_count < 1)
|
|
{
|
|
throw std::runtime_error("No queue family found.");
|
|
}
|
|
|
|
std::vector<VkQueueFamilyProperties> queue_family_properties(queue_family_count);
|
|
vkGetPhysicalDeviceQueueFamilyProperties(context.gpu, &queue_family_count, queue_family_properties.data());
|
|
|
|
for (uint32_t j = 0; j < queue_family_count; j++)
|
|
{
|
|
VkBool32 supports_present;
|
|
vkGetPhysicalDeviceSurfaceSupportKHR(context.gpu, j, context.surface, &supports_present);
|
|
|
|
if ((queue_family_properties[j].queueFlags & VK_QUEUE_GRAPHICS_BIT) && supports_present)
|
|
{
|
|
context.graphics_queue_index = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (context.graphics_queue_index < 0)
|
|
{
|
|
LOGE("Did not find suitable queue which supports graphics, and presentation.")
|
|
}
|
|
|
|
uint32_t device_extension_count;
|
|
VK_CHECK(vkEnumerateDeviceExtensionProperties(context.gpu, nullptr, &device_extension_count, nullptr));
|
|
std::vector<VkExtensionProperties> device_extensions(device_extension_count);
|
|
VK_CHECK(vkEnumerateDeviceExtensionProperties(context.gpu, nullptr, &device_extension_count, device_extensions.data()));
|
|
|
|
if (!validate_extensions(required_device_extensions, device_extensions))
|
|
{
|
|
throw std::runtime_error("Required device extensions are missing, will try without.");
|
|
}
|
|
|
|
float queue_priority = 1.0f;
|
|
|
|
VkDeviceQueueCreateInfo queue_info{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
|
|
queue_info.queueFamilyIndex = context.graphics_queue_index;
|
|
queue_info.queueCount = 1;
|
|
queue_info.pQueuePriorities = &queue_priority;
|
|
|
|
VkDeviceCreateInfo device_info{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
|
|
device_info.queueCreateInfoCount = 1;
|
|
device_info.pQueueCreateInfos = &queue_info;
|
|
|
|
device_info.enabledExtensionCount = vkb::to_u32(required_device_extensions.size());
|
|
device_info.ppEnabledExtensionNames = required_device_extensions.data();
|
|
|
|
VK_CHECK(vkCreateDevice(context.gpu, &device_info, nullptr, &context.device));
|
|
volkLoadDevice(context.device);
|
|
|
|
vkGetDeviceQueue(context.device, context.graphics_queue_index, 0, &context.queue);
|
|
|
|
LOGI("Press the F1-F3 keys to control this sample. Here's what they do:")
|
|
LOGI("F1 sets Windowed view.")
|
|
LOGI("F2 sets Borderless Fullscreen.")
|
|
LOGI("F3 sets Exclusive Fullscreen.")
|
|
}
|
|
|
|
void FullScreenExclusive::init_per_frame(PerFrame &per_frame) const
|
|
{
|
|
VkFenceCreateInfo info{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
|
info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
|
VK_CHECK(vkCreateFence(context.device, &info, nullptr, &per_frame.queue_submit_fence));
|
|
|
|
VkCommandPoolCreateInfo cmd_pool_info{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
|
cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
|
|
cmd_pool_info.queueFamilyIndex = context.graphics_queue_index;
|
|
VK_CHECK(vkCreateCommandPool(context.device, &cmd_pool_info, nullptr, &per_frame.primary_command_pool));
|
|
|
|
VkCommandBufferAllocateInfo cmd_buf_info{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
|
cmd_buf_info.commandPool = per_frame.primary_command_pool;
|
|
cmd_buf_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
cmd_buf_info.commandBufferCount = 1;
|
|
VK_CHECK(vkAllocateCommandBuffers(context.device, &cmd_buf_info, &per_frame.primary_command_buffer));
|
|
|
|
per_frame.device = context.device;
|
|
per_frame.queue_index = context.graphics_queue_index;
|
|
}
|
|
|
|
void FullScreenExclusive::teardown_per_frame(PerFrame &per_frame) const
|
|
{
|
|
if (per_frame.queue_submit_fence != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyFence(context.device, per_frame.queue_submit_fence, nullptr);
|
|
|
|
per_frame.queue_submit_fence = VK_NULL_HANDLE;
|
|
}
|
|
|
|
if (per_frame.primary_command_buffer != VK_NULL_HANDLE)
|
|
{
|
|
vkFreeCommandBuffers(context.device, per_frame.primary_command_pool, 1, &per_frame.primary_command_buffer);
|
|
|
|
per_frame.primary_command_buffer = VK_NULL_HANDLE;
|
|
}
|
|
|
|
if (per_frame.primary_command_pool != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyCommandPool(context.device, per_frame.primary_command_pool, nullptr);
|
|
|
|
per_frame.primary_command_pool = VK_NULL_HANDLE;
|
|
}
|
|
|
|
if (per_frame.swapchain_acquire_semaphore != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroySemaphore(context.device, per_frame.swapchain_acquire_semaphore, nullptr);
|
|
|
|
per_frame.swapchain_acquire_semaphore = VK_NULL_HANDLE;
|
|
}
|
|
|
|
if (per_frame.swapchain_release_semaphore != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroySemaphore(context.device, per_frame.swapchain_release_semaphore, nullptr);
|
|
|
|
per_frame.swapchain_release_semaphore = VK_NULL_HANDLE;
|
|
}
|
|
|
|
per_frame.device = VK_NULL_HANDLE;
|
|
per_frame.queue_index = -1;
|
|
}
|
|
|
|
void FullScreenExclusive::init_swapchain()
|
|
{
|
|
VkSurfaceCapabilitiesKHR surface_properties;
|
|
VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(context.gpu, context.surface, &surface_properties));
|
|
|
|
VkSurfaceFormatKHR format = vkb::select_surface_format(context.gpu, context.surface);
|
|
|
|
VkExtent2D swapchain_size;
|
|
if (is_full_screen_exclusive)
|
|
{
|
|
swapchain_size = surface_properties.maxImageExtent;
|
|
}
|
|
else
|
|
{
|
|
if (surface_properties.currentExtent.width == 0xFFFFFFFF)
|
|
{
|
|
swapchain_size = context.swapchain_dimensions;
|
|
}
|
|
else
|
|
{
|
|
swapchain_size = surface_properties.currentExtent;
|
|
}
|
|
}
|
|
|
|
VkPresentModeKHR swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR;
|
|
|
|
uint32_t desired_swapchain_images = surface_properties.minImageCount + 1;
|
|
if ((surface_properties.maxImageCount > 0) && (desired_swapchain_images > surface_properties.maxImageCount))
|
|
{
|
|
desired_swapchain_images = surface_properties.maxImageCount;
|
|
}
|
|
|
|
VkSurfaceTransformFlagBitsKHR pre_transform;
|
|
if (surface_properties.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
|
|
{
|
|
pre_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
|
}
|
|
else
|
|
{
|
|
pre_transform = surface_properties.currentTransform;
|
|
}
|
|
|
|
VkSwapchainKHR old_swapchain = context.swapchain;
|
|
|
|
VkCompositeAlphaFlagBitsKHR composite = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
|
if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)
|
|
{
|
|
composite = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
|
}
|
|
else if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
|
|
{
|
|
composite = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
|
|
}
|
|
else if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)
|
|
{
|
|
composite = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
|
|
}
|
|
else if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR)
|
|
{
|
|
composite = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
|
|
}
|
|
|
|
VkSwapchainCreateInfoKHR info{}; // initialize the swapchain create info without adding pNext info
|
|
info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
|
|
|
info.pNext = &surface_full_screen_exclusive_info_EXT; // syncing the full screen exclusive info.
|
|
|
|
info.surface = context.surface;
|
|
info.minImageCount = desired_swapchain_images;
|
|
info.imageFormat = format.format;
|
|
info.imageColorSpace = format.colorSpace;
|
|
info.imageExtent = swapchain_size;
|
|
info.imageArrayLayers = 1;
|
|
info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
|
info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
info.preTransform = pre_transform;
|
|
info.compositeAlpha = composite;
|
|
info.presentMode = swapchain_present_mode;
|
|
info.clipped = true;
|
|
info.oldSwapchain = old_swapchain;
|
|
|
|
VkResult result = vkCreateSwapchainKHR(context.device, &info, nullptr, &context.swapchain);
|
|
if (result == VK_ERROR_INITIALIZATION_FAILED)
|
|
{
|
|
LOGE("this mode doesn't work with the current setup.")
|
|
abort();
|
|
}
|
|
VK_CHECK(result);
|
|
|
|
if (old_swapchain != VK_NULL_HANDLE)
|
|
{
|
|
for (VkImageView image_view : context.swapchain_image_views)
|
|
{
|
|
vkDestroyImageView(context.device, image_view, nullptr);
|
|
}
|
|
|
|
uint32_t image_count;
|
|
VK_CHECK(vkGetSwapchainImagesKHR(context.device, old_swapchain, &image_count, nullptr));
|
|
|
|
for (size_t i = 0; i < image_count; i++)
|
|
{
|
|
teardown_per_frame(context.per_frame[i]);
|
|
}
|
|
|
|
context.swapchain_image_views.clear();
|
|
|
|
vkDestroySwapchainKHR(context.device, old_swapchain, nullptr);
|
|
}
|
|
|
|
context.swapchain_dimensions = swapchain_size;
|
|
context.swapchain_format = format.format;
|
|
|
|
uint32_t image_count;
|
|
VK_CHECK(vkGetSwapchainImagesKHR(context.device, context.swapchain, &image_count, nullptr));
|
|
|
|
std::vector<VkImage> swapchain_images(image_count);
|
|
VK_CHECK(vkGetSwapchainImagesKHR(context.device, context.swapchain, &image_count, swapchain_images.data()));
|
|
|
|
context.per_frame.clear();
|
|
context.per_frame.resize(image_count);
|
|
|
|
for (size_t i = 0; i < image_count; i++)
|
|
{
|
|
init_per_frame(context.per_frame[i]);
|
|
}
|
|
|
|
for (size_t i = 0; i < image_count; i++)
|
|
{
|
|
VkImageViewCreateInfo view_info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
|
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
view_info.format = context.swapchain_format;
|
|
view_info.image = swapchain_images[i];
|
|
view_info.subresourceRange.levelCount = 1;
|
|
view_info.subresourceRange.layerCount = 1;
|
|
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
view_info.components.r = VK_COMPONENT_SWIZZLE_R;
|
|
view_info.components.g = VK_COMPONENT_SWIZZLE_G;
|
|
view_info.components.b = VK_COMPONENT_SWIZZLE_B;
|
|
view_info.components.a = VK_COMPONENT_SWIZZLE_A;
|
|
|
|
VkImageView image_view;
|
|
VK_CHECK(vkCreateImageView(context.device, &view_info, nullptr, &image_view));
|
|
|
|
context.swapchain_image_views.push_back(image_view);
|
|
}
|
|
}
|
|
|
|
void FullScreenExclusive::init_render_pass()
|
|
{
|
|
VkAttachmentDescription attachment = {0};
|
|
attachment.format = context.swapchain_format;
|
|
attachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
|
|
VkAttachmentReference color_ref = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
|
|
|
VkSubpassDescription subpass = {0};
|
|
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
subpass.colorAttachmentCount = 1;
|
|
subpass.pColorAttachments = &color_ref;
|
|
|
|
VkSubpassDependency dependency = {0};
|
|
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
|
|
dependency.dstSubpass = 0;
|
|
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
|
|
dependency.srcAccessMask = 0;
|
|
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
|
|
VkRenderPassCreateInfo rp_info = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
|
rp_info.attachmentCount = 1;
|
|
rp_info.pAttachments = &attachment;
|
|
rp_info.subpassCount = 1;
|
|
rp_info.pSubpasses = &subpass;
|
|
rp_info.dependencyCount = 1;
|
|
rp_info.pDependencies = &dependency;
|
|
|
|
VK_CHECK(vkCreateRenderPass(context.device, &rp_info, nullptr, &context.render_pass));
|
|
}
|
|
|
|
VkShaderModule FullScreenExclusive::load_shader_module(const char *path) const
|
|
{
|
|
std::vector<uint32_t> spirv = vkb::fs::read_shader_binary_u32(path);
|
|
|
|
VkShaderModuleCreateInfo module_info{
|
|
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
|
.codeSize = spirv.size() * sizeof(uint32_t),
|
|
.pCode = spirv.data()};
|
|
|
|
VkShaderModule shader_module;
|
|
VK_CHECK(vkCreateShaderModule(context.device, &module_info, nullptr, &shader_module));
|
|
|
|
return shader_module;
|
|
}
|
|
|
|
void FullScreenExclusive::init_pipeline()
|
|
{
|
|
VkPipelineLayoutCreateInfo layout_info{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
|
|
VK_CHECK(vkCreatePipelineLayout(context.device, &layout_info, nullptr, &context.pipeline_layout));
|
|
|
|
VkPipelineVertexInputStateCreateInfo vertex_input{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
|
|
|
|
VkPipelineInputAssemblyStateCreateInfo input_assembly{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
|
input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
|
|
|
VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
|
|
raster.cullMode = VK_CULL_MODE_BACK_BIT;
|
|
raster.frontFace = VK_FRONT_FACE_CLOCKWISE;
|
|
raster.lineWidth = 1.0f;
|
|
|
|
VkPipelineColorBlendAttachmentState blend_attachment{};
|
|
blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
|
|
|
VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
|
|
blend.attachmentCount = 1;
|
|
blend.pAttachments = &blend_attachment;
|
|
|
|
VkPipelineViewportStateCreateInfo viewport{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
|
viewport.viewportCount = 1;
|
|
viewport.scissorCount = 1;
|
|
|
|
VkPipelineDepthStencilStateCreateInfo depth_stencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
|
|
|
VkPipelineMultisampleStateCreateInfo multisample{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
|
multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
std::array<VkDynamicState, 2> dynamics{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
|
|
|
VkPipelineDynamicStateCreateInfo dynamic{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO};
|
|
dynamic.pDynamicStates = dynamics.data();
|
|
dynamic.dynamicStateCount = vkb::to_u32(dynamics.size());
|
|
|
|
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{};
|
|
|
|
shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
|
|
shader_stages[0].module = load_shader_module("triangle.vert.spv");
|
|
shader_stages[0].pName = "main";
|
|
|
|
shader_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
shader_stages[1].module = load_shader_module("triangle.frag.spv");
|
|
shader_stages[1].pName = "main";
|
|
|
|
VkGraphicsPipelineCreateInfo pipe{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
|
|
pipe.stageCount = vkb::to_u32(shader_stages.size());
|
|
pipe.pStages = shader_stages.data();
|
|
pipe.pVertexInputState = &vertex_input;
|
|
pipe.pInputAssemblyState = &input_assembly;
|
|
pipe.pRasterizationState = &raster;
|
|
pipe.pColorBlendState = &blend;
|
|
pipe.pMultisampleState = &multisample;
|
|
pipe.pViewportState = &viewport;
|
|
pipe.pDepthStencilState = &depth_stencil;
|
|
pipe.pDynamicState = &dynamic;
|
|
|
|
pipe.renderPass = context.render_pass;
|
|
pipe.layout = context.pipeline_layout;
|
|
|
|
VK_CHECK(vkCreateGraphicsPipelines(context.device, VK_NULL_HANDLE, 1, &pipe, nullptr, &context.pipeline));
|
|
|
|
vkDestroyShaderModule(context.device, shader_stages[0].module, nullptr);
|
|
vkDestroyShaderModule(context.device, shader_stages[1].module, nullptr);
|
|
}
|
|
|
|
VkResult FullScreenExclusive::acquire_next_image(uint32_t *image)
|
|
{
|
|
VkSemaphore acquire_semaphore;
|
|
if (context.recycled_semaphores.empty())
|
|
{
|
|
VkSemaphoreCreateInfo info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
|
VK_CHECK(vkCreateSemaphore(context.device, &info, nullptr, &acquire_semaphore));
|
|
}
|
|
else
|
|
{
|
|
acquire_semaphore = context.recycled_semaphores.back();
|
|
context.recycled_semaphores.pop_back();
|
|
}
|
|
|
|
VkResult result = vkAcquireNextImageKHR(context.device, context.swapchain, UINT64_MAX, acquire_semaphore, VK_NULL_HANDLE, image);
|
|
|
|
if (result != VK_SUCCESS)
|
|
{
|
|
context.recycled_semaphores.push_back(acquire_semaphore);
|
|
return result;
|
|
}
|
|
|
|
if (context.per_frame[*image].queue_submit_fence != VK_NULL_HANDLE)
|
|
{
|
|
vkWaitForFences(context.device, 1, &context.per_frame[*image].queue_submit_fence, true, UINT64_MAX);
|
|
vkResetFences(context.device, 1, &context.per_frame[*image].queue_submit_fence);
|
|
}
|
|
|
|
if (context.per_frame[*image].primary_command_pool != VK_NULL_HANDLE)
|
|
{
|
|
vkResetCommandPool(context.device, context.per_frame[*image].primary_command_pool, 0);
|
|
}
|
|
|
|
VkSemaphore old_semaphore = context.per_frame[*image].swapchain_acquire_semaphore;
|
|
|
|
if (old_semaphore != VK_NULL_HANDLE)
|
|
{
|
|
context.recycled_semaphores.push_back(old_semaphore);
|
|
}
|
|
|
|
context.per_frame[*image].swapchain_acquire_semaphore = acquire_semaphore;
|
|
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
void FullScreenExclusive::render_triangle(uint32_t swapchain_index)
|
|
{
|
|
VkFramebuffer framebuffer = context.swapchain_framebuffers[swapchain_index];
|
|
|
|
VkCommandBuffer cmd = context.per_frame[swapchain_index].primary_command_buffer;
|
|
|
|
VkCommandBufferBeginInfo begin_info{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
|
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
vkBeginCommandBuffer(cmd, &begin_info);
|
|
|
|
VkClearValue clear_value;
|
|
clear_value.color = {{0.01f, 0.01f, 0.033f, 1.0f}};
|
|
|
|
VkRenderPassBeginInfo rp_begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
|
rp_begin.renderPass = context.render_pass;
|
|
rp_begin.framebuffer = framebuffer;
|
|
rp_begin.renderArea.extent = context.swapchain_dimensions;
|
|
rp_begin.clearValueCount = 1;
|
|
rp_begin.pClearValues = &clear_value;
|
|
|
|
vkCmdBeginRenderPass(cmd, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, context.pipeline);
|
|
|
|
VkViewport vp{};
|
|
vp.width = static_cast<float>(context.swapchain_dimensions.width);
|
|
vp.height = static_cast<float>(context.swapchain_dimensions.height);
|
|
vp.minDepth = 0.0f;
|
|
vp.maxDepth = 1.0f;
|
|
vkCmdSetViewport(cmd, 0, 1, &vp);
|
|
|
|
VkRect2D scissor{};
|
|
scissor.extent = context.swapchain_dimensions;
|
|
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
|
|
|
vkCmdDraw(cmd, 3, 1, 0, 0);
|
|
|
|
vkCmdEndRenderPass(cmd);
|
|
|
|
VK_CHECK(vkEndCommandBuffer(cmd));
|
|
|
|
if (context.per_frame[swapchain_index].swapchain_release_semaphore == VK_NULL_HANDLE)
|
|
{
|
|
VkSemaphoreCreateInfo semaphore_info{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
|
VK_CHECK(vkCreateSemaphore(context.device, &semaphore_info, nullptr,
|
|
&context.per_frame[swapchain_index].swapchain_release_semaphore));
|
|
}
|
|
|
|
VkPipelineStageFlags wait_stage{VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
|
|
|
|
VkSubmitInfo info{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
|
info.commandBufferCount = 1;
|
|
info.pCommandBuffers = &cmd;
|
|
info.waitSemaphoreCount = 1;
|
|
info.pWaitSemaphores = &context.per_frame[swapchain_index].swapchain_acquire_semaphore;
|
|
info.pWaitDstStageMask = &wait_stage;
|
|
info.signalSemaphoreCount = 1;
|
|
info.pSignalSemaphores = &context.per_frame[swapchain_index].swapchain_release_semaphore;
|
|
|
|
VK_CHECK(vkQueueSubmit(context.queue, 1, &info, context.per_frame[swapchain_index].queue_submit_fence));
|
|
}
|
|
|
|
VkResult FullScreenExclusive::present_image(uint32_t index)
|
|
{
|
|
VkPresentInfoKHR present{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
|
present.swapchainCount = 1;
|
|
present.pSwapchains = &context.swapchain;
|
|
present.pImageIndices = &index;
|
|
present.waitSemaphoreCount = 1;
|
|
present.pWaitSemaphores = &context.per_frame[index].swapchain_release_semaphore;
|
|
return vkQueuePresentKHR(context.queue, &present);
|
|
}
|
|
|
|
void FullScreenExclusive::init_framebuffers()
|
|
{
|
|
VkDevice device = context.device;
|
|
|
|
for (auto &image_view : context.swapchain_image_views)
|
|
{
|
|
VkFramebufferCreateInfo fb_info{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
|
fb_info.renderPass = context.render_pass;
|
|
fb_info.attachmentCount = 1;
|
|
fb_info.pAttachments = &image_view;
|
|
fb_info.width = context.swapchain_dimensions.width;
|
|
fb_info.height = context.swapchain_dimensions.height;
|
|
fb_info.layers = 1;
|
|
|
|
VkFramebuffer framebuffer;
|
|
VK_CHECK(vkCreateFramebuffer(device, &fb_info, nullptr, &framebuffer));
|
|
|
|
context.swapchain_framebuffers.push_back(framebuffer);
|
|
}
|
|
}
|
|
|
|
void FullScreenExclusive::teardown_framebuffers()
|
|
{
|
|
vkQueueWaitIdle(context.queue);
|
|
|
|
for (auto &framebuffer : context.swapchain_framebuffers)
|
|
{
|
|
vkDestroyFramebuffer(context.device, framebuffer, nullptr);
|
|
}
|
|
|
|
context.swapchain_framebuffers.clear();
|
|
}
|
|
|
|
void FullScreenExclusive::teardown()
|
|
{
|
|
vkDeviceWaitIdle(context.device);
|
|
|
|
teardown_framebuffers();
|
|
|
|
for (auto &per_frame : context.per_frame)
|
|
{
|
|
teardown_per_frame(per_frame);
|
|
}
|
|
|
|
context.per_frame.clear();
|
|
|
|
for (auto semaphore : context.recycled_semaphores)
|
|
{
|
|
vkDestroySemaphore(context.device, semaphore, nullptr);
|
|
}
|
|
|
|
if (context.pipeline != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyPipeline(context.device, context.pipeline, nullptr);
|
|
}
|
|
|
|
if (context.pipeline_layout != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyPipelineLayout(context.device, context.pipeline_layout, nullptr);
|
|
}
|
|
|
|
if (context.render_pass != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyRenderPass(context.device, context.render_pass, nullptr);
|
|
}
|
|
|
|
for (VkImageView image_view : context.swapchain_image_views)
|
|
{
|
|
vkDestroyImageView(context.device, image_view, nullptr);
|
|
}
|
|
|
|
if (context.swapchain != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroySwapchainKHR(context.device, context.swapchain, nullptr);
|
|
context.swapchain = VK_NULL_HANDLE;
|
|
}
|
|
|
|
if (context.surface != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroySurfaceKHR(context.instance, context.surface, nullptr);
|
|
context.surface = VK_NULL_HANDLE;
|
|
}
|
|
|
|
if (context.device != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyDevice(context.device, nullptr);
|
|
context.device = VK_NULL_HANDLE;
|
|
}
|
|
|
|
if (context.debug_callback != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyDebugUtilsMessengerEXT(context.instance, context.debug_callback, nullptr);
|
|
context.debug_callback = VK_NULL_HANDLE;
|
|
}
|
|
|
|
vk_instance.reset();
|
|
}
|
|
|
|
FullScreenExclusive::~FullScreenExclusive()
|
|
{
|
|
teardown();
|
|
}
|
|
|
|
void FullScreenExclusive::initialize_windows()
|
|
{
|
|
HWND_application_window = GetActiveWindow();
|
|
// The following variable has to be attached to the pNext of surface_full_screen_exclusive_info_EXT:
|
|
surface_full_screen_exclusive_Win32_info_EXT.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT;
|
|
surface_full_screen_exclusive_Win32_info_EXT.pNext = nullptr;
|
|
surface_full_screen_exclusive_Win32_info_EXT.hmonitor = MonitorFromWindow(HWND_application_window, MONITOR_DEFAULTTONEAREST);
|
|
|
|
surface_full_screen_exclusive_info_EXT.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT;
|
|
surface_full_screen_exclusive_info_EXT.pNext = &surface_full_screen_exclusive_Win32_info_EXT;
|
|
surface_full_screen_exclusive_info_EXT.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_DEFAULT_EXT; // Set the fullScreenExclusive stage to default when initializing
|
|
|
|
// Windows placement should only be registered ONCE:
|
|
GetWindowPlacement(HWND_application_window, &wpc);
|
|
}
|
|
|
|
bool FullScreenExclusive::prepare(const vkb::ApplicationOptions &options)
|
|
{
|
|
if (!vkb::Application::prepare(options))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
init_instance({VK_KHR_SURFACE_EXTENSION_NAME}, {});
|
|
|
|
vk_instance = std::make_unique<vkb::core::InstanceC>(context.instance);
|
|
|
|
context.surface = window->create_surface(*vk_instance);
|
|
if (!context.surface)
|
|
throw std::runtime_error("Failed to create window surface.");
|
|
auto &extent = window->get_extent();
|
|
context.swapchain_dimensions.width = extent.width;
|
|
context.swapchain_dimensions.height = extent.height;
|
|
|
|
// As application is running on a Windows platform, then the following TWO device extension are also needed:
|
|
init_device({VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME});
|
|
|
|
init_swapchain();
|
|
|
|
init_render_pass();
|
|
init_pipeline();
|
|
init_framebuffers();
|
|
|
|
return true;
|
|
}
|
|
|
|
void FullScreenExclusive::update(float delta_time)
|
|
{
|
|
uint32_t index;
|
|
|
|
auto res = acquire_next_image(&index);
|
|
|
|
if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR)
|
|
{
|
|
resize(context.swapchain_dimensions.width, context.swapchain_dimensions.height);
|
|
res = acquire_next_image(&index);
|
|
}
|
|
|
|
if (res != VK_SUCCESS)
|
|
{
|
|
vkQueueWaitIdle(context.queue);
|
|
return;
|
|
}
|
|
|
|
render_triangle(index);
|
|
res = present_image(index);
|
|
|
|
if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR)
|
|
{
|
|
resize(context.swapchain_dimensions.width, context.swapchain_dimensions.height);
|
|
}
|
|
else if (res != VK_SUCCESS)
|
|
{
|
|
LOGE("Failed to present swapchain image.")
|
|
}
|
|
}
|
|
|
|
bool FullScreenExclusive::resize(uint32_t width, uint32_t height)
|
|
{
|
|
if (context.device == VK_NULL_HANDLE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
VkSurfaceCapabilitiesKHR surface_properties;
|
|
VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(context.gpu, context.surface, &surface_properties));
|
|
|
|
if (surface_properties.currentExtent.width == context.swapchain_dimensions.width &&
|
|
surface_properties.currentExtent.height == context.swapchain_dimensions.height)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
vkDeviceWaitIdle(context.device);
|
|
teardown_framebuffers();
|
|
|
|
init_swapchain();
|
|
init_framebuffers();
|
|
return true;
|
|
}
|
|
|
|
VkExtent2D FullScreenExclusive::get_current_max_image_extent() const
|
|
{
|
|
VkExtent2D returnMe{};
|
|
|
|
if (context.device != VK_NULL_HANDLE)
|
|
{
|
|
VkSurfaceCapabilitiesKHR surface_properties{};
|
|
VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(context.gpu, context.surface, &surface_properties));
|
|
|
|
returnMe = surface_properties.maxImageExtent;
|
|
}
|
|
|
|
return returnMe;
|
|
}
|
|
|
|
void FullScreenExclusive::input_event(const vkb::InputEvent &input_event)
|
|
{
|
|
if (input_event.get_source() == vkb::EventSource::Keyboard)
|
|
{
|
|
const auto &key_button = reinterpret_cast<const vkb::KeyInputEvent &>(input_event);
|
|
|
|
if (key_button.get_action() == vkb::KeyAction::Down)
|
|
{
|
|
bool needs_recreate = false;
|
|
|
|
switch (key_button.get_code())
|
|
{
|
|
case vkb::KeyCode::F1: // FullscreenExclusiveEXT = Disallowed
|
|
if (full_screen_status != SwapchainMode::Windowed)
|
|
{
|
|
full_screen_status = SwapchainMode::Windowed;
|
|
application_window_status = ApplicationWindowMode::Windowed;
|
|
surface_full_screen_exclusive_info_EXT.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT;
|
|
needs_recreate = true;
|
|
|
|
// If it is switched from the exclusive full screen mode, then release full screen first
|
|
if (is_full_screen_exclusive)
|
|
{
|
|
VkResult result = vkReleaseFullScreenExclusiveModeEXT(context.device, context.swapchain);
|
|
if (result != VK_SUCCESS)
|
|
{
|
|
LOGI("vkReleaseFullScreenExclusiveModeEXT: " + vkb::to_string(result))
|
|
}
|
|
is_full_screen_exclusive = false;
|
|
}
|
|
|
|
LOGI("Windowed Mode Detected!")
|
|
}
|
|
break;
|
|
case vkb::KeyCode::F2: // FullscreenExclusiveEXT = Allowed
|
|
if (full_screen_status != SwapchainMode::BorderlessFullscreen)
|
|
{
|
|
full_screen_status = SwapchainMode::BorderlessFullscreen;
|
|
application_window_status = ApplicationWindowMode::Fullscreen;
|
|
surface_full_screen_exclusive_info_EXT.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT;
|
|
needs_recreate = true;
|
|
|
|
// If it is switched from the exclusive full screen mode, then release full screen first
|
|
if (is_full_screen_exclusive)
|
|
{
|
|
VkResult result = vkReleaseFullScreenExclusiveModeEXT(context.device, context.swapchain);
|
|
if (result != VK_SUCCESS)
|
|
{
|
|
LOGI("vkReleaseFullScreenExclusiveModeEXT: " + vkb::to_string(result))
|
|
}
|
|
is_full_screen_exclusive = false;
|
|
}
|
|
|
|
LOGI("Borderless Fullscreen Mode Detected!")
|
|
}
|
|
break;
|
|
case vkb::KeyCode::F3: // FullscreenExclusiveEXT = Application Controlled
|
|
if (full_screen_status != SwapchainMode::ExclusiveFullscreen)
|
|
{
|
|
full_screen_status = SwapchainMode::ExclusiveFullscreen;
|
|
application_window_status = ApplicationWindowMode::Fullscreen;
|
|
surface_full_screen_exclusive_info_EXT.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT;
|
|
needs_recreate = true;
|
|
is_full_screen_exclusive = true;
|
|
LOGI("Exclusive Fullscreen Mode Detected!")
|
|
}
|
|
break;
|
|
default: // FullscreenExclusiveEXT = Default
|
|
needs_recreate = false;
|
|
break;
|
|
}
|
|
|
|
// now if to recreate the swapchain and everything related:
|
|
if (needs_recreate)
|
|
{
|
|
FullScreenExclusive::recreate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FullScreenExclusive::update_application_window()
|
|
{
|
|
if (application_window_status == ApplicationWindowMode::Fullscreen && is_windowed) // check if it is already in fullscreen, if is, then do nothing
|
|
{
|
|
is_windowed = false;
|
|
|
|
if (HWND_style == 0)
|
|
HWND_style = GetWindowLong(HWND_application_window, GWL_STYLE);
|
|
if (HWND_extended_style == 0)
|
|
HWND_extended_style = GetWindowLong(HWND_application_window, GWL_EXSTYLE);
|
|
|
|
long HWND_newStyle = HWND_style & (~WS_BORDER) & (~WS_DLGFRAME) & (~WS_THICKFRAME);
|
|
long HWND_extended_newStyle = HWND_extended_style & (~WS_EX_WINDOWEDGE);
|
|
|
|
SetWindowLong(HWND_application_window, GWL_STYLE, HWND_newStyle | static_cast<long>(WS_POPUP));
|
|
SetWindowLong(HWND_application_window, GWL_EXSTYLE, HWND_extended_newStyle | WS_EX_TOPMOST);
|
|
|
|
ShowWindow(HWND_application_window, SW_SHOWMAXIMIZED);
|
|
}
|
|
else if (application_window_status == ApplicationWindowMode::Windowed && !is_windowed) // check if it is already "windowed", if is, then do nothing
|
|
{
|
|
is_windowed = true;
|
|
|
|
SetWindowLong(HWND_application_window, GWL_STYLE, HWND_style);
|
|
SetWindowLong(HWND_application_window, GWL_EXSTYLE, HWND_extended_style);
|
|
ShowWindow(HWND_application_window, SW_SHOWNORMAL);
|
|
SetWindowPlacement(HWND_application_window, &wpc);
|
|
}
|
|
}
|
|
|
|
void FullScreenExclusive::recreate()
|
|
{
|
|
// Check if there IS a device, if not don't do anything
|
|
if (context.device != VK_NULL_HANDLE)
|
|
{
|
|
// Step: 0) Idle the device, destroy/teardown the current swapchain and frame buffers.
|
|
vkDeviceWaitIdle(context.device); // pause the renderer
|
|
teardown_framebuffers(); // basically destroy everything swapchain related
|
|
|
|
// Step: 1) recreate the swapchain with its properly selected FullscreenExclusive enum value
|
|
init_swapchain();
|
|
|
|
// Step: 2) recreate the frame buffers using the newly created swapchain
|
|
init_framebuffers();
|
|
|
|
// Step: 3-1) update the window mode, corresponding to the FullscreenExclusive enum value
|
|
update_application_window();
|
|
|
|
// Step: 3-2) remember: ALWAYS change the application window mode BEFORE acquire the full screen exclusive mode!
|
|
if (is_full_screen_exclusive)
|
|
{
|
|
VkResult result = vkAcquireFullScreenExclusiveModeEXT(context.device, context.swapchain);
|
|
if (result != VK_SUCCESS)
|
|
LOGI("vkAcquireFullScreenExclusiveModeEXT: " + vkb::to_string(result)) // it would be necessary to learn the result on an unsuccessful attempt
|
|
}
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<vkb::Application> create_full_screen_exclusive()
|
|
{
|
|
return std::make_unique<FullScreenExclusive>();
|
|
}
|