diff --git a/cmake/modules/FindVulkan.cmake b/cmake/modules/FindVulkan.cmake new file mode 100644 index 0000000000..a89abdbdfc --- /dev/null +++ b/cmake/modules/FindVulkan.cmake @@ -0,0 +1,16 @@ +if (WIN32) + find_path(VULKAN_INCLUDE_DIR NAMES vulkan/vulkan.h HINTS "$ENV{VULKAN_SDK}/Include" "$ENV{VK_SDK_PATH}/Include") + if (CMAKE_CL_64) + find_library(VULKAN_LIBRARY NAMES vulkan-1 HINTS "$ENV{VULKAN_SDK}/Lib" "$ENV{VK_SDK_PATH}/Bin") + else() + find_library(VULKAN_LIBRARY NAMES vulkan-1 HINTS "$ENV{VULKAN_SDK}/Lib32" "$ENV{VK_SDK_PATH}/Bin32") + endif() +else() + find_path(VULKAN_INCLUDE_DIR NAMES vulkan/vulkan.h HINTS "$ENV{VULKAN_SDK}/include") + find_library(VULKAN_LIBRARY NAMES vulkan HINTS + "$ENV{VULKAN_SDK}/lib") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Vulkan DEFAULT_MSG VULKAN_LIBRARY VULKAN_INCLUDE_DIR) +mark_as_advanced(VULKAN_INCLUDE_DIR VULKAN_LIBRARY) diff --git a/libraries/shared/src/shared/GlobalAppProperties.cpp b/libraries/shared/src/shared/GlobalAppProperties.cpp index 1fd6c191b2..02f376fcc3 100644 --- a/libraries/shared/src/shared/GlobalAppProperties.cpp +++ b/libraries/shared/src/shared/GlobalAppProperties.cpp @@ -25,4 +25,8 @@ namespace hifi { namespace properties { const char* PRIMARY_CONTEXT = "com.highfidelity.gl.primaryContext"; } + namespace vk { + const char* CONTEXT = "com.highfidelity.vk.context"; + } + } } diff --git a/libraries/shared/src/shared/GlobalAppProperties.h b/libraries/shared/src/shared/GlobalAppProperties.h index 6809d5530a..55fd802f4b 100644 --- a/libraries/shared/src/shared/GlobalAppProperties.h +++ b/libraries/shared/src/shared/GlobalAppProperties.h @@ -28,6 +28,10 @@ namespace hifi { namespace properties { extern const char* PRIMARY_CONTEXT; } + namespace vk { + extern const char* CONTEXT; + } + } } diff --git a/libraries/vk/CMakeLists.txt b/libraries/vk/CMakeLists.txt new file mode 100644 index 0000000000..9bd86f8538 --- /dev/null +++ b/libraries/vk/CMakeLists.txt @@ -0,0 +1,6 @@ +set(TARGET_NAME vk) +setup_hifi_library(Gui) +GroupSources(src) +link_hifi_libraries(shared gl) + +target_vulkan() diff --git a/libraries/vk/src/vk/Allocation.cpp b/libraries/vk/src/vk/Allocation.cpp new file mode 100644 index 0000000000..e29086aee6 --- /dev/null +++ b/libraries/vk/src/vk/Allocation.cpp @@ -0,0 +1,94 @@ +#include "Allocation.h" + +#include + +using namespace vks; + +#if VULKAN_USE_VMA + + +VmaAllocator& Allocation::getAllocator() { + static VmaAllocator allocator; + return allocator; +} + +void Allocation::initAllocator(const vk::PhysicalDevice& physicalDevice, const vk::Device& device) { + static std::once_flag once; + std::call_once(once, [&] { + VmaAllocatorCreateInfo allocatorInfo = {}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + + VmaAllocator& allocator = getAllocator(); + vmaCreateAllocator(&allocatorInfo, &allocator); + }); +} + + + +void Allocation::flush(vk::DeviceSize size, vk::DeviceSize offset) { + vmaFlushAllocation(getAllocator(), allocation, offset, size); +} + +void Allocation::invalidate(vk::DeviceSize size, vk::DeviceSize offset) { + vmaInvalidateAllocation(getAllocator(), allocation, offset, size); +} + +void* Allocation::rawmap(size_t offset, vk::DeviceSize size) { + if (offset != 0 || size != VK_WHOLE_SIZE) { + throw std::runtime_error("Unsupported"); + } + + if (!mapped) { + vmaMapMemory(getAllocator(), allocation, &mapped); + } + + return mapped; +} + +void Allocation::unmap() { + if (mapped) { + vmaUnmapMemory(getAllocator(), allocation); + mapped = nullptr; + } +} + +void Allocation::destroy() { + unmap(); +} + +#else + +void Alloction::initAllocator(const vk::PhysicalDevice&, const vk::Device&) { +} + +void Alloction::flush(vk::DeviceSize size, vk::DeviceSize offset = 0) { + return device.flushMappedMemoryRanges(vk::MappedMemoryRange{ memory, offset, size }); +} + +void Alloction::invalidate(vk::DeviceSize size, vk::DeviceSize offset = 0) { + return device.invalidateMappedMemoryRanges(vk::MappedMemoryRange{ memory, offset, size }); +} + +void* Alloction::rawmap(size_t offset = 0, VkDeviceSize size) { + mapped = device.mapMemory(memory, offset, size, vk::MemoryMapFlags()); + return (T*)mapped; +} + +void Alloction::unmap() { + device.unmapMemory(memory); +} + +void Alloction::destroy(); +{ + if (mapped) { + unmap(); + } + + if (memory) { + device.freeMemory(memory); + memory = nullptr; + } +} + +#endif diff --git a/libraries/vk/src/vk/Allocation.h b/libraries/vk/src/vk/Allocation.h new file mode 100644 index 0000000000..7af66e70ef --- /dev/null +++ b/libraries/vk/src/vk/Allocation.h @@ -0,0 +1,60 @@ +#pragma once + +#include "Config.h" +#include "Device.h" + + +namespace vks { + + // A wrapper class for an allocation, either an Image or Buffer. Not intended to be used used directly + // but only as a base class providing common functionality for the classes below. + // + // Provides easy to use mechanisms for mapping, unmapping and copying host data to the device memory + struct Allocation { + static void initAllocator(const vk::PhysicalDevice&, const vk::Device&); + + void* rawmap(size_t offset = 0, VkDeviceSize size = VK_WHOLE_SIZE); + void unmap(); + + template + inline T* map(size_t offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) { + return (T*)rawmap(offset, size); + } + + inline void copy(size_t size, const void* data, VkDeviceSize offset = 0) const { + memcpy((uint8_t*)mapped + offset, data, size); + } + + template + inline void copy(const T& data, VkDeviceSize offset = 0) const { + copy(sizeof(T), &data, offset); + } + + template + inline void copy(const std::vector& data, VkDeviceSize offset = 0) const { + copy(sizeof(T) * data.size(), data.data(), offset); + } + + void flush(vk::DeviceSize size, vk::DeviceSize offset = 0); + void invalidate(vk::DeviceSize size, vk::DeviceSize offset = 0); + virtual void destroy(); + + + vks::Device device; + vk::DeviceSize size{ 0 }; + vk::DeviceSize alignment{ 0 }; + vk::DeviceSize allocSize{ 0 }; + +#if VULKAN_USE_VMA + static VmaAllocator& Allocation::getAllocator(); + + VmaAllocation allocation; + /** @brief Memory propertys flags to be filled by external source at buffer creation (to query at some later point) */ + vk::MemoryPropertyFlags memoryPropertyFlags; +#else + vk::DeviceMemory memory; +#endif + void* mapped{ nullptr }; + + }; +} diff --git a/libraries/vk/src/vk/Buffer.h b/libraries/vk/src/vk/Buffer.h new file mode 100644 index 0000000000..94389844a5 --- /dev/null +++ b/libraries/vk/src/vk/Buffer.h @@ -0,0 +1,51 @@ +#pragma once + +#include "Allocation.h" + +namespace vks +{ + /** + * @brief Encapsulates access to a Vulkan buffer backed up by device memory + * @note To be filled by an external source like the VulkanDevice + */ + struct Buffer : public Allocation + { + private: + using Parent = Allocation; + + public: + vk::Buffer buffer; + /** @brief Usage flags to be filled by external source at buffer creation (to query at some later point) */ + vk::BufferUsageFlags usageFlags; + vk::DescriptorBufferInfo descriptor; + + operator bool() const { + return buffer.operator bool(); + } + + /** + * Setup the default descriptor for this buffer + * + * @param size (Optional) Size of the memory range of the descriptor + * @param offset (Optional) Byte offset from beginning + * + */ + void setupDescriptor(vk::DeviceSize size, vk::DeviceSize offset = 0) { + descriptor.offset = offset; + descriptor.buffer = buffer; + descriptor.range = size; + } + + /** + * Release all Vulkan resources held by this buffer + */ + void destroy() { + if (buffer) { + device.destroy(buffer); + buffer = vk::Buffer{}; + } + Parent::destroy(); + } + + }; +} diff --git a/libraries/vk/src/vk/Config.h b/libraries/vk/src/vk/Config.h new file mode 100644 index 0000000000..4da5e4f783 --- /dev/null +++ b/libraries/vk/src/vk/Config.h @@ -0,0 +1,39 @@ +// +// Created by Bradley Austin Davis on 2016/03/19 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + + +#include +#if defined(Q_OS_WIN) +#define VK_USE_PLATFORM_WIN32_KHR +#elif defined(Q_OS_ANDROID) +#define VK_USE_PLATFORM_ANDROID_KHR +#elif defined(Q_OS_DARWIN) +#else +#define VK_USE_PLATFORM_XLIB_KHR +#endif + +#define VKCPP_ENHANCED_MODE +#include + +#define VULKAN_USE_VMA 1 + +#if VULKAN_USE_VMA +#include +#endif diff --git a/libraries/vk/src/vk/Context.cpp b/libraries/vk/src/vk/Context.cpp new file mode 100644 index 0000000000..ead16dbcdf --- /dev/null +++ b/libraries/vk/src/vk/Context.cpp @@ -0,0 +1,7 @@ +#include "Context.h" +#include + +vks::Context& vks::Context::get() { + static Context INSTANCE; + return INSTANCE; +} diff --git a/libraries/vk/src/vk/Context.h b/libraries/vk/src/vk/Context.h new file mode 100644 index 0000000000..43cac4ba0c --- /dev/null +++ b/libraries/vk/src/vk/Context.h @@ -0,0 +1,815 @@ +#pragma once + +#include "Config.h" + +#include "Debug.h" +#include "Image.h" +#include "Buffer.h" +#include "Helpers.h" +#include "Device.h" +#include +#include + +namespace vks { +using StringList = std::list; +using CStringVector = std::vector; + +using DevicePickerFunction = std::function&)>; +using DeviceExtensionsPickerFunction = std::function(const vk::PhysicalDevice&)>; +using InstanceExtensionsPickerFunction = std::function()>; +using InstanceExtensionsPickerFunctions = std::list; +using LayerVector = std::vector; +using MipData = ::std::pair; + +namespace queues { + +struct DeviceCreateInfo : public vk::DeviceCreateInfo { + std::vector deviceQueues; + std::vector> deviceQueuesPriorities; + + void addQueueFamily(uint32_t queueFamilyIndex, vk::ArrayProxy priorities) { + deviceQueues.push_back({ {}, queueFamilyIndex }); + std::vector prioritiesVector; + prioritiesVector.resize(priorities.size()); + memcpy(prioritiesVector.data(), priorities.data(), sizeof(float) * priorities.size()); + deviceQueuesPriorities.push_back(prioritiesVector); + } + void addQueueFamily(uint32_t queueFamilyIndex, size_t count = 1) { + std::vector priorities; + priorities.resize(count); + std::fill(priorities.begin(), priorities.end(), 0.0f); + addQueueFamily(queueFamilyIndex, priorities); + } + + void update() { + assert(deviceQueuesPriorities.size() == deviceQueues.size()); + auto size = deviceQueues.size(); + for (auto i = 0; i < size; ++i) { + auto& deviceQueue = deviceQueues[i]; + auto& deviceQueuePriorities = deviceQueuesPriorities[i]; + deviceQueue.queueCount = (uint32_t)deviceQueuePriorities.size(); + deviceQueue.pQueuePriorities = deviceQueuePriorities.data(); + } + + this->queueCreateInfoCount = (uint32_t)deviceQueues.size(); + this->pQueueCreateInfos = deviceQueues.data(); + } +}; +} // namespace queues +/////////////////////////////////////////////////////////////////////// +// +// Object destruction support +// +// It's often critical to avoid destroying an object that may be in use by the GPU. In order to service this need +// the context class contains structures for objects that are pending deletion. +// +// The first container is the dumpster, and it just contains a set of lambda objects that when executed, destroy +// resources (presumably... in theory the lambda can do anything you want, but the purpose is to contain GPU object +// destruction calls). +// +// When the application makes use of a function that uses a fence, it can provide that fence to the context as a marker +// for destroying all the pending objects. Anything in the dumpster is migrated to the recycler. +// +// Finally, an application can call the recycle function at regular intervals (perhaps once per frame, perhaps less often) +// in order to check the fences and execute the associated destructors for any that are signalled. +using VoidLambda = std::function; +using VoidLambdaList = std::list; +using FencedLambda = std::pair; +using FencedLambdaQueue = std::queue; + +struct Context { +private: + static CStringVector toCStrings(const StringList& values) { + CStringVector result; + result.reserve(values.size()); + for (const auto& string : values) { + result.push_back(string.c_str()); + } + return result; + } + + static CStringVector toCStrings(const vk::ArrayProxy& values) { + CStringVector result; + result.reserve(values.size()); + for (const auto& string : values) { + result.push_back(string.c_str()); + } + return result; + } + + static CStringVector filterLayers(const StringList& desiredLayers) { + static std::set validLayerNames = getAvailableLayers(); + CStringVector result; + for (const auto& string : desiredLayers) { + if (validLayerNames.count(string) != 0) { + result.push_back(string.c_str()); + } + } + return result; + } + + Context() {}; +public: + static Context& get(); + + // Create application wide Vulkan instance + static std::set getAvailableLayers() { + std::set result; + auto layers = vk::enumerateInstanceLayerProperties(); + for (auto layer : layers) { + result.insert(layer.layerName); + } + return result; + } + + static std::vector getExtensions() { return vk::enumerateInstanceExtensionProperties(); } + + static std::set getExtensionNames() { + std::set extensionNames; + for (auto& ext : getExtensions()) { + extensionNames.insert(ext.extensionName); + } + return extensionNames; + } + + static bool isExtensionPresent(const std::string& extensionName) { return getExtensionNames().count(extensionName) != 0; } + + static std::vector getDeviceExtensions(const vk::PhysicalDevice& physicalDevice) { + return physicalDevice.enumerateDeviceExtensionProperties(); + } + + static std::set getDeviceExtensionNames(const vk::PhysicalDevice& physicalDevice) { + std::set extensionNames; + for (auto& ext : getDeviceExtensions(physicalDevice)) { + extensionNames.insert(ext.extensionName); + } + return extensionNames; + } + + static bool isDeviceExtensionPresent(const vk::PhysicalDevice& physicalDevice, const std::string& extension) { + return getDeviceExtensionNames(physicalDevice).count(extension) != 0; + } + + void requireExtensions(const vk::ArrayProxy& requestedExtensions) { + requiredExtensions.insert(requestedExtensions.begin(), requestedExtensions.end()); + } + + void requireDeviceExtensions(const vk::ArrayProxy& requestedExtensions) { + requiredDeviceExtensions.insert(requestedExtensions.begin(), requestedExtensions.end()); + } + + void addInstanceExtensionPicker(const InstanceExtensionsPickerFunction& function) { + instanceExtensionsPickers.push_back(function); + } + + void setDevicePicker(const DevicePickerFunction& picker) { devicePicker = picker; } + + void setDeviceExtensionsPicker(const DeviceExtensionsPickerFunction& picker) { deviceExtensionsPicker = picker; } + + void setValidationEnabled(bool enable) { + if (instance) { + throw std::runtime_error("Cannot change validations state after instance creation"); + } + enableValidation = enable; + } + + void createInstance() { + if (instance) { + throw std::runtime_error("Instance already exists"); + } + + if (isExtensionPresent(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { + requireExtensions({ VK_EXT_DEBUG_UTILS_EXTENSION_NAME }); + enableValidation = true; + enableDebugMarkers = true; + qDebug() << "Found debug marker extension"; + } + + // Vulkan instance + vk::ApplicationInfo appInfo; + appInfo.pApplicationName = "VulkanExamples"; + appInfo.pEngineName = "VulkanExamples"; + appInfo.apiVersion = VK_API_VERSION_1_0; + + + + std::set instanceExtensions; + instanceExtensions.insert(requiredExtensions.begin(), requiredExtensions.end()); + for (const auto& picker : instanceExtensionsPickers) { + auto extensions = picker(); + instanceExtensions.insert(extensions.begin(), extensions.end()); + } + + std::vector enabledExtensions; + for (const auto& extension : instanceExtensions) { + enabledExtensions.push_back(extension.c_str()); + } + + // Enable surface extensions depending on os + vk::InstanceCreateInfo instanceCreateInfo; + instanceCreateInfo.pApplicationInfo = &appInfo; + if (enabledExtensions.size() > 0) { + instanceCreateInfo.enabledExtensionCount = (uint32_t)enabledExtensions.size(); + instanceCreateInfo.ppEnabledExtensionNames = enabledExtensions.data(); + } + + CStringVector layers; + if (enableValidation) { + layers = filterLayers(debug::getDefaultValidationLayers()); + instanceCreateInfo.enabledLayerCount = (uint32_t)layers.size(); + instanceCreateInfo.ppEnabledLayerNames = layers.data(); + } + + instance = vk::createInstance(instanceCreateInfo); + + if (enableValidation) { + debug::setupDebugging(instance); + } + + if (enableDebugMarkers) { + debug::marker::setup(instance); + } + } + + void destroyContext() { + queue.waitIdle(); + device.waitIdle(); + for (const auto& trash : dumpster) { + trash(); + } + + while (!recycler.empty()) { + recycle(); + } + + destroyCommandPool(); + device.destroy(); + if (enableValidation) { + debug::cleanupDebugging(instance); + } + instance.destroy(); + } + + uint32_t findQueue(const vk::QueueFlags& desiredFlags, const vk::SurfaceKHR& presentSurface = vk::SurfaceKHR()) const { + uint32_t bestMatch{ VK_QUEUE_FAMILY_IGNORED }; + VkQueueFlags bestMatchExtraFlags{ VK_QUEUE_FLAG_BITS_MAX_ENUM }; + size_t queueCount = queueFamilyProperties.size(); + for (uint32_t i = 0; i < queueCount; ++i) { + auto currentFlags = queueFamilyProperties[i].queueFlags; + // Doesn't contain the required flags, skip it + if (!(currentFlags & desiredFlags)) { + continue; + } + + VkQueueFlags currentExtraFlags = (currentFlags & ~desiredFlags).operator VkQueueFlags(); + + // If we find an exact match, return immediately + if (0 == currentExtraFlags) { + return i; + } + + if (bestMatch == VK_QUEUE_FAMILY_IGNORED || currentExtraFlags < bestMatchExtraFlags) { + bestMatch = i; + bestMatchExtraFlags = currentExtraFlags; + } + } + + return bestMatch; + } + + template + void trash(T value, std::function destructor = [](T t) { t.destroy(); }) const { + if (!value) { + return; + } + dumpster.push_back([=] { destructor(value); }); + } + + template + void trashAll(const std::vector& values, std::function&)> destructor) const { + if (values.empty()) { + return; + } + dumpster.push_back([=] { destructor(values); }); + } + + // + // Convenience functions for trashing specific types. These functions know what kind of function + // call to make for destroying a given Vulkan object. + // + + void trashPipeline(vk::Pipeline& pipeline) const { + trash(pipeline, [this](vk::Pipeline pipeline) { device.destroyPipeline(pipeline); }); + } + + void trashCommandBuffers(const std::vector& cmdBuffers, vk::CommandPool commandPool = nullptr) const { + if (!commandPool) { + commandPool = getCommandPool(); + } + using DtorLambda = std::function&)>; + DtorLambda destructor = + [=](const std::vector& cmdBuffers) { + device.freeCommandBuffers(commandPool, cmdBuffers); + }; + trashAll(cmdBuffers, destructor); + } + + // Should be called from time to time by the application to migrate zombie resources + // to the recycler along with a fence that will be signalled when the objects are + // safe to delete. + void emptyDumpster(vk::Fence fence) { + VoidLambdaList newDumpster; + newDumpster.swap(dumpster); + recycler.push(FencedLambda{ fence, [fence, newDumpster, this] { + for (const auto& f : newDumpster) { + f(); + } + } }); + } + + // Check the recycler fences for signalled status. Any that are signalled will have their corresponding + // lambdas executed, freeing up the associated resources + void recycle() { + while (!recycler.empty()) { + const auto& trashItem = recycler.front(); + const auto& fence = trashItem.first; + auto fenceStatus = device.getFenceStatus(fence); + if (vk::Result::eSuccess != fenceStatus) { + break; + } + const VoidLambda& lambda = trashItem.second; + lambda(); + device.destroyFence(fence); + recycler.pop(); + } + } + + // Create an image memory barrier for changing the layout of + // an image and put it into an active command buffer + // See chapter 11.4 "vk::Image Layout" for details + + void setImageLayout(vk::CommandBuffer cmdbuffer, + vk::Image image, + vk::ImageLayout oldImageLayout, + vk::ImageLayout newImageLayout, + vk::ImageSubresourceRange subresourceRange) const { + // Create an image barrier object + vk::ImageMemoryBarrier imageMemoryBarrier; + imageMemoryBarrier.oldLayout = oldImageLayout; + imageMemoryBarrier.newLayout = newImageLayout; + imageMemoryBarrier.image = image; + imageMemoryBarrier.subresourceRange = subresourceRange; + imageMemoryBarrier.srcAccessMask = vks::util::accessFlagsForLayout(oldImageLayout); + imageMemoryBarrier.dstAccessMask = vks::util::accessFlagsForLayout(newImageLayout); + + // Put barrier on top + // Put barrier inside setup command buffer + cmdbuffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eAllCommands, + vk::DependencyFlags(), nullptr, nullptr, imageMemoryBarrier); + } + + // Fixed sub resource on first mip level and layer + void setImageLayout(vk::CommandBuffer cmdbuffer, + vk::Image image, + vk::ImageAspectFlags aspectMask, + vk::ImageLayout oldImageLayout, + vk::ImageLayout newImageLayout) const { + vk::ImageSubresourceRange subresourceRange; + subresourceRange.aspectMask = aspectMask; + subresourceRange.levelCount = 1; + subresourceRange.layerCount = 1; + setImageLayout(cmdbuffer, image, oldImageLayout, newImageLayout, subresourceRange); + } + + void setImageLayout(vk::Image image, + vk::ImageLayout oldImageLayout, + vk::ImageLayout newImageLayout, + vk::ImageSubresourceRange subresourceRange) const { + withPrimaryCommandBuffer([&](const auto& commandBuffer) { + setImageLayout(commandBuffer, image, oldImageLayout, newImageLayout, subresourceRange); + }); + } + + // Fixed sub resource on first mip level and layer + void setImageLayout(vk::Image image, + vk::ImageAspectFlags aspectMask, + vk::ImageLayout oldImageLayout, + vk::ImageLayout newImageLayout) const { + withPrimaryCommandBuffer([&](const auto& commandBuffer) { + setImageLayout(commandBuffer, image, aspectMask, oldImageLayout, newImageLayout); + }); + } + + void createDevice(const vk::SurfaceKHR& surface = nullptr) { + pickDevice(surface); + + buildDevice(); + +#if VULKAN_USE_VMA + vks::Allocation::initAllocator(physicalDevice, device); +#endif + + // Get the graphics queue + queue = device.getQueue(queueIndices.graphics, 0); + } + +protected: + void pickDevice(const vk::SurfaceKHR& surface ) { + // Physical device + physicalDevices = instance.enumeratePhysicalDevices(); + + // Note : + // This example will always use the first physical device reported, + // change the vector index if you have multiple Vulkan devices installed + // and want to use another one + physicalDevice = devicePicker(physicalDevices); + struct Version { + uint32_t patch : 12; + uint32_t minor : 10; + uint32_t major : 10; + } _version; + + + for (const auto& extensionProperties : physicalDevice.enumerateDeviceExtensionProperties()) { + physicalDeviceExtensions.insert(extensionProperties.extensionName); + qDebug() << "Device Extension " << extensionProperties.extensionName; + } + // Store properties (including limits) and features of the phyiscal device + // So examples can check against them and see if a feature is actually supported + queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + deviceProperties = physicalDevice.getProperties(); + memcpy(&_version, &deviceProperties.apiVersion, sizeof(uint32_t)); + deviceFeatures = physicalDevice.getFeatures(); + // Gather physical device memory properties + deviceMemoryProperties = physicalDevice.getMemoryProperties(); + queueIndices.graphics = findQueue(vk::QueueFlagBits::eGraphics, surface); + queueIndices.compute = findQueue(vk::QueueFlagBits::eCompute); + queueIndices.transfer = findQueue(vk::QueueFlagBits::eTransfer); + } + + void buildDevice() { + // Vulkan device + vks::queues::DeviceCreateInfo deviceCreateInfo; + deviceCreateInfo.addQueueFamily(queueIndices.graphics, queueFamilyProperties[queueIndices.graphics].queueCount); + if (queueIndices.compute != VK_QUEUE_FAMILY_IGNORED && queueIndices.compute != queueIndices.graphics) { + deviceCreateInfo.addQueueFamily(queueIndices.compute, queueFamilyProperties[queueIndices.compute].queueCount); + } + if (queueIndices.transfer != VK_QUEUE_FAMILY_IGNORED && queueIndices.transfer != queueIndices.graphics && + queueIndices.transfer != queueIndices.compute) { + deviceCreateInfo.addQueueFamily(queueIndices.transfer, queueFamilyProperties[queueIndices.transfer].queueCount); + } + deviceCreateInfo.update(); + + deviceCreateInfo.pEnabledFeatures = &deviceFeatures; + + std::set requestedDeviceExtensions = deviceExtensionsPicker(physicalDevice); + requestedDeviceExtensions.insert(requiredDeviceExtensions.begin(), requiredDeviceExtensions.end()); + + // enable the debug marker extension if it is present (likely meaning a debugging tool is present) + + std::vector enabledExtensions; + for (const auto& extension : requestedDeviceExtensions) { + enabledExtensions.push_back(extension.c_str()); + } + + + if (enabledExtensions.size() > 0) { + deviceCreateInfo.enabledExtensionCount = (uint32_t)enabledExtensions.size(); + deviceCreateInfo.ppEnabledExtensionNames = enabledExtensions.data(); + } + device = physicalDevice.createDevice(deviceCreateInfo); + } + +public: + // Vulkan instance, stores all per-application states + vk::Instance instance; + + std::vector physicalDevices; + // Physical device (GPU) that Vulkan will ise + vk::PhysicalDevice physicalDevice; + std::unordered_set physicalDeviceExtensions; + + // Queue family properties + std::vector queueFamilyProperties; + // Stores physical device properties (for e.g. checking device limits) + vk::PhysicalDeviceProperties deviceProperties; + // Stores phyiscal device features (for e.g. checking if a feature is available) + vk::PhysicalDeviceFeatures deviceFeatures; + + vk::PhysicalDeviceFeatures enabledFeatures; + // Stores all available memory (type) properties for the physical device + vk::PhysicalDeviceMemoryProperties deviceMemoryProperties; + // Logical device, application's view of the physical device (GPU) + vks::Device device; + + struct QueueIndices { + uint32_t graphics{ VK_QUEUE_FAMILY_IGNORED }; + uint32_t transfer{ VK_QUEUE_FAMILY_IGNORED }; + uint32_t compute{ VK_QUEUE_FAMILY_IGNORED }; + } queueIndices; + + vk::Queue queue; + + const vk::CommandPool& getCommandPool() const { + if (!commandPool) { + vk::CommandPoolCreateInfo cmdPoolInfo; + cmdPoolInfo.queueFamilyIndex = queueIndices.graphics; + cmdPoolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; + auto pool = device.createCommandPool(cmdPoolInfo); + const_cast(commandPool) = pool; + } + return commandPool; + } + + void destroyCommandPool() { + if (commandPool) { + device.destroy(commandPool); + commandPool = nullptr; + } + } + + std::vector allocateCommandBuffers( + uint32_t count, + vk::CommandBufferLevel level = vk::CommandBufferLevel::ePrimary) const { + std::vector result; + vk::CommandBufferAllocateInfo commandBufferAllocateInfo; + commandBufferAllocateInfo.commandPool = getCommandPool(); + commandBufferAllocateInfo.commandBufferCount = count; + commandBufferAllocateInfo.level = vk::CommandBufferLevel::ePrimary; + result = device.allocateCommandBuffers(commandBufferAllocateInfo); + return result; + } + + vk::CommandBuffer createCommandBuffer(vk::CommandBufferLevel level = vk::CommandBufferLevel::ePrimary) const { + vk::CommandBuffer cmdBuffer; + vk::CommandBufferAllocateInfo cmdBufAllocateInfo; + cmdBufAllocateInfo.commandPool = getCommandPool(); + cmdBufAllocateInfo.level = level; + cmdBufAllocateInfo.commandBufferCount = 1; + cmdBuffer = device.allocateCommandBuffers(cmdBufAllocateInfo)[0]; + return cmdBuffer; + } + + void flushCommandBuffer(vk::CommandBuffer& commandBuffer) const { + if (!commandBuffer) { + return; + } + queue.submit(vk::SubmitInfo{ 0, nullptr, nullptr, 1, &commandBuffer }, vk::Fence()); + queue.waitIdle(); + device.waitIdle(); + } + + // Create a short lived command buffer which is immediately executed and released + // This function is intended for initialization only. It incurs a queue and device + // flush and may impact performance if used in non-setup code + void withPrimaryCommandBuffer(const std::function& f) const { + vk::CommandBuffer commandBuffer = createCommandBuffer(vk::CommandBufferLevel::ePrimary); + commandBuffer.begin(vk::CommandBufferBeginInfo{ vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); + f(commandBuffer); + commandBuffer.end(); + flushCommandBuffer(commandBuffer); + device.freeCommandBuffers(getCommandPool(), commandBuffer); + } + + Image createImage(const vk::ImageCreateInfo& imageCreateInfo, const vk::MemoryPropertyFlags& memoryPropertyFlags) const { + Image result; + result.device = device; + result.format = imageCreateInfo.format; + result.extent = imageCreateInfo.extent; + +#if VULKAN_USE_VMA + VmaAllocationCreateInfo allocInfo = {}; + allocInfo.requiredFlags = memoryPropertyFlags.operator unsigned int(); + auto pCreateInfo = &(imageCreateInfo.operator const VkImageCreateInfo&()); + auto pImage = &reinterpret_cast(result.image); + vmaCreateImage(Allocation::getAllocator(), pCreateInfo, &allocInfo, pImage, &result.allocation, nullptr); +#else + result.image = device.createImage(imageCreateInfo); + if ((memoryPropertyFlags & vk::MemoryPropertyFlagBits::eLazilyAllocated) != + vk::MemoryPropertyFlagBits::eLazilyAllocated) { + vk::MemoryRequirements memReqs = device.getImageMemoryRequirements(result.image); + vk::MemoryAllocateInfo memAllocInfo; + memAllocInfo.allocationSize = result.allocSize = memReqs.size; + memAllocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, memoryPropertyFlags); + result.memory = device.allocateMemory(memAllocInfo); + device.bindImageMemory(result.image, result.memory, 0); + } +#endif + return result; + } + + Image stageToDeviceImage(vk::ImageCreateInfo imageCreateInfo, + const vk::MemoryPropertyFlags& memoryPropertyFlags, + vk::DeviceSize size, + const void* data, + const std::vector& mipData = {}) const { + Buffer staging = createStagingBuffer(size, data); + imageCreateInfo.usage = imageCreateInfo.usage | vk::ImageUsageFlagBits::eTransferDst; + Image result = createImage(imageCreateInfo, memoryPropertyFlags); + + withPrimaryCommandBuffer([&](const vk::CommandBuffer& copyCmd) { + vk::ImageSubresourceRange range(vk::ImageAspectFlagBits::eColor, 0, imageCreateInfo.mipLevels, 0, 1); + // Prepare for transfer + setImageLayout(copyCmd, result.image, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, range); + + // Prepare for transfer + std::vector bufferCopyRegions; + { + vk::BufferImageCopy bufferCopyRegion; + bufferCopyRegion.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; + bufferCopyRegion.imageSubresource.layerCount = 1; + if (!mipData.empty()) { + for (uint32_t i = 0; i < imageCreateInfo.mipLevels; i++) { + bufferCopyRegion.imageSubresource.mipLevel = i; + bufferCopyRegion.imageExtent = mipData[i].first; + bufferCopyRegions.push_back(bufferCopyRegion); + bufferCopyRegion.bufferOffset += mipData[i].second; + } + } else { + bufferCopyRegion.imageExtent = imageCreateInfo.extent; + bufferCopyRegions.push_back(bufferCopyRegion); + } + } + copyCmd.copyBufferToImage(staging.buffer, result.image, vk::ImageLayout::eTransferDstOptimal, bufferCopyRegions); + // Prepare for shader read + setImageLayout(copyCmd, result.image, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, + range); + }); + staging.destroy(); + return result; + } + + template + Image stageToDeviceImage(const vk::ImageCreateInfo& imageCreateInfo, + const vk::MemoryPropertyFlags& memoryPropertyFlags, + const std::vector& data) const { + return stageToDeviceImage(imageCreateInfo, memoryPropertyFlags, data.size() * sizeof(T), (void*)data.data()); + } + + template + Image stageToDeviceImage(const vk::ImageCreateInfo& imageCreateInfo, const std::vector& data) const { + return stageToDeviceImage(imageCreateInfo, vk::MemoryPropertyFlagBits::eDeviceLocal, data.size() * sizeof(T), + (void*)data.data()); + } + + Buffer createBuffer(const vk::BufferUsageFlags& usageFlags, + vk::DeviceSize size, + const vk::MemoryPropertyFlags& memoryPropertyFlags) const { + Buffer result; + result.device = device; + result.size = size; + result.descriptor.range = size; + result.descriptor.offset = 0; + + vk::BufferCreateInfo createInfo{ {}, size, usageFlags }; + +#if VULKAN_USE_VMA + VmaAllocationCreateInfo allocInfo = {}; + allocInfo.requiredFlags = memoryPropertyFlags.operator unsigned int(); + auto pCreateInfo = &createInfo.operator const VkBufferCreateInfo&(); + auto pBuffer = &reinterpret_cast(result.buffer); + vmaCreateBuffer(Allocation::getAllocator(), pCreateInfo, &allocInfo, pBuffer, &result.allocation, nullptr); +#else + result.descriptor.buffer = result.buffer = device.createBuffer(bufferCreateInfo); + vk::MemoryRequirements memReqs = device.getBufferMemoryRequirements(result.buffer); + vk::MemoryAllocateInfo memAlloc; + result.allocSize = memAlloc.allocationSize = memReqs.size; + memAlloc.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, memoryPropertyFlags); + result.memory = device.allocateMemory(memAlloc); + device.bindBufferMemory(result.buffer, result.memory, 0); +#endif + result.descriptor.buffer = result.buffer; + return result; + } + + Buffer createDeviceBuffer(const vk::BufferUsageFlags& usageFlags, vk::DeviceSize size) const { + static const vk::MemoryPropertyFlags memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal; + return createBuffer(usageFlags, size, memoryProperties); + } + + Buffer createStagingBuffer(vk::DeviceSize size, const void* data = nullptr) const { + auto result = createBuffer(vk::BufferUsageFlagBits::eTransferSrc, size, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); + if (data != nullptr) { + result.map(); + result.copy(size, data); + result.unmap(); + } + return result; + } + + template + Buffer createStagingBuffer(const std::vector& data) const { + return createBuffer(data.size() * sizeof(T), (void*)data.data()); + } + + template + Buffer createStagingBuffer(const T& data) const { + return createStagingBuffer(sizeof(T), &data); + } + + template + Buffer createUniformBuffer(const T& data, size_t count = 3) const { + auto alignment = deviceProperties.limits.minUniformBufferOffsetAlignment; + auto extra = sizeof(T) % alignment; + auto alignedSize = sizeof(T) + (alignment - extra); + auto allocatedSize = count * alignedSize; + static const auto usageFlags = vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eTransferDst; + static const auto memoryFlags = vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent; + auto result = createBuffer(usageFlags, allocatedSize, memoryFlags); + result.alignment = alignedSize; + result.descriptor.range = result.alignment; + result.map(); + result.copy(data); + return result; + } + + Buffer stageToDeviceBuffer(const vk::BufferUsageFlags& usage, size_t size, const void* data) const { + Buffer staging = createStagingBuffer(size, data); + Buffer result = createDeviceBuffer(usage | vk::BufferUsageFlagBits::eTransferDst, size); + withPrimaryCommandBuffer( + [&](vk::CommandBuffer copyCmd) { copyCmd.copyBuffer(staging.buffer, result.buffer, vk::BufferCopy(0, 0, size)); }); + staging.destroy(); + return result; + } + + template + Buffer stageToDeviceBuffer(const vk::BufferUsageFlags& usage, const std::vector& data) const { + return stageToDeviceBuffer(usage, sizeof(T) * data.size(), data.data()); + } + + template + Buffer stageToDeviceBuffer(const vk::BufferUsageFlags& usage, const T& data) const { + return stageToDeviceBuffer(usage, sizeof(T), (void*)&data); + } + + vk::Bool32 getMemoryType(uint32_t typeBits, const vk::MemoryPropertyFlags& properties, uint32_t* typeIndex) const { + for (uint32_t i = 0; i < 32; i++) { + if ((typeBits & 1) == 1) { + if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { + *typeIndex = i; + return true; + } + } + typeBits >>= 1; + } + return false; + } + + uint32_t getMemoryType(uint32_t typeBits, const vk::MemoryPropertyFlags& properties) const { + uint32_t result = 0; + if (!getMemoryType(typeBits, properties, &result)) { + // todo : throw error + } + return result; + } + + vk::Format getSupportedDepthFormat() const { + // Since all depth formats may be optional, we need to find a suitable depth format to use + // Start with the highest precision packed format + std::vector depthFormats = { vk::Format::eD32SfloatS8Uint, vk::Format::eD32Sfloat, + vk::Format::eD24UnormS8Uint, vk::Format::eD16UnormS8Uint, + vk::Format::eD16Unorm }; + + for (auto& format : depthFormats) { + vk::FormatProperties formatProps; + formatProps = physicalDevice.getFormatProperties(format); + // vk::Format must support depth stencil attachment for optimal tiling + if (formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment) { + return format; + } + } + + throw std::runtime_error("No supported depth format"); + } + + +private: + // A collection of items queued for destruction. Once a fence has been created + // for a queued submit, these items can be moved to the recycler for actual destruction + // by calling the rec + mutable VoidLambdaList dumpster; + FencedLambdaQueue recycler; + + InstanceExtensionsPickerFunctions instanceExtensionsPickers; + // Set to true when example is created with enabled validation layers + bool enableValidation = false; + // Set to true when the debug marker extension is detected + bool enableDebugMarkers = false; + + std::set requiredExtensions; + std::set requiredDeviceExtensions; + + DevicePickerFunction devicePicker = [](const std::vector& devices) -> vk::PhysicalDevice { + return devices[0]; + }; + + DeviceExtensionsPickerFunction deviceExtensionsPicker = [](const vk::PhysicalDevice& device) -> std::set { + return {}; + }; + + vk::CommandPool commandPool; +}; + +using ContextPtr = std::shared_ptr; +} // namespace vks diff --git a/libraries/vk/src/vk/Debug.cpp b/libraries/vk/src/vk/Debug.cpp new file mode 100644 index 0000000000..e7fc929b0b --- /dev/null +++ b/libraries/vk/src/vk/Debug.cpp @@ -0,0 +1,173 @@ +/* +* Vulkan examples debug wrapper +* +* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +*/ + +#include "Debug.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace vks { namespace debug { + +const StringList& getDefaultValidationLayers() { + static const StringList validationLayerNames { +#if defined(__ANDROID__) + "VK_LAYER_GOOGLE_threading", "VK_LAYER_LUNARG_parameter_validation", "VK_LAYER_LUNARG_object_tracker", + "VK_LAYER_LUNARG_core_validation", "VK_LAYER_LUNARG_swapchain", "VK_LAYER_GOOGLE_unique_objects", +#else + "VK_LAYER_LUNARG_standard_validation", +#endif + }; + return validationLayerNames; +} + +const Output DEFAULT_OUTPUT = [](const SevFlags& sevFlags, const std::string& message) { +#ifdef _MSC_VER + OutputDebugStringA(message.c_str()); + OutputDebugStringA("\n"); +#endif + std::stringstream buf; + if (sevFlags & SevBits::eError) { + std::cout << "ERROR: "; + } else if (sevFlags & SevBits::eWarning) { + std::cout << "WARNING: "; + } else if (sevFlags & SevBits::eInfo) { + std::cout << "INFO: "; + } else if (sevFlags & SevBits::eVerbose) { + std::cout << "VERBOSE: "; + } else { + std::cout << "Unknown sev: "; + } + + std::cout << message << std::endl; +}; + +const MessageFormatter DEFAULT_MESSAGE_FORMATTER = + [](const SevFlags& sevFlags, const TypeFlags& typeFlags, const CallbackData* callbackData, void*) -> std::string { + // FIXME improve on this + return std::string(callbackData->pMessage); +}; + +MessageFormatter CURRENT_FORMATTER = DEFAULT_MESSAGE_FORMATTER; +Output CURRENT_OUTPUT = DEFAULT_OUTPUT; + +Output setOutputFunction(const Output& function) { + Output result = function; + std::swap(result, CURRENT_OUTPUT); + return result; +} + +void setMessageFormatter(const MessageFormatter& function) { + CURRENT_FORMATTER = function; +} + +VkBool32 debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) { + SevFlags sevFlags; + reinterpret_cast(sevFlags) = messageSeverity; + TypeFlags typeFlags{ messageType }; + auto callbackData = reinterpret_cast(pCallbackData); + auto message = CURRENT_FORMATTER(sevFlags, typeFlags, callbackData, pUserData); + CURRENT_OUTPUT(sevFlags, message); + return VK_TRUE; +} + +static vk::DebugUtilsMessengerEXT messenger{}; + +const vk::DispatchLoaderDynamic& getInstanceDispatcher(const vk::Instance& instance = nullptr) { + static vk::DispatchLoaderDynamic dispatcher; + static std::once_flag once; + if (instance) { + std::call_once(once, [&] { dispatcher.init(instance); }); + } + return dispatcher; +} + +void setupDebugging(const vk::Instance& instance, const SevFlags& severityFlags, const TypeFlags& typeFlags, void* userData) { + vk::DebugUtilsMessengerCreateInfoEXT createInfo{ {}, severityFlags, typeFlags, debugCallback, userData }; + messenger = instance.createDebugUtilsMessengerEXT(createInfo, nullptr, getInstanceDispatcher(instance)); +} + +void cleanupDebugging(const vk::Instance& instance) { + instance.destroyDebugUtilsMessengerEXT(messenger, nullptr, getInstanceDispatcher(instance)); +} + +namespace marker { + +static bool active = false; + +void setup(const vk::Instance& instance) { + qDebug() << "QQQ" << __FUNCTION__ << "setup debugging"; + const auto& dispatcher = getInstanceDispatcher(instance); + active = (nullptr != dispatcher.vkSetDebugUtilsObjectTagEXT); +} + +void setObjectName(const vk::Device& device, uint64_t object, vk::ObjectType objectType, const std::string& name) { + const auto& dispatcher = getInstanceDispatcher(); + if (active) { + device.setDebugUtilsObjectNameEXT({ objectType, object, name.c_str() }, getInstanceDispatcher()); + } +} + +void beginRegion(const vk::CommandBuffer& cmdbuffer, const std::string& name, const glm::vec4& color) { + // Check for valid function pointer (may not be present if not running in a debugging application) + if (active) { + cmdbuffer.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ name.c_str(), { { color.r, color.g, color.b, color.a } } }, + getInstanceDispatcher()); + } +} + +void insert(const vk::CommandBuffer& cmdbuffer, const std::string& name, const glm::vec4& color) { + // Check for valid function pointer (may not be present if not running in a debugging application) + if (active) { + cmdbuffer.insertDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ name.c_str(), { { color.r, color.g, color.b, color.a } } }, + getInstanceDispatcher()); + } +} + +void endRegion(const vk::CommandBuffer& cmdbuffer) { + // Check for valid function (may not be present if not running in a debugging application) + if (active) { + cmdbuffer.endDebugUtilsLabelEXT(getInstanceDispatcher()); + } +} + +void setName(const vk::Device& device, const vk::CommandBuffer& obj, const std::string& name) { + setObjectName(device, (uint64_t)obj.operator VkCommandBuffer(), vk::ObjectType::eCommandBuffer, name); +} + +void setName(const vk::Device& device, const vk::Queue& obj, const std::string& name) { + setObjectName(device, (uint64_t)obj.operator VkQueue(), vk::ObjectType::eQueue, name); +} + +void setName(const vk::Device& device, const vk::Image& obj, const std::string& name) { + setObjectName(device, (uint64_t)obj.operator VkImage(), vk::ObjectType::eImage, name); +} + +void setName(const vk::Device& device, const vk::Buffer& obj, const std::string& name) { + setObjectName(device, (uint64_t)obj.operator VkBuffer(), vk::ObjectType::eBuffer, name); +} + +void setName(const vk::Device& device, const vk::Framebuffer& obj, const std::string& name) { + setObjectName(device, (uint64_t)obj.operator VkFramebuffer(), vk::ObjectType::eFramebuffer, name); +} + +void setName(const vk::Device& device, const vk::Pipeline& obj, const std::string& name) { + setObjectName(device, (uint64_t)obj.operator VkPipeline(), vk::ObjectType::ePipeline, name); +} + +} // namespace marker + +}} // namespace vks::debug diff --git a/libraries/vk/src/vk/Debug.h b/libraries/vk/src/vk/Debug.h new file mode 100644 index 0000000000..fa3625956d --- /dev/null +++ b/libraries/vk/src/vk/Debug.h @@ -0,0 +1,65 @@ +#pragma once + +#include "Config.h" + +namespace vks { namespace debug { + +using StringList = std::list; +const StringList& getDefaultValidationLayers(); + +using SevBits = vk::DebugUtilsMessageSeverityFlagBitsEXT; +using TypeBits = vk::DebugUtilsMessageTypeFlagBitsEXT; +using SevFlags = vk::DebugUtilsMessageSeverityFlagsEXT; +using TypeFlags = vk::DebugUtilsMessageTypeFlagsEXT; +using CallbackData = vk::DebugUtilsMessengerCallbackDataEXT; + +using Output = std::function; +Output setOutputFunction(const Output& function); + +using MessageFormatter = std::function; +void setMessageFormatter(const MessageFormatter& function); + +void setupDebugging(const vk::Instance& instance, + const SevFlags& severityFlags = SevBits::eError | SevBits::eWarning, + const TypeFlags& typeFlags = TypeBits::eGeneral | TypeBits::eValidation | TypeBits::ePerformance, + void* userData = nullptr); + +// Clear debug callback +void cleanupDebugging(const vk::Instance& instance); + +// Setup and functions for the VK_EXT_debug_marker_extension +// Extension spec can be found at https://github.com/KhronosGroup/Vulkan-Docs/blob/1.0-VK_EXT_debug_marker/doc/specs/vulkan/appendices/VK_EXT_debug_marker.txt +// Note that the extension will only be present if run from an offline debugging application +// The actual check for extension presence and enabling it on the device is done in the example base class +// See ExampleBase::createInstance and ExampleBase::createDevice (base/vkx::ExampleBase.cpp) + +namespace marker { + +// Get function pointers for the debug report extensions from the device +void setup(const vk::Instance& instance); + +// Start a new debug marker region +void beginRegion(const vk::CommandBuffer& cmdbuffer, const std::string& pMarkerName, const glm::vec4& color); + +// Insert a new debug marker into the command buffer +void insert(const vk::CommandBuffer& cmdbuffer, const std::string& markerName, const glm::vec4& color); + +// End the current debug marker region +void endRegion(const vk::CommandBuffer& cmdbuffer); + +// Sets the debug name of an object +// All Objects in Vulkan are represented by their 64-bit handles which are passed into this function +// along with the object type +void setObjectName(const vk::Device& device, uint64_t object, vk::ObjectType objectType, const std::string& name); + +// Object specific naming functions +void setName(const vk::Device& device, const vk::CommandBuffer& obj, const std::string& name); +void setName(const vk::Device& device, const vk::Queue& obj, const std::string& name); +void setName(const vk::Device& device, const vk::Image& obj, const std::string& name); +void setName(const vk::Device& device, const vk::Buffer& obj, const std::string& name); +void setName(const vk::Device& device, const vk::Framebuffer& obj, const std::string& name); +void setName(const vk::Device& device, const vk::Pipeline& obj, const std::string& name); + +} // namespace marker + +}} // namespace vks::debug diff --git a/libraries/vk/src/vk/Device.h b/libraries/vk/src/vk/Device.h new file mode 100644 index 0000000000..fe25e90d19 --- /dev/null +++ b/libraries/vk/src/vk/Device.h @@ -0,0 +1,171 @@ +#pragma once + +#include "Config.h" + +namespace vks { + + struct Device : public vk::Device { + using OptionalAllocationCallbacks = vk::Optional; + + using vk::Device::destroy; + + Device& operator=(const vk::Device& device) { + (vk::Device&)(*this) = device; + return *this; + } + + template + void destroy(T& t, OptionalAllocationCallbacks allocator = nullptr) const { +#if __cplusplus > 201703L + static_assert(false); +#else + assert(false); +#endif + } + + + template <> + void destroy(vk::Fence& object, OptionalAllocationCallbacks allocator) const { + destroyFence(object, allocator); + object = vk::Fence(); + } + + template <> + void destroy(vk::Semaphore& object, OptionalAllocationCallbacks allocator) const { + destroySemaphore(object, allocator); + object = vk::Semaphore(); + } + + template <> + void destroy(vk::Event& object, OptionalAllocationCallbacks allocator) const { + destroyEvent(object, allocator); + object = vk::Event(); + } + + template <> + void destroy(vk::QueryPool& object, OptionalAllocationCallbacks allocator) const { + destroyQueryPool(object, allocator); + object = vk::QueryPool(); + } + + template <> + void destroy(vk::Buffer& object, OptionalAllocationCallbacks allocator) const { + destroyBuffer(object, allocator); + object = vk::Buffer(); + } + + template <> + void destroy(vk::BufferView& object, OptionalAllocationCallbacks allocator) const { + destroyBufferView(object, allocator); + object = vk::BufferView(); + } + + template <> + void destroy(vk::Image& object, OptionalAllocationCallbacks allocator) const { + destroyImage(object, allocator); + object = vk::Image(); + } + + template <> + void destroy(vk::ImageView& object, OptionalAllocationCallbacks allocator) const { + destroyImageView(object, allocator); + object = vk::ImageView(); + } + + template <> + void destroy(vk::ShaderModule& object, OptionalAllocationCallbacks allocator) const { + destroyShaderModule(object, allocator); + object = vk::ShaderModule(); + } + + template <> + void destroy(vk::PipelineCache& object, OptionalAllocationCallbacks allocator) const { + destroyPipelineCache(object, allocator); + object = vk::PipelineCache(); + } + + template <> + void destroy(vk::Pipeline& object, OptionalAllocationCallbacks allocator) const { + destroyPipeline(object, allocator); + object = vk::Pipeline(); + } + + template <> + void destroy(vk::PipelineLayout& object, OptionalAllocationCallbacks allocator) const { + destroyPipelineLayout(object, allocator); + object = vk::PipelineLayout(); + } + + template <> + void destroy(vk::Sampler& object, OptionalAllocationCallbacks allocator) const { + destroySampler(object, allocator); + object = vk::Sampler(); + } + + template <> + void destroy(vk::DescriptorSetLayout& object, OptionalAllocationCallbacks allocator) const { + destroyDescriptorSetLayout(object, allocator); + object = vk::DescriptorSetLayout(); + } + + template <> + void destroy(vk::DescriptorPool& object, OptionalAllocationCallbacks allocator) const { + destroyDescriptorPool(object, allocator); + object = vk::DescriptorPool(); + } + + template <> + void destroy(vk::Framebuffer& object, OptionalAllocationCallbacks allocator) const { + destroyFramebuffer(object, allocator); + object = vk::Framebuffer(); + } + + template <> + void destroy(vk::RenderPass& object, OptionalAllocationCallbacks allocator) const { + destroyRenderPass(object, allocator); + object = vk::RenderPass(); + } + + template <> + void destroy(vk::CommandPool& object, OptionalAllocationCallbacks allocator) const { + destroyCommandPool(object, allocator); + object = vk::CommandPool(); + } + + template <> + void destroy(vk::SwapchainKHR& object, OptionalAllocationCallbacks allocator) const { + destroySwapchainKHR(object, allocator); + object = vk::SwapchainKHR(); + } + + template <> + void destroy(vk::IndirectCommandsLayoutNVX& object, OptionalAllocationCallbacks allocator) const { + destroyIndirectCommandsLayoutNVX(object, allocator); + object = vk::IndirectCommandsLayoutNVX(); + } + + template <> + void destroy(vk::ObjectTableNVX& object, OptionalAllocationCallbacks allocator) const { + destroyObjectTableNVX(object, allocator); + object = vk::ObjectTableNVX(); + } + + template <> + void destroy(vk::DescriptorUpdateTemplateKHR& object, OptionalAllocationCallbacks allocator) const { + destroyDescriptorUpdateTemplateKHR(object, allocator); + object = vk::DescriptorUpdateTemplateKHR(); + } + + template <> + void destroy(vk::SamplerYcbcrConversionKHR& object, OptionalAllocationCallbacks allocator) const { + destroySamplerYcbcrConversionKHR(object, allocator); + object = vk::SamplerYcbcrConversionKHR(); + } + + template <> + void destroy(vk::ValidationCacheEXT& object, OptionalAllocationCallbacks allocator) const { + destroyValidationCacheEXT(object, allocator); + object = vk::ValidationCacheEXT(); + } + }; +} diff --git a/libraries/vk/src/vk/Forward.h b/libraries/vk/src/vk/Forward.h new file mode 100644 index 0000000000..51596091e7 --- /dev/null +++ b/libraries/vk/src/vk/Forward.h @@ -0,0 +1,7 @@ +namespace vks { + struct Allocated; + struct Buffer; + struct Image; + struct Context; + struct Swapchain; +} diff --git a/libraries/vk/src/vk/Framebuffer.h b/libraries/vk/src/vk/Framebuffer.h new file mode 100644 index 0000000000..2407cff510 --- /dev/null +++ b/libraries/vk/src/vk/Framebuffer.h @@ -0,0 +1,109 @@ +// +// Created by Bradley Austin Davis on 2016/03/19 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#include "Config.h" +#include "Context.h" + +namespace vks { + using FramebufferAttachment = Image; + struct Framebuffer { + using Attachment = FramebufferAttachment; + vk::Device device; + vk::Framebuffer framebuffer; + Attachment depth; + std::vector colors; + + void destroy() { + for (auto& color : colors) { + color.destroy(); + } + if (depth.format != vk::Format::eUndefined) { + depth.destroy(); + } + if (framebuffer) { + device.destroyFramebuffer(framebuffer); + framebuffer = vk::Framebuffer(); + } + } + + // Prepare a new framebuffer for offscreen rendering + // The contents of this framebuffer are then + // blitted to our render target + void create(const vks::Context& context, const glm::uvec2& size, const std::vector& colorFormats, vk::Format depthFormat, const vk::RenderPass& renderPass, vk::ImageUsageFlags colorUsage = vk::ImageUsageFlagBits::eSampled, vk::ImageUsageFlags depthUsage = vk::ImageUsageFlags()) { + device = context.device; + destroy(); + + colors.resize(colorFormats.size()); + + // Color attachment + vk::ImageCreateInfo image; + image.imageType = vk::ImageType::e2D; + image.extent.width = size.x; + image.extent.height = size.y; + image.extent.depth = 1; + image.mipLevels = 1; + image.arrayLayers = 1; + image.samples = vk::SampleCountFlagBits::e1; + image.tiling = vk::ImageTiling::eOptimal; + // vk::Image of the framebuffer is blit source + image.usage = vk::ImageUsageFlagBits::eColorAttachment | colorUsage; + + vk::ImageViewCreateInfo colorImageView; + colorImageView.viewType = vk::ImageViewType::e2D; + colorImageView.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + colorImageView.subresourceRange.levelCount = 1; + colorImageView.subresourceRange.layerCount = 1; + + for (size_t i = 0; i < colorFormats.size(); ++i) { + image.format = colorFormats[i]; + colors[i] = context.createImage(image, vk::MemoryPropertyFlagBits::eDeviceLocal); + colorImageView.format = colorFormats[i]; + colorImageView.image = colors[i].image; + colors[i].view = device.createImageView(colorImageView); + } + + + bool useDepth = depthFormat != vk::Format::eUndefined; + // Depth stencil attachment + if (useDepth) { + image.format = depthFormat; + image.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment | depthUsage; + depth = context.createImage(image, vk::MemoryPropertyFlagBits::eDeviceLocal); + + vk::ImageViewCreateInfo depthStencilView; + depthStencilView.viewType = vk::ImageViewType::e2D; + depthStencilView.format = depthFormat; + depthStencilView.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; + depthStencilView.subresourceRange.levelCount = 1; + depthStencilView.subresourceRange.layerCount = 1; + depthStencilView.image = depth.image; + depth.view = device.createImageView(depthStencilView); + } + + std::vector attachments; + attachments.resize(colors.size()); + for (size_t i = 0; i < colors.size(); ++i) { + attachments[i] = colors[i].view; + } + if (useDepth) { + attachments.push_back(depth.view); + } + + vk::FramebufferCreateInfo fbufCreateInfo; + fbufCreateInfo.renderPass = renderPass; + fbufCreateInfo.attachmentCount = (uint32_t)attachments.size(); + fbufCreateInfo.pAttachments = attachments.data(); + fbufCreateInfo.width = size.x; + fbufCreateInfo.height = size.y; + fbufCreateInfo.layers = 1; + framebuffer = context.device.createFramebuffer(fbufCreateInfo); + } + }; +} diff --git a/libraries/vk/src/vk/Helpers.cpp b/libraries/vk/src/vk/Helpers.cpp new file mode 100644 index 0000000000..fbeebfdebd --- /dev/null +++ b/libraries/vk/src/vk/Helpers.cpp @@ -0,0 +1,86 @@ +#include "Helpers.h" + +#include + +#include +#include + +#include +#include + +const QString& getPipelineCacheFile() { + static const QString PIPELINE_CACHE_FOLDER{ "" }; + static const QString PIPELINE_CACHE_FILE_NAME{ "pipeline_cache.bin" }; + static const QString PIPELINE_CACHE_FILE = FileUtils::standardPath(PIPELINE_CACHE_FOLDER) + PIPELINE_CACHE_FILE_NAME; + return PIPELINE_CACHE_FOLDER; +} + +bool vks::util::loadPipelineCacheData(std::vector& outCache) { + outCache.clear(); + const QString& cacheFile = getPipelineCacheFile(); + if (QFileInfo(cacheFile).exists()) { + QFile file(cacheFile); + if (file.open(QFile::ReadOnly)) { + QByteArray data = file.readAll(); + outCache.resize(data.size()); + memcpy(outCache.data(), data.data(), data.size()); + file.close(); + return true; + } + } + return false; +} + +void vks::util::savePipelineCacheData(const std::vector& cache) { + QString cacheFile = getPipelineCacheFile(); + QFile saveFile(cacheFile); + saveFile.open(QFile::WriteOnly | QFile::Truncate); + saveFile.write((const char*)cache.data(), cache.size()); + saveFile.close(); +} + +static std::set getGLExtensions() { + static std::set result; + static std::once_flag once; + std::call_once(once, [&]{ + GLint count = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &count); + for (GLint i = 0; i < count; ++i) { + auto name = glGetStringi(GL_EXTENSIONS, i); + result.insert((const char*)name); + } + }); + return result; +} + +static bool hasExtension(const std::string& name) { + const auto& extensions = getGLExtensions(); + return 0 != extensions.count(name); +} + + +vks::util::gl::UuidSet vks::util::gl::getUuids() { + static vks::util::gl::UuidSet result; + static std::once_flag once; + QUuid driverUuid; + using GLUUID = std::array; + + std::call_once(once, [&]{ + GLUUID value; + glGetIntegerv(GL_DRIVER_UUID_EXT, value.data()); + + GLint deviceIdCount = 0; + glGetIntegerv(GL_NUM_DEVICE_UUIDS_EXT, &deviceIdCount); + for (GLint i = 0; i < deviceIdCount; ++i) { + result.insert(QUuid(QByteArray((const char*)value.data(), (int)value.size()))); + } + }); + return result; +} + +bool vks::util::gl::contextSupported(QOpenGLContext*) { + return hasExtension("GL_EXT_memory_object") && hasExtension("GL_EXT_semaphore"); + } + + + diff --git a/libraries/vk/src/vk/Helpers.h b/libraries/vk/src/vk/Helpers.h new file mode 100644 index 0000000000..5777fe627f --- /dev/null +++ b/libraries/vk/src/vk/Helpers.h @@ -0,0 +1,91 @@ +#pragma once + +#include "Config.h" +#include +#include + +#include + + +class QOpenGLContext; + +namespace vks { namespace util { + + +inline vk::ColorComponentFlags fullColorWriteMask() { + return vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | + vk::ColorComponentFlagBits::eA; +} + +inline vk::Viewport viewport(float width, float height, float minDepth = 0, float maxDepth = 1) { + vk::Viewport viewport; + viewport.width = width; + viewport.height = height; + viewport.minDepth = minDepth; + viewport.maxDepth = maxDepth; + return viewport; +} + +inline vk::Viewport viewport(const glm::uvec2& size, float minDepth = 0, float maxDepth = 1) { + return viewport((float)size.x, (float)size.y, minDepth, maxDepth); +} + +inline vk::Viewport viewport(const vk::Extent2D& size, float minDepth = 0, float maxDepth = 1) { + return viewport((float)size.width, (float)size.height, minDepth, maxDepth); +} + +inline vk::Rect2D rect2D(uint32_t width, uint32_t height, int32_t offsetX = 0, int32_t offsetY = 0) { + vk::Rect2D rect2D; + rect2D.extent.width = width; + rect2D.extent.height = height; + rect2D.offset.x = offsetX; + rect2D.offset.y = offsetY; + return rect2D; +} + +inline vk::Rect2D rect2D(const glm::uvec2& size, const glm::ivec2& offset = glm::ivec2(0)) { + return rect2D(size.x, size.y, offset.x, offset.y); +} + +inline vk::Rect2D rect2D(const vk::Extent2D& size, const vk::Offset2D& offset = vk::Offset2D()) { + return rect2D(size.width, size.height, offset.x, offset.y); +} + +inline vk::AccessFlags accessFlagsForLayout(vk::ImageLayout layout) { + switch (layout) { + case vk::ImageLayout::ePreinitialized: + return vk::AccessFlagBits::eHostWrite; + case vk::ImageLayout::eTransferDstOptimal: + return vk::AccessFlagBits::eTransferWrite; + case vk::ImageLayout::eTransferSrcOptimal: + return vk::AccessFlagBits::eTransferRead; + case vk::ImageLayout::eColorAttachmentOptimal: + return vk::AccessFlagBits::eColorAttachmentWrite; + case vk::ImageLayout::eDepthStencilAttachmentOptimal: + return vk::AccessFlagBits::eDepthStencilAttachmentWrite; + case vk::ImageLayout::eShaderReadOnlyOptimal: + return vk::AccessFlagBits::eShaderRead; + default: + return vk::AccessFlags(); + } +} + +inline vk::ClearColorValue clearColor(const glm::vec4& v = glm::vec4(0)) { + vk::ClearColorValue result; + memcpy(&result.float32, &v, sizeof(result.float32)); + return result; +} + +bool loadPipelineCacheData(std::vector& outCache); +void savePipelineCacheData(const std::vector& cache); + +namespace gl { + +using UuidSet = std::set; + +UuidSet getUuids(); +bool contextSupported(QOpenGLContext*); + +} // namespace vks::util::gl + +}} // namespace vks::util diff --git a/libraries/vk/src/vk/Image.h b/libraries/vk/src/vk/Image.h new file mode 100644 index 0000000000..68ed3a73eb --- /dev/null +++ b/libraries/vk/src/vk/Image.h @@ -0,0 +1,42 @@ +#pragma once + +#include "Allocation.h" + +namespace vks { + // Encaspulates an image, the memory for that image, a view of the image, + // as well as a sampler and the image format. + // + // The sampler is not populated by the allocation code, but is provided + // for convenience and easy cleanup if it is populated. + struct Image : public Allocation + { + private: + using Parent = Allocation; + public: + vk::Image image; + vk::Extent3D extent; + vk::ImageView view; + vk::Sampler sampler; + vk::Format format{ vk::Format::eUndefined }; + + operator bool() const { + return image.operator bool(); + } + + void destroy() override { + if (sampler) { + device.destroySampler(sampler); + sampler = vk::Sampler(); + } + if (view) { + device.destroyImageView(view); + view = vk::ImageView(); + } + if (image) { + device.destroyImage(image); + image = vk::Image(); + } + Parent::destroy(); + } + }; +} diff --git a/libraries/vk/src/vk/Pipelines.h b/libraries/vk/src/vk/Pipelines.h new file mode 100644 index 0000000000..0aa298840d --- /dev/null +++ b/libraries/vk/src/vk/Pipelines.h @@ -0,0 +1,173 @@ +#pragma once + +#include "Context.h" +#include "Shaders.h" + +namespace vks { + namespace pipelines { + struct PipelineRasterizationStateCreateInfo : public vk::PipelineRasterizationStateCreateInfo { + using Parent = vk::PipelineRasterizationStateCreateInfo; + PipelineRasterizationStateCreateInfo() { + lineWidth = 1.0f; + cullMode = vk::CullModeFlagBits::eBack; + } + }; + + + struct PipelineInputAssemblyStateCreateInfo : public vk::PipelineInputAssemblyStateCreateInfo { + PipelineInputAssemblyStateCreateInfo() { + topology = vk::PrimitiveTopology::eTriangleList; + } + }; + + struct PipelineColorBlendAttachmentState : public vk::PipelineColorBlendAttachmentState { + PipelineColorBlendAttachmentState() { + colorWriteMask = vks::util::fullColorWriteMask(); + } + }; + + struct PipelineColorBlendStateCreateInfo : public vk::PipelineColorBlendStateCreateInfo { + // Default to a single color attachment state with no blending + std::vector blendAttachmentStates{ PipelineColorBlendAttachmentState() }; + + void update() { + this->attachmentCount = (uint32_t)blendAttachmentStates.size(); + this->pAttachments = blendAttachmentStates.data(); + } + }; + + struct PipelineDynamicStateCreateInfo : public vk::PipelineDynamicStateCreateInfo { + std::vector dynamicStateEnables; + + PipelineDynamicStateCreateInfo() { + dynamicStateEnables = { vk::DynamicState::eViewport, vk::DynamicState::eScissor }; + } + + void update() { + this->dynamicStateCount = (uint32_t)dynamicStateEnables.size(); + this->pDynamicStates = dynamicStateEnables.data(); + } + }; + + struct PipelineVertexInputStateCreateInfo : public vk::PipelineVertexInputStateCreateInfo { + std::vector bindingDescriptions; + std::vector attributeDescriptions; + + void update() { + vertexAttributeDescriptionCount = (uint32_t)attributeDescriptions.size(); + vertexBindingDescriptionCount = (uint32_t)bindingDescriptions.size(); + pVertexBindingDescriptions = bindingDescriptions.data(); + pVertexAttributeDescriptions = attributeDescriptions.data(); + } + }; + + struct PipelineViewportStateCreateInfo : public vk::PipelineViewportStateCreateInfo { + std::vector viewports; + std::vector scissors; + + void update() { + if (viewports.empty()) { + viewportCount = 1; + pViewports = nullptr; + } else { + viewportCount = (uint32_t)viewports.size(); + pViewports = viewports.data(); + } + + if (scissors.empty()) { + scissorCount = 1; + pScissors = 0; + } else { + scissorCount = (uint32_t)scissors.size(); + pScissors = scissors.data(); + } + } + }; + + struct PipelineDepthStencilStateCreateInfo : public vk::PipelineDepthStencilStateCreateInfo { + PipelineDepthStencilStateCreateInfo(bool depthEnable = true) { + if (depthEnable) { + depthTestEnable = VK_TRUE; + depthWriteEnable = VK_TRUE; + depthCompareOp = vk::CompareOp::eLessOrEqual; + } + } + }; + struct GraphicsPipelineBuilder { + private: + void init() { + pipelineCreateInfo.pRasterizationState = &rasterizationState; + pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; + pipelineCreateInfo.pColorBlendState = &colorBlendState; + pipelineCreateInfo.pMultisampleState = &multisampleState; + pipelineCreateInfo.pViewportState = &viewportState; + pipelineCreateInfo.pDepthStencilState = &depthStencilState; + pipelineCreateInfo.pDynamicState = &dynamicState; + pipelineCreateInfo.pVertexInputState = &vertexInputState; + } + public: + GraphicsPipelineBuilder(const vk::Device& device, const vk::PipelineLayout layout, const vk::RenderPass& renderPass) : + device(device) { + pipelineCreateInfo.layout = layout; + pipelineCreateInfo.renderPass = renderPass; + init(); + } + + GraphicsPipelineBuilder(const GraphicsPipelineBuilder& other) : GraphicsPipelineBuilder(other.device, other.layout, other.renderPass) {} + + GraphicsPipelineBuilder& operator=(const GraphicsPipelineBuilder& other) = delete; + + ~GraphicsPipelineBuilder() { + destroyShaderModules(); + } + + const vk::Device& device; + vk::PipelineCache pipelineCache; + vk::RenderPass& renderPass { pipelineCreateInfo.renderPass }; + vk::PipelineLayout& layout { pipelineCreateInfo.layout }; + PipelineInputAssemblyStateCreateInfo inputAssemblyState; + PipelineRasterizationStateCreateInfo rasterizationState; + vk::PipelineMultisampleStateCreateInfo multisampleState; + PipelineDepthStencilStateCreateInfo depthStencilState; + PipelineViewportStateCreateInfo viewportState; + PipelineDynamicStateCreateInfo dynamicState; + PipelineColorBlendStateCreateInfo colorBlendState; + PipelineVertexInputStateCreateInfo vertexInputState; + std::vector shaderStages; + + vk::GraphicsPipelineCreateInfo pipelineCreateInfo; + + void update() { + pipelineCreateInfo.stageCount = static_cast(shaderStages.size()); + pipelineCreateInfo.pStages = shaderStages.data(); + dynamicState.update(); + colorBlendState.update(); + vertexInputState.update(); + viewportState.update(); + } + + void destroyShaderModules() { + for (const auto shaderStage : shaderStages) { + device.destroyShaderModule(shaderStage.module); + } + shaderStages.clear(); + } + + // Load a SPIR-V shader + vk::PipelineShaderStageCreateInfo& loadShader(const std::string& fileName, vk::ShaderStageFlagBits stage, const char* entryPoint = "main") { + vk::PipelineShaderStageCreateInfo shaderStage = vks::shaders::loadShader(device, fileName, stage, entryPoint); + shaderStages.push_back(shaderStage); + return shaderStages.back(); + } + + vk::Pipeline create(const vk::PipelineCache& cache) { + update(); + return device.createGraphicsPipeline(cache, pipelineCreateInfo); + } + + vk::Pipeline create() { + return create(pipelineCache); + } + }; + } +} // namespace vks::pipelines diff --git a/libraries/vk/src/vk/Renderpasses.h b/libraries/vk/src/vk/Renderpasses.h new file mode 100644 index 0000000000..ad9d6aeeed --- /dev/null +++ b/libraries/vk/src/vk/Renderpasses.h @@ -0,0 +1,147 @@ +#pragma once + +#include "Config.h" + +namespace vks { namespace renderpasses { + + struct AttachmentDescription : public vk::AttachmentDescription { + AttachmentDescription& withFlags(vk::AttachmentDescriptionFlags flags) { + this->flags = flags; return *this; + } + AttachmentDescription& withFormat(vk::Format format) { + this->format = format; return *this; + } + AttachmentDescription& withInitialLayout(vk::ImageLayout layout) { + this->initialLayout = layout; return *this; + } + AttachmentDescription& withFinalLayout(vk::ImageLayout layout) { + this->finalLayout = layout; return *this; + } + AttachmentDescription& withSampleCount(vk::SampleCountFlagBits samples) { + this->samples = samples; return *this; + } + AttachmentDescription& withLoadOp(vk::AttachmentLoadOp loadOp) { + this->loadOp = loadOp; return *this; + } + AttachmentDescription& withStoreOp(vk::AttachmentStoreOp storeOp) { + this->storeOp = storeOp; return *this; + } + AttachmentDescription& withLStenciloadOp(vk::AttachmentLoadOp loadOp) { + this->stencilLoadOp = loadOp; return *this; + } + AttachmentDescription& withStencilStoreOp(vk::AttachmentStoreOp storeOp) { + this->stencilStoreOp = storeOp; return *this; + } + }; + + struct SubpassDescription : public vk::SubpassDescription { + std::vector colorAttachments; + std::vector inputAttachments; + std::vector resolveAttachments; + vk::AttachmentReference depthStencilAttachment; + std::vector preserveAttachments; + + void update() { + this->colorAttachmentCount = (uint32_t)colorAttachments.size(); + this->pColorAttachments = colorAttachments.data(); + this->inputAttachmentCount = (uint32_t)inputAttachments.size(); + this->pInputAttachments = inputAttachments.data(); + this->pResolveAttachments = resolveAttachments.data(); + this->pDepthStencilAttachment = &depthStencilAttachment; + this->preserveAttachmentCount = (uint32_t)preserveAttachments.size(); + this->pPreserveAttachments = preserveAttachments.data(); + } + }; + + struct RenderPassBuilder { + std::vector attachments; + std::vector subpassDependencies; + std::vector subpasses; + + size_t addAttachment(const vk::AttachmentDescription& attachment) { + attachments.push_back(attachment); + return attachments.size() - 1; + } + + vk::RenderPass build(const vk::Device& device) { + for (auto& subpass : subpasses) { + subpass.update(); + } + + vk::RenderPassCreateInfo renderPassInfo; + renderPassInfo.attachmentCount = (uint32_t)attachments.size(); + renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.subpassCount = (uint32_t)subpasses.size(); + renderPassInfo.pSubpasses = subpasses.data(); + renderPassInfo.dependencyCount = (uint32_t)subpassDependencies.size(); + renderPassInfo.pDependencies = subpassDependencies.data(); + return device.createRenderPass(renderPassInfo); + } + }; + +#if 0 +subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; +attachments.resize(colorFormats.size()); +colorAttachmentReferences.resize(attachments.size()); +// Color attachment +for (uint32_t i = 0; i < attachments.size(); ++i) { + attachments[i].format = colorFormats[i]; + attachments[i].loadOp = vk::AttachmentLoadOp::eClear; + attachments[i].storeOp = colorFinalLayout == vk::ImageLayout::eColorAttachmentOptimal ? vk::AttachmentStoreOp::eDontCare : vk::AttachmentStoreOp::eStore; + attachments[i].initialLayout = vk::ImageLayout::eUndefined; + attachments[i].finalLayout = colorFinalLayout; + + vk::AttachmentReference& attachmentReference = colorAttachmentReferences[i]; + attachmentReference.attachment = i; + attachmentReference.layout = vk::ImageLayout::eColorAttachmentOptimal; + + subpass.colorAttachmentCount = (uint32_t)colorAttachmentReferences.size(); + subpass.pColorAttachments = colorAttachmentReferences.data(); +} + +// Do we have a depth format? +vk::AttachmentReference depthAttachmentReference; +if (depthFormat != vk::Format::eUndefined) { + vk::AttachmentDescription depthAttachment; + depthAttachment.format = depthFormat; + depthAttachment.loadOp = vk::AttachmentLoadOp::eClear; + // We might be using the depth attacment for something, so preserve it if it's final layout is not undefined + depthAttachment.storeOp = + depthFinalLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal ? vk::AttachmentStoreOp::eDontCare : vk::AttachmentStoreOp::eStore; + depthAttachment.initialLayout = vk::ImageLayout::eUndefined; + depthAttachment.finalLayout = depthFinalLayout; + attachments.push_back(depthAttachment); + depthAttachmentReference.attachment = (uint32_t)attachments.size() - 1; + depthAttachmentReference.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal; + subpass.pDepthStencilAttachment = &depthAttachmentReference; +} + +{ + if ((colorFinalLayout != vk::ImageLayout::eColorAttachmentOptimal) && (colorFinalLayout != vk::ImageLayout::eUndefined)) { + // Implicit transition + vk::SubpassDependency dependency; + dependency.srcSubpass = 0; + dependency.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + + dependency.dstSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstAccessMask = vks::util::accessFlagsForLayout(colorFinalLayout); + dependency.dstStageMask = vk::PipelineStageFlagBits::eBottomOfPipe; + subpassDependencies.push_back(dependency); + } + + if ((depthFinalLayout != vk::ImageLayout::eColorAttachmentOptimal) && (depthFinalLayout != vk::ImageLayout::eUndefined)) { + // Implicit transition + vk::SubpassDependency dependency; + dependency.srcSubpass = 0; + dependency.srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite; + dependency.srcStageMask = vk::PipelineStageFlagBits::eBottomOfPipe; + + dependency.dstSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstAccessMask = vks::util::accessFlagsForLayout(depthFinalLayout); + dependency.dstStageMask = vk::PipelineStageFlagBits::eBottomOfPipe; + subpassDependencies.push_back(dependency); + } +#endif + +}} // namespace vks::renderpasses diff --git a/libraries/vk/src/vk/Swapchain.h b/libraries/vk/src/vk/Swapchain.h new file mode 100644 index 0000000000..eece0083cb --- /dev/null +++ b/libraries/vk/src/vk/Swapchain.h @@ -0,0 +1,279 @@ +/* +* Class wrapping access to the swap chain +* +* A swap chain is a collection of framebuffers used for rendering +* The swap chain images can then presented to the windowing system +* +* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +*/ + +#pragma once + +#include "Config.h" +#include + +namespace vks { + struct SwapchainImage { + vk::Image image; + vk::ImageView view; + vk::Fence fence; + }; + + struct Swapchain { + vk::SurfaceKHR surface; + vk::SwapchainKHR swapchain; + vk::PresentInfoKHR presentInfo; + vk::Extent2D extent; + const vks::Context& context{ vks::Context::get() }; + const vk::PhysicalDevice& physicalDevice{ context.physicalDevice }; + const vk::Device& device{ context.device }; + const uint32_t& graphicsQueueIndex{ context.queueIndices.graphics }; + const vk::Queue& queue{ context.queue }; + std::vector images; + vk::Format colorFormat; + vk::ColorSpaceKHR colorSpace; + uint32_t imageCount{ 0 }; + uint32_t currentImage{ 0 }; + + Swapchain() { + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &swapchain; + presentInfo.pImageIndices = ¤tImage; + } + + void waitIdle() { + queue.waitIdle(); + device.waitIdle(); + } + + void setSurface(const vk::SurfaceKHR& newSurface) { + surface = newSurface; + } + + // Creates an os specific surface + // Tries to find a graphics and a present queue + void create(vk::Extent2D& size, bool vsync = false) { + if (!physicalDevice || !device || !surface) { + throw std::runtime_error("Initialize the physicalDevice, device, and queue members"); + } + + static std::once_flag once; + std::call_once(once, [&] { + // Get list of supported surface formats + std::vector surfaceFormats = physicalDevice.getSurfaceFormatsKHR(surface); + auto formatCount = surfaceFormats.size(); + + physicalDevice.getSurfaceSupportKHR(graphicsQueueIndex, surface); + + + // If the surface format list only includes one entry with vk::Format::eUndefined, + // there is no preferered format, so we assume vk::Format::eB8G8R8A8Unorm + if ((formatCount == 1) && (surfaceFormats[0].format == vk::Format::eUndefined)) { + colorFormat = vk::Format::eB8G8R8A8Unorm; + } else { + // Always select the first available color format + // If you need a specific format (e.g. SRGB) you'd need to + // iterate over the list of available surface format and + // check for it's presence + colorFormat = surfaceFormats[0].format; + } + colorSpace = surfaceFormats[0].colorSpace; + }); + + + vk::SwapchainKHR oldSwapchain = swapchain; + currentImage = 0; + + // Get physical device surface properties and formats + vk::SurfaceCapabilitiesKHR surfCaps = physicalDevice.getSurfaceCapabilitiesKHR(surface); + // Get available present modes + std::vector presentModes = physicalDevice.getSurfacePresentModesKHR(surface); + auto presentModeCount = presentModes.size(); + + // width and height are either both -1, or both not -1. + if (surfCaps.currentExtent.width == -1) { + // If the surface size is undefined, the size is set to + // the size of the images requested. + extent = size; + } else { + // If the surface size is defined, the swap chain size must match + extent = surfCaps.currentExtent; + size = surfCaps.currentExtent; + } + + // Prefer mailbox mode if present, it's the lowest latency non-tearing present mode + vk::PresentModeKHR swapchainPresentMode = vk::PresentModeKHR::eFifo; + + if (!vsync) { + for (size_t i = 0; i < presentModeCount; i++) { + if (presentModes[i] == vk::PresentModeKHR::eMailbox) { + swapchainPresentMode = vk::PresentModeKHR::eMailbox; + break; + } + if ((swapchainPresentMode != vk::PresentModeKHR::eMailbox) && (presentModes[i] == vk::PresentModeKHR::eImmediate)) { + swapchainPresentMode = vk::PresentModeKHR::eImmediate; + } + } + } + + // Determine the number of images + uint32_t desiredNumberOfSwapchainImages = surfCaps.minImageCount + 1; + if ((surfCaps.maxImageCount > 0) && (desiredNumberOfSwapchainImages > surfCaps.maxImageCount)) { + desiredNumberOfSwapchainImages = surfCaps.maxImageCount; + } + + vk::SurfaceTransformFlagBitsKHR preTransform; + if (surfCaps.supportedTransforms & vk::SurfaceTransformFlagBitsKHR::eIdentity) { + preTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity; + } else { + preTransform = surfCaps.currentTransform; + } + + //auto imageFormat = context.physicalDevice.getImageFormatProperties(colorFormat, vk::ImageType::e2D, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eColorAttachment, vk::ImageCreateFlags()); + vk::SwapchainCreateInfoKHR swapchainCI; + swapchainCI.surface = surface; + swapchainCI.minImageCount = desiredNumberOfSwapchainImages; + swapchainCI.imageFormat = colorFormat; + swapchainCI.imageColorSpace = colorSpace; + swapchainCI.imageExtent = extent; + swapchainCI.imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst; + swapchainCI.preTransform = preTransform; + swapchainCI.imageArrayLayers = 1; + swapchainCI.imageSharingMode = vk::SharingMode::eExclusive; + swapchainCI.queueFamilyIndexCount = 0; + swapchainCI.pQueueFamilyIndices = NULL; + swapchainCI.presentMode = swapchainPresentMode; + swapchainCI.oldSwapchain = oldSwapchain; + swapchainCI.clipped = true; + swapchainCI.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; + + swapchain = device.createSwapchainKHR(swapchainCI); + + + // If an existing sawp chain is re-created, destroy the old swap chain + // This also cleans up all the presentable images + if (oldSwapchain) { + for (uint32_t i = 0; i < imageCount; i++) { + device.destroyImageView(images[i].view); + } + device.destroySwapchainKHR(oldSwapchain); + } + + vk::ImageViewCreateInfo colorAttachmentView; + colorAttachmentView.format = colorFormat; + colorAttachmentView.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + colorAttachmentView.subresourceRange.levelCount = 1; + colorAttachmentView.subresourceRange.layerCount = 1; + colorAttachmentView.viewType = vk::ImageViewType::e2D; + + // Get the swap chain images + auto swapChainImages = device.getSwapchainImagesKHR(swapchain); + imageCount = (uint32_t)swapChainImages.size(); + + // Get the swap chain buffers containing the image and imageview + images.resize(imageCount); + for (uint32_t i = 0; i < imageCount; i++) { + images[i].image = swapChainImages[i]; + colorAttachmentView.image = swapChainImages[i]; + images[i].view = device.createImageView(colorAttachmentView); + images[i].fence = vk::Fence(); + } + } + + std::vector createFramebuffers(vk::FramebufferCreateInfo framebufferCreateInfo) { + // Verify that the first attachment is null + assert(framebufferCreateInfo.pAttachments[0] == vk::ImageView()); + + + std::vector attachments; + attachments.resize(framebufferCreateInfo.attachmentCount); + for (size_t i = 0; i < framebufferCreateInfo.attachmentCount; ++i) { + attachments[i] = framebufferCreateInfo.pAttachments[i]; + } + framebufferCreateInfo.pAttachments = attachments.data(); + + std::vector framebuffers; + framebuffers.resize(imageCount); + for (uint32_t i = 0; i < imageCount; i++) { + attachments[0] = images[i].view; + framebuffers[i] = device.createFramebuffer(framebufferCreateInfo); + } + return framebuffers; + } + + // Acquires the next image in the swap chain + vk::ResultValue acquireNextImage(const vk::Semaphore& presentCompleteSemaphore, const vk::Fence& fence = vk::Fence()) { + auto resultValue = device.acquireNextImageKHR(swapchain, UINT64_MAX, presentCompleteSemaphore, fence); + vk::Result result = resultValue.result; + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { + throw std::error_code(result); + } + currentImage = resultValue.value; + return resultValue; + } + + void clearSubmitFence(uint32_t index) { + images[index].fence = vk::Fence(); + } + + vk::Fence getSubmitFence(bool destroy = false) { + auto& image = images[currentImage]; + while (image.fence) { + vk::Result fenceRes = device.waitForFences(image.fence, VK_TRUE, UINT64_MAX); + if (fenceRes == vk::Result::eSuccess) { + if (destroy) { + device.destroyFence(image.fence); + } + image.fence = vk::Fence(); + } + } + + image.fence = device.createFence({}); + return image.fence; + } + + // Present the current image to the queue + vk::Result queuePresent(vk::Semaphore waitSemaphore) { + presentInfo.waitSemaphoreCount = waitSemaphore ? 1 : 0; + presentInfo.pWaitSemaphores = &waitSemaphore; + return queue.presentKHR(presentInfo); + } + + // Free all Vulkan resources used by the swap chain + void destroy() { + for (uint32_t i = 0; i < imageCount; i++) { + device.destroyImageView(images[i].view); + } + device.destroySwapchainKHR(swapchain); + } + + private: + uint32_t findQueue(const vk::QueueFlags& flags) const { + std::vector queueProps = physicalDevice.getQueueFamilyProperties(); + size_t queueCount = queueProps.size(); + for (uint32_t i = 0; i < queueCount; i++) { + if (queueProps[i].queueFlags & flags) { + if (surface && !physicalDevice.getSurfaceSupportKHR(i, surface)) { + continue; + } + return i; + } + } + throw std::runtime_error("No queue matches the flags " + vk::to_string(flags)); + } + }; + + using SwapchainPtr = std::shared_ptr; +} + +#if 0 +#ifdef __ANDROID__ +vk::AndroidSurfaceCreateInfoKHR surfaceCreateInfo; +surfaceCreateInfo.window = window; +surface = context.instance.createAndroidSurfaceKHR(surfaceCreateInfo); +#else +surface = glfw::createWindowSurface(context.instance, window); +#endif +#endif \ No newline at end of file diff --git a/libraries/vk/src/vk/Texture.h b/libraries/vk/src/vk/Texture.h new file mode 100644 index 0000000000..df9059c4f9 --- /dev/null +++ b/libraries/vk/src/vk/Texture.h @@ -0,0 +1,572 @@ +#pragma once + +#include "Config.h" + +#include "Context.h" +#include "Buffer.h" +#include "Image.h" + +namespace vks { namespace texture { + + /** @brief Vulkan texture base class */ + class Texture : public vks::Image { + using Parent = vks::Image; + public: + vk::Device device; + vk::ImageLayout imageLayout; + uint32_t mipLevels; + uint32_t layerCount{ 1 }; + vk::DescriptorImageInfo descriptor; + + Texture& operator=(const vks::Image& image) { + destroy(); + (vks::Image&)*this = image; + return *this; + } + + /** @brief Update image descriptor from current sampler, view and image layout */ + void updateDescriptor() { + descriptor.sampler = sampler; + descriptor.imageView = view; + descriptor.imageLayout = imageLayout; + } + + /** @brief Release all Vulkan resources held by this texture */ + void destroy() { + Parent::destroy(); + } + }; + + /** @brief 2D texture */ + class Texture2D : public Texture { + using Parent = Texture; + public: + /** + * Load a 2D texture including all mip levels + * + * @param filename File to load (supports .ktx and .dds) + * @param format Vulkan format of the image data stored in the file + * @param device Vulkan device to create the texture on + * @param copyQueue Queue used for the texture staging copy commands (must support transfer) + * @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT) + * @param (Optional) imageLayout Usage layout for the texture (defaults VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + * @param (Optional) forceLinear Force linear tiling (not advised, defaults to false) + * + */ + void loadFromFile( + const vks::Context& context, + const std::string& filename, + vk::Format format, + vk::ImageUsageFlags imageUsageFlags = vk::ImageUsageFlagBits::eSampled, + vk::ImageLayout imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + bool forceLinear = false) + { + this->imageLayout = imageLayout; + std::shared_ptr tex2Dptr; + vks::file::withBinaryFileContents(filename, [&](size_t size, const void* data) { + tex2Dptr = std::make_shared(gli::load((const char*)data, size)); + }); + const auto& tex2D = *tex2Dptr; + assert(!tex2D.empty()); + + device = context.device; + extent.width = static_cast(tex2D[0].extent().x); + extent.height = static_cast(tex2D[0].extent().y); + extent.depth = 1; + mipLevels = static_cast(tex2D.levels()); + layerCount = 1; + + // Create optimal tiled target image + vk::ImageCreateInfo imageCreateInfo; + imageCreateInfo.imageType = vk::ImageType::e2D; + imageCreateInfo.format = format; + imageCreateInfo.mipLevels = mipLevels; + imageCreateInfo.arrayLayers = 1; + imageCreateInfo.extent = extent; + imageCreateInfo.usage = imageUsageFlags | vk::ImageUsageFlagBits::eTransferDst; + +#if 1 + ((vks::Image&)(*this)) = context.stageToDeviceImage(imageCreateInfo, vk::MemoryPropertyFlagBits::eDeviceLocal, tex2D); + +#else + ((vks::Image&)*this) = context.createImage(imageCreateInfo); + auto stagingBuffer = context.createBuffer(vk::BufferUsageFlagBits::eTransferSrc, tex2D); + + // Setup buffer copy regions for each layer including all of it's miplevels + std::vector bufferCopyRegions; + size_t offset = 0; + vk::BufferImageCopy bufferCopyRegion; + bufferCopyRegion.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; + bufferCopyRegion.imageSubresource.layerCount = 1; + bufferCopyRegion.imageExtent.depth = 1; + for (uint32_t level = 0; level < mipLevels; level++) { + auto image = tex2D[level]; + auto imageExtent = image.extent(); + bufferCopyRegion.imageSubresource.mipLevel = level; + bufferCopyRegion.imageSubresource.baseArrayLayer = 1; + bufferCopyRegion.imageExtent.width = static_cast(imageExtent.x); + bufferCopyRegion.imageExtent.height = static_cast(imageExtent.y); + bufferCopyRegion.bufferOffset = offset; + bufferCopyRegions.push_back(bufferCopyRegion); + // Increase offset into staging buffer for next level / face + offset += image.size(); + } + + + + vk::ImageSubresourceRange subresourceRange; + subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + subresourceRange.levelCount = mipLevels; + subresourceRange.layerCount = layerCount; + + // Use a separate command buffer for texture loading + context.withPrimaryCommandBuffer([&](const vk::CommandBuffer& copyCmd) { + // Image barrier for optimal image (target) + // Set initial layout for all array layers (faces) of the optimal (target) tiled texture + context.setImageLayout(copyCmd, image, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, subresourceRange); + // Copy the layers and mip levels from the staging buffer to the optimal tiled image + copyCmd.copyBufferToImage(stagingBuffer.buffer, image, vk::ImageLayout::eTransferDstOptimal, bufferCopyRegions); + // Change texture image layout to shader read after all faces have been copied + context.setImageLayout(copyCmd, image, vk::ImageLayout::eTransferDstOptimal, imageLayout, subresourceRange); + }); + + // Clean up staging resources + stagingBuffer.destroy(); +#endif + + + // Create sampler + vk::SamplerCreateInfo samplerCreateInfo; + samplerCreateInfo.magFilter = vk::Filter::eLinear; + samplerCreateInfo.minFilter = vk::Filter::eLinear; + samplerCreateInfo.mipmapMode = vk::SamplerMipmapMode::eLinear; + // Max level-of-detail should match mip level count + samplerCreateInfo.maxLod = (float)mipLevels; + // Only enable anisotropic filtering if enabled on the devicec + samplerCreateInfo.maxAnisotropy = context.deviceFeatures.samplerAnisotropy ? context.deviceProperties.limits.maxSamplerAnisotropy : 1.0f; + samplerCreateInfo.anisotropyEnable = context.deviceFeatures.samplerAnisotropy; + samplerCreateInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite; + sampler = device.createSampler(samplerCreateInfo); + + // Create image view + vk::ImageViewCreateInfo viewCreateInfo; + viewCreateInfo.viewType = vk::ImageViewType::e2D; + viewCreateInfo.image = image; + viewCreateInfo.format = format; + viewCreateInfo.subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, layerCount }; + view = context.device.createImageView(viewCreateInfo); + + // Update descriptor image info member that can be used for setting up descriptor sets + updateDescriptor(); + } + +#if 0 + /** + * Creates a 2D texture from a buffer + * + * @param buffer Buffer containing texture data to upload + * @param bufferSize Size of the buffer in machine units + * @param width Width of the texture to create + * @param height Height of the texture to create + * @param format Vulkan format of the image data stored in the file + * @param device Vulkan device to create the texture on + * @param copyQueue Queue used for the texture staging copy commands (must support transfer) + * @param (Optional) filter Texture filtering for the sampler (defaults to VK_FILTER_LINEAR) + * @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT) + * @param (Optional) imageLayout Usage layout for the texture (defaults VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + */ + void fromBuffer( + void* buffer, + VkDeviceSize bufferSize, + VkFormat format, + uint32_t width, + uint32_t height, + VkQueue copyQueue, + VkFilter filter = VK_FILTER_LINEAR, + VkImageUsageFlags imageUsageFlags = VK_IMAGE_USAGE_SAMPLED_BIT, + VkImageLayout imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + { + assert(buffer); + + this->device = device; + width = width; + height = height; + mipLevels = 1; + + VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); + VkMemoryRequirements memReqs; + + // Use a separate command buffer for texture loading + VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + + // Create a host-visible staging buffer that contains the raw image data + VkBuffer stagingBuffer; + VkDeviceMemory stagingMemory; + + VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(); + bufferCreateInfo.size = bufferSize; + // This buffer is used as a transfer source for the buffer copy + bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer)); + + // Get memory requirements for the staging buffer (alignment, memory type bits) + vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs); + + memAllocInfo.allocationSize = memReqs.size; + // Get memory type index for a host visible buffer + memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory)); + VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0)); + + // Copy texture data into staging buffer + uint8_t *data; + VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void **)&data)); + memcpy(data, buffer, bufferSize); + vkUnmapMemory(device->logicalDevice, stagingMemory); + + VkBufferImageCopy bufferCopyRegion = {}; + bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + bufferCopyRegion.imageSubresource.mipLevel = 0; + bufferCopyRegion.imageSubresource.baseArrayLayer = 0; + bufferCopyRegion.imageSubresource.layerCount = 1; + bufferCopyRegion.imageExtent.width = width; + bufferCopyRegion.imageExtent.height = height; + bufferCopyRegion.imageExtent.depth = 1; + bufferCopyRegion.bufferOffset = 0; + + // Create optimal tiled target image + VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); + imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imageCreateInfo.format = format; + imageCreateInfo.mipLevels = mipLevels; + imageCreateInfo.arrayLayers = 1; + imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageCreateInfo.extent = { width, height, 1 }; + imageCreateInfo.usage = imageUsageFlags; + // Ensure that the TRANSFER_DST bit is set for staging + if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT)) + { + imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + } + VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image)); + + vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs); + + memAllocInfo.allocationSize = memReqs.size; + + memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory)); + VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0)); + + VkImageSubresourceRange subresourceRange = {}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.baseMipLevel = 0; + subresourceRange.levelCount = mipLevels; + subresourceRange.layerCount = 1; + + // Image barrier for optimal image (target) + // Optimal image will be used as destination for the copy + vks::tools::setImageLayout( + copyCmd, + image, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + subresourceRange); + + // Copy mip levels from staging buffer + vkCmdCopyBufferToImage( + copyCmd, + stagingBuffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &bufferCopyRegion + ); + + // Change texture image layout to shader read after all mip levels have been copied + this->imageLayout = imageLayout; + vks::tools::setImageLayout( + copyCmd, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + imageLayout, + subresourceRange); + + device->flushCommandBuffer(copyCmd, copyQueue); + + // Clean up staging resources + vkFreeMemory(device->logicalDevice, stagingMemory, nullptr); + vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr); + + // Create sampler + VkSamplerCreateInfo samplerCreateInfo = {}; + samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerCreateInfo.magFilter = filter; + samplerCreateInfo.minFilter = filter; + samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCreateInfo.mipLodBias = 0.0f; + samplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER; + samplerCreateInfo.minLod = 0.0f; + samplerCreateInfo.maxLod = 0.0f; + samplerCreateInfo.maxAnisotropy = 1.0f; + VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &sampler)); + + // Create image view + VkImageViewCreateInfo viewCreateInfo = {}; + viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewCreateInfo.pNext = NULL; + viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewCreateInfo.format = format; + viewCreateInfo.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; + viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + viewCreateInfo.subresourceRange.levelCount = 1; + viewCreateInfo.image = image; + VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &view)); + + // Update descriptor image info member that can be used for setting up descriptor sets + updateDescriptor(); + } +#endif + }; + + /** @brief 2D array texture */ + class Texture2DArray : public Texture { + public: + /** + * Load a 2D texture array including all mip levels + * + * @param filename File to load (supports .ktx and .dds) + * @param format Vulkan format of the image data stored in the file + * @param device Vulkan device to create the texture on + * @param copyQueue Queue used for the texture staging copy commands (must support transfer) + * @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT) + * @param (Optional) imageLayout Usage layout for the texture (defaults VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + * + */ + void loadFromFile( + const vks::Context& context, + std::string filename, + vk::Format format, + vk::ImageUsageFlags imageUsageFlags = vk::ImageUsageFlagBits::eSampled, + vk::ImageLayout imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal) { + this->device = device; + + std::shared_ptr texPtr; + vks::file::withBinaryFileContents(filename, [&](size_t size, const void* data) { + texPtr = std::make_shared(gli::load((const char*)data, size)); + }); + + const gli::texture2d_array& tex2DArray = *texPtr; + + extent.width = static_cast(tex2DArray.extent().x); + extent.height = static_cast(tex2DArray.extent().y); + extent.depth = 1; + layerCount = static_cast(tex2DArray.layers()); + mipLevels = static_cast(tex2DArray.levels()); + + auto stagingBuffer = context.createStagingBuffer(tex2DArray); + + // Setup buffer copy regions for each layer including all of it's miplevels + std::vector bufferCopyRegions; + size_t offset = 0; + vk::BufferImageCopy bufferCopyRegion; + bufferCopyRegion.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; + bufferCopyRegion.imageSubresource.layerCount = 1; + bufferCopyRegion.imageExtent.depth = 1; + for (uint32_t layer = 0; layer < layerCount; layer++) { + for (uint32_t level = 0; level < mipLevels; level++) { + auto image = tex2DArray[layer][level]; + auto imageExtent = image.extent(); + bufferCopyRegion.imageSubresource.mipLevel = level; + bufferCopyRegion.imageSubresource.baseArrayLayer = layer; + bufferCopyRegion.imageExtent.width = static_cast(imageExtent.x); + bufferCopyRegion.imageExtent.height = static_cast(imageExtent.y); + bufferCopyRegion.bufferOffset = offset; + bufferCopyRegions.push_back(bufferCopyRegion); + // Increase offset into staging buffer for next level / face + offset += image.size(); + } + } + + + // Create optimal tiled target image + vk::ImageCreateInfo imageCreateInfo; + imageCreateInfo.imageType = vk::ImageType::e2D; + imageCreateInfo.format = format; + imageCreateInfo.extent = extent; + imageCreateInfo.usage = imageUsageFlags | vk::ImageUsageFlagBits::eTransferDst; + imageCreateInfo.arrayLayers = layerCount; + imageCreateInfo.mipLevels = mipLevels; + ((vks::Image&)*this) = context.createImage(imageCreateInfo); + + vk::ImageSubresourceRange subresourceRange; + subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + subresourceRange.levelCount = mipLevels; + subresourceRange.layerCount = layerCount; + + // Use a separate command buffer for texture loading + context.withPrimaryCommandBuffer([&](const vk::CommandBuffer& copyCmd) { + // Image barrier for optimal image (target) + // Set initial layout for all array layers (faces) of the optimal (target) tiled texture + context.setImageLayout(copyCmd, image, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, subresourceRange); + // Copy the layers and mip levels from the staging buffer to the optimal tiled image + copyCmd.copyBufferToImage(stagingBuffer.buffer, image, vk::ImageLayout::eTransferDstOptimal, bufferCopyRegions); + // Change texture image layout to shader read after all faces have been copied + context.setImageLayout(copyCmd, image, vk::ImageLayout::eTransferDstOptimal, imageLayout, subresourceRange); + }); + + // Clean up staging resources + stagingBuffer.destroy(); + + // Create sampler + vk::SamplerCreateInfo samplerCreateInfo; + samplerCreateInfo.magFilter = vk::Filter::eLinear; + samplerCreateInfo.minFilter = vk::Filter::eLinear; + samplerCreateInfo.mipmapMode = vk::SamplerMipmapMode::eLinear; + samplerCreateInfo.addressModeU = vk::SamplerAddressMode::eClampToEdge; + samplerCreateInfo.addressModeV = vk::SamplerAddressMode::eClampToEdge; + samplerCreateInfo.addressModeW = vk::SamplerAddressMode::eClampToEdge; + samplerCreateInfo.maxAnisotropy = context.deviceFeatures.samplerAnisotropy ? context.deviceProperties.limits.maxSamplerAnisotropy : 1.0f; + samplerCreateInfo.maxLod = (float)mipLevels; + samplerCreateInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite; + sampler = context.device.createSampler(samplerCreateInfo); + + // Create image view + vk::ImageViewCreateInfo viewCreateInfo; + viewCreateInfo.viewType = vk::ImageViewType::e2DArray; + viewCreateInfo.image = image; + viewCreateInfo.format = format; + viewCreateInfo.subresourceRange = vk::ImageSubresourceRange{ vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, layerCount }; + view = context.device.createImageView(viewCreateInfo); + + // Update descriptor image info member that can be used for setting up descriptor sets + updateDescriptor(); + } + }; + + /** @brief Cube map texture */ + class TextureCubeMap : public Texture { + public: + /** + * Load a cubemap texture including all mip levels from a single file + * + * @param filename File to load (supports .ktx and .dds) + * @param format Vulkan format of the image data stored in the file + * @param device Vulkan device to create the texture on + * @param copyQueue Queue used for the texture staging copy commands (must support transfer) + * @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT) + * @param (Optional) imageLayout Usage layout for the texture (defaults VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + * + */ + void loadFromFile( + const vks::Context& context, + const std::string& filename, + vk::Format format, + vk::ImageUsageFlags imageUsageFlags = vk::ImageUsageFlagBits::eSampled, + vk::ImageLayout imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal) { + device = context.device; + + std::shared_ptr texPtr; + vks::file::withBinaryFileContents(filename, [&](size_t size, const void* data) { + texPtr = std::make_shared(gli::load((const char*)data, size)); + }); + const auto& texCube = *texPtr; + assert(!texCube.empty()); + + + extent.width = static_cast(texCube.extent().x); + extent.height = static_cast(texCube.extent().y); + extent.depth = 1; + mipLevels = static_cast(texCube.levels()); + auto stagingBuffer = context.createStagingBuffer(texCube); + + // Setup buffer copy regions for each face including all of it's miplevels + std::vector bufferCopyRegions; + size_t offset = 0; + vk::BufferImageCopy bufferImageCopy; + bufferImageCopy.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; + bufferImageCopy.imageSubresource.layerCount = 1; + bufferImageCopy.imageExtent.depth = 1; + for (uint32_t face = 0; face < 6; face++) { + for (uint32_t level = 0; level < mipLevels; level++) { + auto image = (texCube)[face][level]; + auto imageExtent = image.extent(); + bufferImageCopy.bufferOffset = offset; + bufferImageCopy.imageSubresource.mipLevel = level; + bufferImageCopy.imageSubresource.baseArrayLayer = face; + bufferImageCopy.imageExtent.width = (uint32_t)imageExtent.x; + bufferImageCopy.imageExtent.height = (uint32_t)imageExtent.y; + bufferCopyRegions.push_back(bufferImageCopy); + // Increase offset into staging buffer for next level / face + offset += image.size(); + } + } + + // Create optimal tiled target image + vk::ImageCreateInfo imageCreateInfo; + imageCreateInfo.imageType = vk::ImageType::e2D; + imageCreateInfo.format = format; + imageCreateInfo.mipLevels = mipLevels; + imageCreateInfo.extent = extent; + // Cube faces count as array layers in Vulkan + imageCreateInfo.arrayLayers = 6; + // Ensure that the TRANSFER_DST bit is set for staging + imageCreateInfo.usage = imageUsageFlags | vk::ImageUsageFlagBits::eTransferDst; + // This flag is required for cube map images + imageCreateInfo.flags = vk::ImageCreateFlagBits::eCubeCompatible; + ((vks::Image&)*this) = context.createImage(imageCreateInfo); + + context.withPrimaryCommandBuffer([&](const vk::CommandBuffer& copyCmd) { + // Image barrier for optimal image (target) + // Set initial layout for all array layers (faces) of the optimal (target) tiled texture + vk::ImageSubresourceRange subresourceRange { vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 6 }; + context.setImageLayout(copyCmd, image, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, subresourceRange); + // Copy the cube map faces from the staging buffer to the optimal tiled image + copyCmd.copyBufferToImage(stagingBuffer.buffer, image, vk::ImageLayout::eTransferDstOptimal, bufferCopyRegions); + // Change texture image layout to shader read after all faces have been copied + this->imageLayout = imageLayout; + context.setImageLayout(copyCmd, image, vk::ImageLayout::eTransferDstOptimal, imageLayout, subresourceRange); + }); + + + // Create sampler + // Create a defaultsampler + vk::SamplerCreateInfo samplerCreateInfo; + samplerCreateInfo.magFilter = vk::Filter::eLinear; + samplerCreateInfo.minFilter = vk::Filter::eLinear; + samplerCreateInfo.mipmapMode = vk::SamplerMipmapMode::eLinear; + samplerCreateInfo.addressModeU = vk::SamplerAddressMode::eClampToEdge; + samplerCreateInfo.addressModeV = vk::SamplerAddressMode::eClampToEdge; + samplerCreateInfo.addressModeW = vk::SamplerAddressMode::eClampToEdge; + // Max level-of-detail should match mip level count + samplerCreateInfo.maxLod = (float)mipLevels; + // Only enable anisotropic filtering if enabled on the devicec + samplerCreateInfo.maxAnisotropy = context.deviceFeatures.samplerAnisotropy ? context.deviceProperties.limits.maxSamplerAnisotropy : 1.0f; + samplerCreateInfo.anisotropyEnable = context.deviceFeatures.samplerAnisotropy; + samplerCreateInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite; + sampler = device.createSampler(samplerCreateInfo); + + // Create image view + // Textures are not directly accessed by the shaders and + // are abstracted by image views containing additional + // information and sub resource ranges + view = device.createImageView(vk::ImageViewCreateInfo{ + {}, image, vk::ImageViewType::eCube, format,{}, + vk::ImageSubresourceRange{ vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 6 } + }); + stagingBuffer.destroy(); + + // Update descriptor image info member that can be used for setting up descriptor sets + updateDescriptor(); + } + }; +} } diff --git a/libraries/vk/src/vk/VKWindow.cpp b/libraries/vk/src/vk/VKWindow.cpp new file mode 100644 index 0000000000..4debe0ef2c --- /dev/null +++ b/libraries/vk/src/vk/VKWindow.cpp @@ -0,0 +1,197 @@ +// +// Created by Bradley Austin Davis on 2016/05/26 +// Copyright 2013-2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "VKWindow.h" +#include +#include + +#include "Config.h" +#include "Swapchain.h" +#include "Context.h" + +VKWindow::VKWindow(QScreen* screen) : QWindow(screen) { + _resizeTimer = new QTimer(this); + _resizeTimer->setTimerType(Qt::TimerType::PreciseTimer); + _resizeTimer->setInterval(50); + _resizeTimer->setSingleShot(true); + connect(_resizeTimer, &QTimer::timeout, this, &VKWindow::resizeFramebuffer); +} + +const vk::SurfaceKHR& VKWindow::createSurface() { + _surface = _context.instance.createWin32SurfaceKHR({ {}, GetModuleHandle(NULL), (HWND)winId() }); + _swapchain.setSurface(_surface); + return _surface; +} + +void VKWindow::createSwapchain() { + if (!_surface) { + throw std::runtime_error("No surface"); + } + + { + auto qsize = size(); + _extent = { (uint32_t)qsize.width(), (uint32_t)qsize.height() }; + } + _swapchain.create(_extent, true); + + setupRenderPass(); + setupDepthStencil(); + setupFramebuffers(); +} + +void VKWindow::setupDepthStencil() { + if (_depthStencil) { + _depthStencil.destroy(); + _depthStencil = {}; + } + + vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil; + vk::ImageCreateInfo depthStencilCreateInfo; + depthStencilCreateInfo.imageType = vk::ImageType::e2D; + depthStencilCreateInfo.extent = vk::Extent3D{ _extent.width, _extent.height, 1 }; + depthStencilCreateInfo.format = vk::Format::eD24UnormS8Uint; + depthStencilCreateInfo.mipLevels = 1; + depthStencilCreateInfo.arrayLayers = 1; + depthStencilCreateInfo.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransferSrc; + _depthStencil = _context.createImage(depthStencilCreateInfo, vk::MemoryPropertyFlagBits::eDeviceLocal); + + vk::ImageViewCreateInfo depthStencilView; + depthStencilView.viewType = vk::ImageViewType::e2D; + depthStencilView.format = vk::Format::eD24UnormS8Uint; + depthStencilView.subresourceRange.aspectMask = aspect; + depthStencilView.subresourceRange.levelCount = 1; + depthStencilView.subresourceRange.layerCount = 1; + depthStencilView.image = _depthStencil.image; + _depthStencil.view = _device.createImageView(depthStencilView); +} + +void VKWindow::setupFramebuffers() { + // Recreate the frame buffers + if (!_framebuffers.empty()) { + for (auto& framebuffer : _framebuffers) { + _device.destroy(framebuffer); + } + _framebuffers.clear(); + } + + vk::ImageView attachments[2]; + + // Depth/Stencil attachment is the same for all frame buffers + attachments[1] = _depthStencil.view; + + vk::FramebufferCreateInfo framebufferCreateInfo; + framebufferCreateInfo.renderPass = _renderPass; + framebufferCreateInfo.attachmentCount = 2; + framebufferCreateInfo.pAttachments = attachments; + framebufferCreateInfo.width = _extent.width; + framebufferCreateInfo.height = _extent.height; + framebufferCreateInfo.layers = 1; + + // Create frame buffers for every swap chain image + _framebuffers = _swapchain.createFramebuffers(framebufferCreateInfo); + +} + +void VKWindow::setupRenderPass() { + if (_renderPass) { + _device.destroy(_renderPass); + } + + std::array attachments; + // Color attachment + attachments[0].format = _swapchain.colorFormat; + attachments[0].loadOp = vk::AttachmentLoadOp::eClear; + attachments[0].storeOp = vk::AttachmentStoreOp::eStore; + attachments[0].initialLayout = vk::ImageLayout::eUndefined; + attachments[0].finalLayout = vk::ImageLayout::ePresentSrcKHR; + + // Depth attachment + attachments[1].format = vk::Format::eD24UnormS8Uint; + attachments[1].loadOp = vk::AttachmentLoadOp::eClear; + attachments[1].storeOp = vk::AttachmentStoreOp::eDontCare; + attachments[1].stencilLoadOp = vk::AttachmentLoadOp::eClear; + attachments[1].stencilStoreOp = vk::AttachmentStoreOp::eDontCare; + attachments[1].initialLayout = vk::ImageLayout::eUndefined; + attachments[1].finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; + + // Only one depth attachment, so put it first in the references + vk::AttachmentReference depthReference; + depthReference.attachment = 1; + depthReference.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal; + + std::vector colorAttachmentReferences; + { + vk::AttachmentReference colorReference; + colorReference.attachment = 0; + colorReference.layout = vk::ImageLayout::eColorAttachmentOptimal; + colorAttachmentReferences.push_back(colorReference); + } + + std::vector subpasses; + std::vector subpassDependencies; + { + { + vk::SubpassDependency dependency; + dependency.srcSubpass = 0; + dependency.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + dependency.dstSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead; + dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + subpassDependencies.push_back(dependency); + } + + vk::SubpassDescription subpass; + subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; + subpass.pDepthStencilAttachment = &depthReference; + subpass.colorAttachmentCount = (uint32_t)colorAttachmentReferences.size(); + subpass.pColorAttachments = colorAttachmentReferences.data(); + subpasses.push_back(subpass); + } + + vk::RenderPassCreateInfo renderPassInfo; + renderPassInfo.attachmentCount = (uint32_t)attachments.size(); + renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.subpassCount = (uint32_t)subpasses.size(); + renderPassInfo.pSubpasses = subpasses.data(); + renderPassInfo.dependencyCount = (uint32_t)subpassDependencies.size(); + renderPassInfo.pDependencies = subpassDependencies.data(); + _renderPass = _device.createRenderPass(renderPassInfo); +} + + +void VKWindow::resizeEvent(QResizeEvent* event) { + QWindow::resizeEvent(event); + auto qsize = event->size(); + if (qsize.width() != _extent.width || qsize.height() != _extent.height) { + _resizeTimer->start(); + } +} + +void VKWindow::resizeFramebuffer() { + auto qsize = size(); + _extent = { (uint32_t)qsize.width(), (uint32_t)qsize.height() }; + _swapchain.waitIdle(); + _swapchain.create(_extent, true); + setupDepthStencil(); + setupFramebuffers(); +} + +VKWindow::~VKWindow() { + _swapchain.destroy(); +} + +void VKWindow::emitClosing() { + emit aboutToClose(); +} + +vk::Framebuffer VKWindow::acquireFramebuffer(const vk::Semaphore& semaphore) { + auto result = _swapchain.acquireNextImage(semaphore); + auto imageIndex = result.value; + return _framebuffers[imageIndex]; +} diff --git a/libraries/vk/src/vk/VKWindow.h b/libraries/vk/src/vk/VKWindow.h new file mode 100644 index 0000000000..3e6a1b0d3e --- /dev/null +++ b/libraries/vk/src/vk/VKWindow.h @@ -0,0 +1,57 @@ +// +// Created by Bradley Austin Davis on 2016/03/19 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#include + +#include "Config.h" +#include "Context.h" +#include "Swapchain.h" + +class VKWindow : public QWindow { + Q_OBJECT +public: + VKWindow(QScreen* screen = nullptr); + virtual ~VKWindow(); + + void createSwapchain(); + void queuePresent(const vk::ArrayProxy& waitSemaphores); + const vk::SurfaceKHR& createSurface(); + const vk::SurfaceKHR& getSurface() { return _surface; } + vks::Swapchain& getSwapchain() { return _swapchain; } + vk::Framebuffer acquireFramebuffer(const vk::Semaphore& semaphore); + +signals: + void aboutToClose(); + +protected: + friend class VkCloseEventFilter; + void emitClosing(); + +protected slots: + virtual void resizeFramebuffer(); + +protected: + void resizeEvent(QResizeEvent* event) override; + + void setupRenderPass(); + void setupDepthStencil(); + void setupFramebuffers(); + +public: + vks::Context& _context{ vks::Context::get() }; + const vk::Device& _device{ _context.device }; + vk::SurfaceKHR _surface; + vk::RenderPass _renderPass; + vk::Extent2D _extent; + vks::Swapchain _swapchain; + vks::Image _depthStencil; + std::vector _framebuffers; + QTimer* _resizeTimer{ nullptr }; +}; diff --git a/libraries/vk/src/vk/Version.h b/libraries/vk/src/vk/Version.h new file mode 100644 index 0000000000..66b5839d75 --- /dev/null +++ b/libraries/vk/src/vk/Version.h @@ -0,0 +1,49 @@ +// +// Created by Bradley Austin Davis on 2016/03/19 +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#include +#include +#include + +namespace vks { + + // Version information for Vulkan is stored in a single 32 bit integer + // with individual bits representing the major, minor and patch versions. + // The maximum possible major and minor version is 512 (look out nVidia) + // while the maximum possible patch version is 2048 + struct Version { + Version() : vulkan_major(0), vulkan_minor(0), vulkan_patch(0) { + } + Version(uint32_t version) : Version() { + *this = version; + } + + Version& operator =(uint32_t version) { + memcpy(this, &version, sizeof(uint32_t)); + return *this; + } + + operator uint32_t() const { + uint32_t result; + memcpy(&result, this, sizeof(uint32_t)); + return result; + } + + std::string toString() const { + std::stringstream buffer; + buffer << vulkan_major << "." << vulkan_minor << "." << vulkan_patch; + return buffer.str(); + } + + const uint32_t vulkan_patch : 12; + const uint32_t vulkan_minor : 10; + const uint32_t vulkan_major : 10; + + }; +} diff --git a/libraries/vk/src/vk/VmaImplementation.cpp b/libraries/vk/src/vk/VmaImplementation.cpp new file mode 100644 index 0000000000..61797a3d7b --- /dev/null +++ b/libraries/vk/src/vk/VmaImplementation.cpp @@ -0,0 +1,2 @@ +#define VMA_IMPLEMENTATION +#include