/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2019-2025 Baldur Karlsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ #include "../test_common.h" std::string VKFullscreenQuadVertex = R"EOSHADER( #version 460 core void main() { vec2 positions[] = { vec2(-1.0f, 1.0f), vec2( 1.0f, 1.0f), vec2(-1.0f, -1.0f), vec2( 1.0f, -1.0f), }; gl_Position = vec4(positions[gl_VertexIndex], 0, 1); } )EOSHADER"; static std::string common = R"EOSHADER( #version 460 core #define v2f v2f_block \ { \ vec4 pos; \ vec4 col; \ vec4 uv; \ } )EOSHADER"; std::string VKDefaultVertex = common + R"EOSHADER( layout(location = 0) in vec3 Position; layout(location = 1) in vec4 Color; layout(location = 2) in vec2 UV; layout(location = 0) out v2f vertOut; void main() { vertOut.pos = vec4(Position.xyz*vec3(1,-1,1), 1); gl_Position = vertOut.pos; vertOut.col = Color; vertOut.uv = vec4(UV.xy, 0, 1); } )EOSHADER"; std::string VKDefaultPixel = common + R"EOSHADER( layout(location = 0) in v2f vertIn; layout(location = 0, index = 0) out vec4 Color; void main() { Color = vertIn.col; } )EOSHADER"; #define VMA_IMPLEMENTATION #define VMA_STATIC_VULKAN_FUNCTIONS 0 #define VMA_ASSERT(expr) TEST_ASSERT(expr, "VMA assertion failed"); #pragma warning(push) #pragma warning(disable : 4127) #pragma warning(disable : 4189) #pragma warning(disable : 4324) #pragma warning(disable : 4505) #include "vk_headers.h" #pragma warning(pop) #include "vk_test.h" #if defined(WIN32) #include "../win32/win32_window.h" #elif defined(ANDROID) #include "../android/android_window.h" #elif defined(__linux__) #include "../linux/linux_window.h" #elif defined(__APPLE__) #include "../apple/apple_window.h" #else #error UNKNOWN PLATFORM #endif static VkBool32 VKAPI_PTR vulkanCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, void *pUserData) { if(strstr(pCallbackData->pMessageIdName, "-01779")) return false; TEST_WARN("Vulkan message: [%s] %s", pCallbackData->pMessageIdName, pCallbackData->pMessage); return false; } VulkanGraphicsTest::VulkanGraphicsTest() { } namespace { bool volk = false; bool spv = false; uint32_t vulkanVersion = 0; VkInstance inst = VK_NULL_HANDLE; VkPhysicalDevice selectedPhys = VK_NULL_HANDLE; std::vector enabledInstExts; std::vector enabledLayers; }; void VulkanGraphicsTest::Prepare(int argc, char **argv) { GraphicsTest::Prepare(argc, argv); static bool prepared = false; std::vector availInstLayers; std::vector availInstExts; if(!prepared) { prepared = true; volk = (volkInitialize() == VK_SUCCESS); spv = SpvCompilationSupported(); if(volk && spv) { enabledInstExts = instExts; enabledLayers = instLayers; enabledInstExts.push_back(VK_KHR_SURFACE_EXTENSION_NAME); #if defined(WIN32) enabledInstExts.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); #elif defined(ANDROID) enabledInstExts.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); #elif defined(__linux__) enabledInstExts.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); X11Window::Init(); #elif defined(__APPLE__) enabledInstExts.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME); AppleWindow::Init(); #else #error UNKNOWN PLATFORM #endif std::vector optInstExts; // this is used by so many sub extensions, initialise it if we can. optInstExts.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); // enable debug utils when possible optInstExts.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); // ditto validation features optInstExts.push_back(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME); CHECK_VKR(vkh::enumerateInstanceLayerProperties(availInstLayers)); if(debugDevice) { bool found = false; for(const VkLayerProperties &layer : availInstLayers) { if(!strcmp(layer.layerName, "VK_LAYER_KHRONOS_validation")) { enabledLayers.push_back("VK_LAYER_KHRONOS_validation"); found = true; break; } } if(!found) { for(const VkLayerProperties &layer : availInstLayers) { if(!strcmp(layer.layerName, "VK_LAYER_LUNARG_standard_validation")) { enabledLayers.push_back("VK_LAYER_LUNARG_standard_validation"); found = true; break; } } } } CHECK_VKR(vkh::enumerateInstanceExtensionProperties(availInstExts, NULL)); for(const char *l : enabledLayers) { bool supported = false; for(const VkLayerProperties &layer : availInstLayers) { if(!strcmp(layer.layerName, l)) { supported = true; break; } } if(!supported) { Avail = "Vulkan layer '"; Avail += l; Avail += "' is not available"; return; } std::vector tmp; CHECK_VKR(vkh::enumerateInstanceExtensionProperties(tmp, l)); for(const VkExtensionProperties &t : tmp) availInstExts.push_back(t); } // strip any extensions that are not supported for(auto it = enabledInstExts.begin(); it != enabledInstExts.end();) { bool found = false; for(VkExtensionProperties &ext : availInstExts) { if(!strcmp(ext.extensionName, *it)) { found = true; break; } } if(found) { ++it; } else { DEBUG_BREAK(); it = enabledInstExts.erase(it); } } // add any optional extensions that are supported for(const char *search : optInstExts) { bool found = false; for(VkExtensionProperties &ext : availInstExts) { if(!strcmp(ext.extensionName, search)) { found = true; break; } } if(found) enabledInstExts.push_back(search); } VkValidationFeaturesEXT featuresEXT = {VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT}; VkValidationFeatureEnableEXT enableFeatures[] = { VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT, VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT, }; featuresEXT.enabledValidationFeatureCount = ARRAY_COUNT(enableFeatures); featuresEXT.pEnabledValidationFeatures = enableFeatures; // allow command line override for(int i = 0; i < argc; i++) { if(!strcmp(argv[i], "--gpuva")) { for(const char *a : enabledInstExts) { if(std::string(a) == VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME) { featuresEXT.pNext = instInfoNext; instInfoNext = &featuresEXT; } } } } vulkanVersion = volkGetInstanceVersion(); vkh::ApplicationInfo app("RenderDoc autotesting", VK_MAKE_VERSION(1, 0, 0), "RenderDoc autotesting", VK_MAKE_VERSION(1, 0, 0), vulkanVersion); TEST_LOG("Initialising Vulkan at VK%u.%u", VK_VERSION_MAJOR(vulkanVersion), VK_VERSION_MINOR(vulkanVersion)); VkResult vkr = vkCreateInstance( vkh::InstanceCreateInfo(app, enabledLayers, enabledInstExts).next(instInfoNext), NULL, &inst); if(vkr != VK_SUCCESS) { TEST_ERROR("Error initialising vulkan instance: %d", vkr); } else { volkLoadInstance((VkInstance)inst); std::vector physDevices; CHECK_VKR(vkh::enumeratePhysicalDevices(physDevices, inst)); std::vector physProps; for(VkPhysicalDevice p : physDevices) { VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(p, &props); physProps.push_back(props); } // default to the first discrete card for(size_t i = 0; i < physDevices.size(); i++) { if(physProps[i].deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { selectedPhys = physDevices[i]; break; } } // if none found, default to first if(selectedPhys == VK_NULL_HANDLE && !physDevices.empty()) selectedPhys = physDevices[0]; // allow command line override for(int i = 0; i < argc; i++) { if(!strcmp(argv[i], "--gpu") && i + 1 < argc) { std::string needle = strlower(argv[i + 1]); const bool nv = (needle == "nv" || needle == "nvidia"); const bool amd = (needle == "amd"); const bool intel = (needle == "intel"); for(size_t p = 0; p < physDevices.size(); p++) { std::string haystack = strlower(physProps[p].deviceName); if(haystack.find(needle) != std::string::npos || (nv && physProps[p].vendorID == PCI_VENDOR_NV) || (amd && physProps[p].vendorID == PCI_VENDOR_AMD) || (intel && physProps[p].vendorID == PCI_VENDOR_INTEL)) { selectedPhys = physDevices[p]; break; } } break; } } } } } instance = inst; phys = selectedPhys; if(!volk) Avail = "volk did not initialise - vulkan library is not available"; else if(!spv) Avail = InternalSpvCompiler() ? "Internal SPIR-V compiler did not initialise" : "Couldn't find 'glslc' or 'glslangValidator' in PATH - " "required for SPIR-V compilation"; else if(instance == VK_NULL_HANDLE) Avail = "Vulkan instance did not initialise"; else if(phys == VK_NULL_HANDLE) Avail = "Couldn't find vulkan physical device"; if(!Avail.empty()) return; devExts.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); VkPhysicalDeviceFeatures supported; vkGetPhysicalDeviceFeatures(phys, &supported); #define CHECK_FEATURE(a) \ if(features.a && !supported.a) \ { \ Avail = "Required physical device feature '" #a "' is not supported"; \ return; \ } \ if(optFeatures.a && supported.a) \ { \ features.a = VK_TRUE; \ } CHECK_FEATURE(robustBufferAccess); CHECK_FEATURE(fullDrawIndexUint32); CHECK_FEATURE(imageCubeArray); CHECK_FEATURE(independentBlend); CHECK_FEATURE(geometryShader); CHECK_FEATURE(tessellationShader); CHECK_FEATURE(sampleRateShading); CHECK_FEATURE(dualSrcBlend); CHECK_FEATURE(logicOp); CHECK_FEATURE(multiDrawIndirect); CHECK_FEATURE(drawIndirectFirstInstance); CHECK_FEATURE(depthClamp); CHECK_FEATURE(depthBiasClamp); CHECK_FEATURE(fillModeNonSolid); CHECK_FEATURE(depthBounds); CHECK_FEATURE(wideLines); CHECK_FEATURE(largePoints); CHECK_FEATURE(alphaToOne); CHECK_FEATURE(multiViewport); CHECK_FEATURE(samplerAnisotropy); CHECK_FEATURE(textureCompressionETC2); CHECK_FEATURE(textureCompressionASTC_LDR); CHECK_FEATURE(textureCompressionBC); CHECK_FEATURE(occlusionQueryPrecise); CHECK_FEATURE(pipelineStatisticsQuery); CHECK_FEATURE(vertexPipelineStoresAndAtomics); CHECK_FEATURE(fragmentStoresAndAtomics); CHECK_FEATURE(shaderTessellationAndGeometryPointSize); CHECK_FEATURE(shaderImageGatherExtended); CHECK_FEATURE(shaderStorageImageExtendedFormats); CHECK_FEATURE(shaderStorageImageMultisample); CHECK_FEATURE(shaderStorageImageReadWithoutFormat); CHECK_FEATURE(shaderStorageImageWriteWithoutFormat); CHECK_FEATURE(shaderUniformBufferArrayDynamicIndexing); CHECK_FEATURE(shaderSampledImageArrayDynamicIndexing); CHECK_FEATURE(shaderStorageBufferArrayDynamicIndexing); CHECK_FEATURE(shaderStorageImageArrayDynamicIndexing); CHECK_FEATURE(shaderClipDistance); CHECK_FEATURE(shaderCullDistance); CHECK_FEATURE(shaderFloat64); CHECK_FEATURE(shaderInt64); CHECK_FEATURE(shaderInt16); CHECK_FEATURE(shaderResourceResidency); CHECK_FEATURE(shaderResourceMinLod); CHECK_FEATURE(sparseBinding); CHECK_FEATURE(sparseResidencyBuffer); CHECK_FEATURE(sparseResidencyImage2D); CHECK_FEATURE(sparseResidencyImage3D); CHECK_FEATURE(sparseResidency2Samples); CHECK_FEATURE(sparseResidency4Samples); CHECK_FEATURE(sparseResidency8Samples); CHECK_FEATURE(sparseResidency16Samples); CHECK_FEATURE(sparseResidencyAliased); CHECK_FEATURE(variableMultisampleRate); CHECK_FEATURE(inheritedQueries); CHECK_VKR(vkh::enumerateInstanceLayerProperties(availInstLayers)); CHECK_VKR(vkh::enumerateInstanceExtensionProperties(availInstExts, NULL)); instExts = enabledInstExts; instLayers = enabledLayers; for(const char *l : instLayers) { bool layerSupported = false; for(const VkLayerProperties &layer : availInstLayers) { if(!strcmp(layer.layerName, l)) { layerSupported = true; break; } } if(!layerSupported) { Avail = "Vulkan layer '"; Avail += l; Avail += "' is not available"; return; } std::vector tmp; CHECK_VKR(vkh::enumerateInstanceExtensionProperties(tmp, l)); for(const VkExtensionProperties &t : tmp) availInstExts.push_back(t); } for(const char *search : instExts) { bool extSupported = false; for(const VkExtensionProperties &e : availInstExts) { if(!strcmp(e.extensionName, search)) { extSupported = true; break; } } if(!extSupported) { Avail = "instance extension '"; Avail += search; Avail += "' is not available"; return; } } std::vector supportedExts; CHECK_VKR(vkh::enumerateDeviceExtensionProperties(supportedExts, phys, NULL)); // add any optional extensions that are supported for(const char *search : optDevExts) { bool found = false; for(VkExtensionProperties &ext : supportedExts) { if(!strcmp(ext.extensionName, search)) { found = true; break; } } if(found) devExts.push_back(search); } vkGetPhysicalDeviceProperties(phys, &physProperties); instVersion = vulkanVersion; devVersion = physProperties.apiVersion; if(std::find(enabledInstExts.begin(), enabledInstExts.end(), VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) != enabledInstExts.end()) { vkh::PhysicalDeviceProperties2KHR props2; vkGetPhysicalDeviceProperties2KHR(phys, props2); devVersion = props2.properties.apiVersion; } for(const char *search : devExts) { bool found = false; for(VkExtensionProperties &ext : supportedExts) { if(!strcmp(ext.extensionName, search)) { found = true; break; } } if(!found) { // try the layers we're enabling for(const char *layer : enabledLayers) { std::vector layerExts; CHECK_VKR(vkh::enumerateDeviceExtensionProperties(layerExts, phys, layer)); for(VkExtensionProperties &ext : layerExts) { if(!strcmp(ext.extensionName, search)) { found = true; break; } } if(found) break; } if(!found) { Avail = "Required device extension '"; Avail += search; Avail += "' is not supported"; return; } } } std::vector queueProps; vkh::getQueueFamilyProperties(queueProps, phys); for(uint32_t q = 0; q < queueProps.size(); q++) { if(queueProps[q].queueFlags & VK_QUEUE_GRAPHICS_BIT) { if(graphicsQueueFamilyIndex == ~0U) graphicsQueueFamilyIndex = q; } else if(queueProps[q].queueFlags & VK_QUEUE_COMPUTE_BIT) { if(computeQueueFamilyIndex == ~0U) computeQueueFamilyIndex = q; } else if(queueProps[q].queueFlags & VK_QUEUE_TRANSFER_BIT) { if(transferQueueFamilyIndex == ~0U) transferQueueFamilyIndex = q; } } // if no queue has been selected, find it now if(queueFamilyIndex == ~0U) { // try to find an exact match first for(uint32_t q = 0; q < queueProps.size(); q++) { VkQueueFlags flags = queueProps[q].queueFlags; if(flags == queueFlagsRequired) { queueFamilyIndex = q; queueCount = 1; break; } } } if(queueFamilyIndex == ~0U) { // if we didn't find an exact match, look for any that does satisfy what we want for(uint32_t q = 0; q < queueProps.size(); q++) { VkQueueFlags flags = queueProps[q].queueFlags; if(((flags & queueFlagsRequired) == queueFlagsRequired) && ((flags & queueFlagsBanned) == 0)) { queueFamilyIndex = q; queueCount = 1; break; } } } if(queueFamilyIndex == ~0U) Avail = "No satisfactory queue family available"; } bool VulkanGraphicsTest::Init() { // parse parameters here to override parameters if(!GraphicsTest::Init()) return false; if(debugDevice) { CHECK_VKR(vkCreateDebugUtilsMessengerEXT( instance, vkh::DebugUtilsMessengerCreateInfoEXT(&vulkanCallback, NULL, VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT), NULL, &debugUtilsMessenger)); } std::vector supportedExts; CHECK_VKR(vkh::enumerateDeviceExtensionProperties(supportedExts, phys, NULL)); // add any optional extensions that are supported for(const char *search : optDevExts) { bool found = false; for(VkExtensionProperties &ext : supportedExts) { if(!strcmp(ext.extensionName, search)) { found = true; break; } } if(found) devExts.push_back(search); } const std::vector priorities = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, }; std::vector queueCreates = { vkh::DeviceQueueCreateInfo(queueFamilyIndex, queueCount, priorities), }; if(queueFamilyIndex != graphicsQueueFamilyIndex && forceGraphicsQueue) queueCreates.push_back(vkh::DeviceQueueCreateInfo(graphicsQueueFamilyIndex, 1, priorities)); if(queueFamilyIndex != computeQueueFamilyIndex && (graphicsQueueFamilyIndex != computeQueueFamilyIndex || !forceGraphicsQueue) && computeQueueFamilyIndex != ~0U && forceComputeQueue) queueCreates.push_back(vkh::DeviceQueueCreateInfo(computeQueueFamilyIndex, 1, priorities)); if(queueFamilyIndex != transferQueueFamilyIndex && graphicsQueueFamilyIndex != transferQueueFamilyIndex && computeQueueFamilyIndex != transferQueueFamilyIndex && transferQueueFamilyIndex != ~0U && forceTransferQueue) queueCreates.push_back(vkh::DeviceQueueCreateInfo(transferQueueFamilyIndex, 1, priorities)); CHECK_VKR(vkCreateDevice( phys, vkh::DeviceCreateInfo(queueCreates, enabledLayers, devExts, features).next(devInfoNext), NULL, &device)); volkLoadDevice(device); vkGetDeviceQueue(device, queueFamilyIndex, 0, &queue); if(!headless) { mainWindow = MakeWindow(screenWidth, screenHeight, "Autotesting"); if(!mainWindow->Initialised()) { TEST_ERROR("Error creating surface"); return false; } } VmaVulkanFunctions funcs = { vkGetInstanceProcAddr, vkGetDeviceProcAddr, vkGetPhysicalDeviceProperties, vkGetPhysicalDeviceMemoryProperties, vkAllocateMemory, vkFreeMemory, vkMapMemory, vkUnmapMemory, vkFlushMappedMemoryRanges, vkInvalidateMappedMemoryRanges, vkBindBufferMemory, vkBindImageMemory, vkGetBufferMemoryRequirements, vkGetImageMemoryRequirements, vkCreateBuffer, vkDestroyBuffer, vkCreateImage, vkDestroyImage, vkCmdCopyBuffer, vkGetBufferMemoryRequirements2KHR, vkGetImageMemoryRequirements2KHR, vkBindBufferMemory2KHR, vkBindImageMemory2KHR, vkGetPhysicalDeviceMemoryProperties2KHR, vkGetDeviceBufferMemoryRequirements, vkGetDeviceImageMemoryRequirements, }; VmaAllocatorCreateInfo allocInfo = {}; allocInfo.instance = instance; allocInfo.physicalDevice = phys; allocInfo.device = device; allocInfo.pVulkanFunctions = &funcs; allocInfo.vulkanApiVersion = devVersion; if(hasExt(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME) && vmaDedicated) allocInfo.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT; if((hasExt(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) || devVersion >= VK_MAKE_VERSION(1, 2, 0)) && vmaBDA) allocInfo.flags |= VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; vmaCreateAllocator(&allocInfo, &allocator); TEST_LOG("Running Vulkan test on %s (version %d.%d)", physProperties.deviceName, VK_VERSION_MAJOR(physProperties.apiVersion), VK_VERSION_MINOR(physProperties.apiVersion)); headlessCmds = new VulkanCommands(this); if(!headless) { VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo()); vkh::GraphicsPipelineCreateInfo pipeCreateInfo; pipeCreateInfo.layout = layout; pipeCreateInfo.renderPass = mainWindow->rp; pipeCreateInfo.vertexInputState.vertexBindingDescriptions = {vkh::vertexBind(0, DefaultA2V)}; pipeCreateInfo.vertexInputState.vertexAttributeDescriptions = { vkh::vertexAttr(0, 0, DefaultA2V, pos), vkh::vertexAttr(1, 0, DefaultA2V, col), vkh::vertexAttr(2, 0, DefaultA2V, uv), }; pipeCreateInfo.stages = { CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"), CompileShaderModule(VKDefaultPixel, ShaderLang::glsl, ShaderStage::frag, "main"), }; DefaultTriPipe = createGraphicsPipeline(pipeCreateInfo); DefaultTriVB = AllocatedBuffer( this, vkh::BufferCreateInfo(sizeof(DefaultTri), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT), VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); DefaultTriVB.upload(DefaultTri); } return true; } VulkanWindow *VulkanGraphicsTest::MakeWindow(int width, int height, const char *title) { #if defined(WIN32) GraphicsWindow *platWin = new Win32Window(width, height, title); #elif defined(ANDROID) GraphicsWindow *platWin = new AndroidWindow(width, height, title); #elif defined(__linux__) GraphicsWindow *platWin = new X11Window(width, height, 0, title); #elif defined(__APPLE__) GraphicsWindow *platWin = new AppleWindow(width, height, title); #else #error UNKNOWN PLATFORM #endif return new VulkanWindow(this, platWin); } void VulkanGraphicsTest::Shutdown() { if(device) { vkDeviceWaitIdle(device); for(VkShaderModule shader : shaders) vkDestroyShaderModule(device, shader, NULL); for(VkDescriptorPool pool : descPools) vkDestroyDescriptorPool(device, pool, NULL); for(VkPipeline pipe : pipes) vkDestroyPipeline(device, pipe, NULL); for(VkFramebuffer fb : framebuffers) vkDestroyFramebuffer(device, fb, NULL); for(VkRenderPass rp : renderpasses) vkDestroyRenderPass(device, rp, NULL); for(VkImageView view : imageviews) vkDestroyImageView(device, view, NULL); for(VkBufferView view : bufferviews) vkDestroyBufferView(device, view, NULL); for(VkPipelineLayout layout : pipelayouts) vkDestroyPipelineLayout(device, layout, NULL); for(VkDescriptorSetLayout layout : setlayouts) vkDestroyDescriptorSetLayout(device, layout, NULL); for(VkSampler sampler : samplers) vkDestroySampler(device, sampler, NULL); for(auto it : imageAllocs) vmaDestroyImage(allocator, it.first, it.second); for(auto it : bufferAllocs) vmaDestroyBuffer(allocator, it.first, it.second); vmaDestroyAllocator(allocator); if(headlessCmds) delete headlessCmds; delete mainWindow; vkDestroyDevice(device, NULL); } if(debugUtilsMessenger) vkDestroyDebugUtilsMessengerEXT(instance, debugUtilsMessenger, NULL); if(instance) vkDestroyInstance(instance, NULL); } bool VulkanGraphicsTest::Running() { if(!FrameLimit()) return false; return mainWindow->Update(); } VkImage VulkanGraphicsTest::StartUsingBackbuffer(VkCommandBuffer cmd, VkAccessFlags nextUse, VkImageLayout layout, VulkanWindow *window) { if(window == NULL) window = mainWindow; VkImage img = window->GetImage(); vkh::cmdPipelineBarrier( cmd, { vkh::ImageMemoryBarrier(0, nextUse, VK_IMAGE_LAYOUT_UNDEFINED, layout, img), }); return img; } void VulkanGraphicsTest::FinishUsingBackbuffer(VkCommandBuffer cmd, VkAccessFlags prevUse, VkImageLayout layout, VulkanWindow *window) { if(window == NULL) window = mainWindow; VkImage img = window->GetImage(); vkh::cmdPipelineBarrier(cmd, { vkh::ImageMemoryBarrier(prevUse, VK_ACCESS_MEMORY_READ_BIT, layout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, img), }); } void VulkanGraphicsTest::Submit(int index, int totalSubmits, const std::vector &cmds, const std::vector &seccmds) { if(mainWindow) mainWindow->Submit(index, totalSubmits, cmds, seccmds, queue); else headlessCmds->Submit(cmds, seccmds, queue, VK_NULL_HANDLE, VK_NULL_HANDLE); } void VulkanGraphicsTest::SubmitAndPresent(const std::vector &cmds) { Submit(0, 1, cmds, {}); Present(); } void VulkanGraphicsTest::Present() { mainWindow->Present(queue); } VkPipelineShaderStageCreateInfo VulkanGraphicsTest::LoadShaderModule(const std::string &filename, ShaderStage stage, const char *entry_point) { VkShaderModule ret = VK_NULL_HANDLE; std::vector spirv; FILE *f = fopen(filename.c_str(), "rb"); if(f) { fseek(f, 0, SEEK_END); spirv.resize(ftell(f) / sizeof(uint32_t)); fseek(f, 0, SEEK_SET); fread(spirv.data(), sizeof(uint32_t), spirv.size(), f); fclose(f); } if(spirv.empty()) return {}; CHECK_VKR(vkCreateShaderModule(device, vkh::ShaderModuleCreateInfo(spirv), NULL, &ret)); shaders.push_back(ret); VkShaderStageFlagBits vkstage[] = { VK_SHADER_STAGE_VERTEX_BIT, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, VK_SHADER_STAGE_GEOMETRY_BIT, VK_SHADER_STAGE_FRAGMENT_BIT, VK_SHADER_STAGE_COMPUTE_BIT, }; return vkh::PipelineShaderStageCreateInfo(ret, vkstage[(int)stage], entry_point); } VkPipelineShaderStageCreateInfo VulkanGraphicsTest::CompileShaderModule( const std::string &source_text, ShaderLang lang, ShaderStage stage, const char *entry_point, const std::map ¯os, SPIRVTarget target) { VkShaderModule ret = VK_NULL_HANDLE; std::vector spirv = ::CompileShaderToSpv(source_text, target, lang, stage, entry_point, macros); if(spirv.empty()) return {}; CHECK_VKR(vkCreateShaderModule(device, vkh::ShaderModuleCreateInfo(spirv), NULL, &ret)); shaders.push_back(ret); VkShaderStageFlagBits vkstage[] = { VK_SHADER_STAGE_VERTEX_BIT, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, VK_SHADER_STAGE_GEOMETRY_BIT, VK_SHADER_STAGE_FRAGMENT_BIT, VK_SHADER_STAGE_COMPUTE_BIT, VK_SHADER_STAGE_MESH_BIT_EXT, VK_SHADER_STAGE_TASK_BIT_EXT, }; return vkh::PipelineShaderStageCreateInfo(ret, vkstage[(int)stage], entry_point); } VkCommandBuffer VulkanGraphicsTest::GetCommandBuffer(VkCommandBufferLevel level, VulkanWindow *window) { if(window == NULL) window = mainWindow; if(window) return window->GetCommandBuffer(level); return headlessCmds->GetCommandBuffer(level); } template <> void VulkanGraphicsTest::setName(VkPipeline obj, const std::string &name) { setName(VK_OBJECT_TYPE_PIPELINE, (uint64_t)obj, name); } template <> void VulkanGraphicsTest::setName(VkFramebuffer obj, const std::string &name) { setName(VK_OBJECT_TYPE_FRAMEBUFFER, (uint64_t)obj, name); } template <> void VulkanGraphicsTest::setName(VkImage obj, const std::string &name) { setName(VK_OBJECT_TYPE_IMAGE, (uint64_t)obj, name); } template <> void VulkanGraphicsTest::setName(VkImageView obj, const std::string &name) { setName(VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)obj, name); } template <> void VulkanGraphicsTest::setName(VkSampler obj, const std::string &name) { setName(VK_OBJECT_TYPE_SAMPLER, (uint64_t)obj, name); } template <> void VulkanGraphicsTest::setName(VkBuffer obj, const std::string &name) { setName(VK_OBJECT_TYPE_BUFFER, (uint64_t)obj, name); } template <> void VulkanGraphicsTest::setName(VkBufferView obj, const std::string &name) { setName(VK_OBJECT_TYPE_BUFFER_VIEW, (uint64_t)obj, name); } template <> void VulkanGraphicsTest::setName(VkSemaphore obj, const std::string &name) { setName(VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)obj, name); } template <> void VulkanGraphicsTest::setName(VkFence obj, const std::string &name) { setName(VK_OBJECT_TYPE_FENCE, (uint64_t)obj, name); } template <> void VulkanGraphicsTest::setName(VkAccelerationStructureKHR obj, const std::string &name) { setName(VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR, (uint64_t)obj, name); } void VulkanGraphicsTest::setName(VkObjectType objType, uint64_t obj, const std::string &name) { if(vkSetDebugUtilsObjectNameEXT) { VkDebugUtilsObjectNameInfoEXT info = {}; info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; info.objectType = objType; info.objectHandle = obj; info.pObjectName = name.c_str(); vkSetDebugUtilsObjectNameEXT(device, &info); } } void VulkanGraphicsTest::pushMarker(VkCommandBuffer cmd, const std::string &name) { if(vkCmdBeginDebugUtilsLabelEXT) { VkDebugUtilsLabelEXT info = {}; info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; info.pLabelName = name.c_str(); vkCmdBeginDebugUtilsLabelEXT(cmd, &info); } } void VulkanGraphicsTest::setMarker(VkCommandBuffer cmd, const std::string &name) { if(vkCmdInsertDebugUtilsLabelEXT) { VkDebugUtilsLabelEXT info = {}; info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; info.pLabelName = name.c_str(); vkCmdInsertDebugUtilsLabelEXT(cmd, &info); } } void VulkanGraphicsTest::popMarker(VkCommandBuffer cmd) { if(vkCmdEndDebugUtilsLabelEXT) vkCmdEndDebugUtilsLabelEXT(cmd); } void VulkanGraphicsTest::blitToSwap(VkCommandBuffer cmd, VkImage src, VkImageLayout srcLayout, VkImage dst, VkImageLayout dstLayout) { VkImageBlit region = {}; region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.srcSubresource.layerCount = 1; region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.dstSubresource.layerCount = 1; region.srcOffsets[1].x = mainWindow->scissor.extent.width; region.srcOffsets[1].y = mainWindow->scissor.extent.height; region.srcOffsets[1].z = 1; region.dstOffsets[1].x = mainWindow->scissor.extent.width; region.dstOffsets[1].y = mainWindow->scissor.extent.height; region.dstOffsets[1].z = 1; vkCmdBlitImage(cmd, src, srcLayout, dst, dstLayout, 1, ®ion, VK_FILTER_LINEAR); } void VulkanGraphicsTest::uploadBufferToImage(VkImage destImage, VkExtent3D destExtent, VkBuffer srcBuffer, VkImageLayout finalLayout) { VkCommandBuffer cmd = GetCommandBuffer(); vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); vkh::cmdPipelineBarrier( cmd, { vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, destImage), }); VkBufferImageCopy copy = {}; copy.imageExtent = destExtent; copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; copy.imageSubresource.layerCount = 1; vkCmdCopyBufferToImage(cmd, srcBuffer, destImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); vkh::cmdPipelineBarrier( cmd, { vkh::ImageMemoryBarrier(VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, finalLayout, destImage), }); vkEndCommandBuffer(cmd); Submit(99, 99, {cmd}); vkDeviceWaitIdle(device); } void VulkanGraphicsTest::pushMarker(VkQueue q, const std::string &name) { if(vkQueueBeginDebugUtilsLabelEXT) { VkDebugUtilsLabelEXT info = {}; info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; info.pLabelName = name.c_str(); vkQueueBeginDebugUtilsLabelEXT(q, &info); } } void VulkanGraphicsTest::setMarker(VkQueue q, const std::string &name) { if(vkQueueInsertDebugUtilsLabelEXT) { VkDebugUtilsLabelEXT info = {}; info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; info.pLabelName = name.c_str(); vkQueueInsertDebugUtilsLabelEXT(q, &info); } } void VulkanGraphicsTest::popMarker(VkQueue q) { if(vkQueueEndDebugUtilsLabelEXT) vkQueueEndDebugUtilsLabelEXT(q); } VkDescriptorSet VulkanGraphicsTest::allocateDescriptorSet(VkDescriptorSetLayout setLayout) { VkDescriptorSet ret = VK_NULL_HANDLE; if(!descPools.empty()) { VkDescriptorPool pool = descPools.back(); VkResult vkr = vkAllocateDescriptorSets(device, vkh::DescriptorSetAllocateInfo(pool, {setLayout}), &ret); if(vkr == VK_SUCCESS) return ret; } // failed to allocate, create a new pool and push it { VkDescriptorPool pool = VK_NULL_HANDLE; std::vector poolSizes = { {VK_DESCRIPTOR_TYPE_SAMPLER, 1024}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1024}, {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1024}, {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1024}, {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1024}, {VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1024}, {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1024}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1024}, {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1024}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1024}, {VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1024}, }; VkDescriptorPoolInlineUniformBlockCreateInfo inlineCreateInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_INLINE_UNIFORM_BLOCK_CREATE_INFO, }; void *next = NULL; if(hasExt(VK_EXT_INLINE_UNIFORM_BLOCK_EXTENSION_NAME) || devVersion >= VK_MAKE_VERSION(1, 3, 0)) { poolSizes.push_back({VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK, 128 * 4096}); inlineCreateInfo.maxInlineUniformBlockBindings = 1024; next = &inlineCreateInfo; } if(hasExt(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME)) { poolSizes.push_back({VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1024}); } CHECK_VKR(vkCreateDescriptorPool( device, vkh::DescriptorPoolCreateInfo(128, poolSizes).next(next), NULL, &pool)); descPools.push_back(pool); // this must succeed or we can't continue. CHECK_VKR( vkAllocateDescriptorSets(device, vkh::DescriptorSetAllocateInfo(pool, {setLayout}), &ret)); return ret; } } VkPipeline VulkanGraphicsTest::createGraphicsPipeline(const VkGraphicsPipelineCreateInfo *info) { VkPipeline ret; CHECK_VKR(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, info, NULL, &ret)); pipes.push_back(ret); return ret; } VkPipeline VulkanGraphicsTest::createComputePipeline(const VkComputePipelineCreateInfo *info) { VkPipeline ret; CHECK_VKR(vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, info, NULL, &ret)); pipes.push_back(ret); return ret; } VkFramebuffer VulkanGraphicsTest::createFramebuffer(const VkFramebufferCreateInfo *info) { VkFramebuffer ret; CHECK_VKR(vkCreateFramebuffer(device, info, NULL, &ret)); framebuffers.push_back(ret); return ret; } VkRenderPass VulkanGraphicsTest::createRenderPass(const VkRenderPassCreateInfo *info) { VkRenderPass ret; CHECK_VKR(vkCreateRenderPass(device, info, NULL, &ret)); renderpasses.push_back(ret); return ret; } VkImageView VulkanGraphicsTest::createImageView(const VkImageViewCreateInfo *info) { VkImageView ret; CHECK_VKR(vkCreateImageView(device, info, NULL, &ret)); imageviews.push_back(ret); return ret; } VkBufferView VulkanGraphicsTest::createBufferView(const VkBufferViewCreateInfo *info) { VkBufferView ret; CHECK_VKR(vkCreateBufferView(device, info, NULL, &ret)); bufferviews.push_back(ret); return ret; } VkPipelineLayout VulkanGraphicsTest::createPipelineLayout(const VkPipelineLayoutCreateInfo *info) { VkPipelineLayout ret; CHECK_VKR(vkCreatePipelineLayout(device, info, NULL, &ret)); pipelayouts.push_back(ret); return ret; } VkDescriptorSetLayout VulkanGraphicsTest::createDescriptorSetLayout( const VkDescriptorSetLayoutCreateInfo *info) { VkDescriptorSetLayout ret; CHECK_VKR(vkCreateDescriptorSetLayout(device, info, NULL, &ret)); setlayouts.push_back(ret); return ret; } VkSampler VulkanGraphicsTest::createSampler(const VkSamplerCreateInfo *info) { VkSampler ret; CHECK_VKR(vkCreateSampler(device, info, NULL, &ret)); samplers.push_back(ret); return ret; } VulkanCommands::VulkanCommands(VulkanGraphicsTest *test) { m_Test = test; CHECK_VKR(vkCreateCommandPool( m_Test->device, vkh::CommandPoolCreateInfo(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, m_Test->queueFamilyIndex), NULL, &cmdPool)); } VulkanCommands::~VulkanCommands() { vkDestroyCommandPool(m_Test->device, cmdPool, NULL); for(VkFence fence : fences) vkDestroyFence(m_Test->device, fence, NULL); } VkCommandBuffer VulkanCommands::GetCommandBuffer(VkCommandBufferLevel level) { std::vector &buflist = freeCommandBuffers[level]; if(buflist.empty()) { buflist.resize(4); CHECK_VKR(vkAllocateCommandBuffers( m_Test->device, vkh::CommandBufferAllocateInfo(cmdPool, 4, level), &buflist[0])); } VkCommandBuffer ret = buflist.back(); buflist.pop_back(); return ret; } void VulkanCommands::Submit(const std::vector &cmds, const std::vector &seccmds, VkQueue q, VkSemaphore wait, VkSemaphore signal) { VkFence fence; CHECK_VKR(vkCreateFence(m_Test->device, vkh::FenceCreateInfo(), NULL, &fence)); fences.insert(fence); if(m_Test->hasExt(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME)) { VkSubmitInfo2KHR submit = {VK_STRUCTURE_TYPE_SUBMIT_INFO_2_KHR}; std::vector cmdSubmits; for(VkCommandBuffer cmd : cmds) cmdSubmits.push_back({VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO_KHR, NULL, cmd, 0}); submit.commandBufferInfoCount = (uint32_t)cmdSubmits.size(); submit.pCommandBufferInfos = cmdSubmits.data(); VkSemaphoreSubmitInfoKHR waitInfo = {}, signalInfo = {}; if(wait != VK_NULL_HANDLE) { waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR; waitInfo.semaphore = wait; waitInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT_KHR; submit.waitSemaphoreInfoCount = 1; submit.pWaitSemaphoreInfos = &waitInfo; } if(signal != VK_NULL_HANDLE) { signalInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR; signalInfo.semaphore = signal; signalInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT_KHR; submit.signalSemaphoreInfoCount = 1; submit.pSignalSemaphoreInfos = &signalInfo; } CHECK_VKR(vkQueueSubmit2KHR(q, 1, &submit, fence)); } else { VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; VkSubmitInfo submit = vkh::SubmitInfo(cmds); if(wait != VK_NULL_HANDLE) { submit.waitSemaphoreCount = 1; submit.pWaitDstStageMask = &waitStage; submit.pWaitSemaphores = &wait; } if(signal != VK_NULL_HANDLE) { submit.signalSemaphoreCount = 1; submit.pSignalSemaphores = &signal; } CHECK_VKR(vkQueueSubmit(q, 1, &submit, fence)); } for(const VkCommandBuffer &cmd : cmds) pendingCommandBuffers[0].push_back(std::make_pair(cmd, fence)); for(const VkCommandBuffer &cmd : seccmds) pendingCommandBuffers[1].push_back(std::make_pair(cmd, fence)); } VulkanWindow::VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win) : GraphicsWindow(win->title), VulkanCommands(test) { m_Test = test; m_Win = win; { std::lock_guard lock(m_Test->mutex); #if defined(WIN32) VkWin32SurfaceCreateInfoKHR createInfo; createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; createInfo.pNext = NULL; createInfo.flags = 0; createInfo.hwnd = ((Win32Window *)win)->wnd; createInfo.hinstance = GetModuleHandleA(NULL); vkCreateWin32SurfaceKHR(m_Test->instance, &createInfo, NULL, &surface); #elif defined(ANDROID) VkAndroidSurfaceCreateInfoKHR createInfo; createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; createInfo.pNext = NULL; createInfo.flags = 0; createInfo.window = ((AndroidWindow *)win)->window; vkCreateAndroidSurfaceKHR(m_Test->instance, &createInfo, NULL, &surface); #elif defined(__linux__) VkXcbSurfaceCreateInfoKHR createInfo; createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; createInfo.pNext = NULL; createInfo.flags = 0; createInfo.connection = ((X11Window *)win)->xcb.connection; createInfo.window = ((X11Window *)win)->xcb.window; vkCreateXcbSurfaceKHR(m_Test->instance, &createInfo, NULL, &surface); #elif defined(__APPLE__) VkMacOSSurfaceCreateInfoMVK createInfo; createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; createInfo.pNext = NULL; createInfo.flags = 0; createInfo.pView = ((AppleWindow *)win)->view; vkCreateMacOSSurfaceMVK(m_Test->instance, &createInfo, NULL, &surface); #else #error UNKNOWN PLATFORM #endif } CreateSwapchain(); Acquire(); } VulkanWindow::~VulkanWindow() { DestroySwapchain(); { TEST_ASSERT(renderStartSemaphore.size() == renderEndSemaphore.size(), "size mismatch between start/end semaphore"); TEST_ASSERT(renderStartSemaphore.size() == imageFences.size(), "size mismatch between render/image semaphore"); for(size_t i = 0; i < renderStartSemaphore.size(); i++) { vkDestroySemaphore(m_Test->device, renderStartSemaphore[i], NULL); vkDestroySemaphore(m_Test->device, renderEndSemaphore[i], NULL); vkDestroyFence(m_Test->device, imageFences[i], NULL); } if(surface) vkDestroySurfaceKHR(m_Test->instance, surface, NULL); } delete m_Win; } void VulkanWindow::setViewScissor(VkCommandBuffer cmd) { vkCmdSetViewport(cmd, 0, 1, &viewport); vkCmdSetScissor(cmd, 0, 1, &scissor); } bool VulkanWindow::CreateSwapchain() { std::lock_guard lock(m_Test->mutex); if(surface == VK_NULL_HANDLE) return false; VkResult vkr = VK_SUCCESS; VkSurfaceFormatKHR surfaceFormat = {}; std::vector formats; CHECK_VKR(vkh::getSurfaceFormatsKHR(formats, m_Test->phys, surface)); VkBool32 support = VK_FALSE; CHECK_VKR(vkGetPhysicalDeviceSurfaceSupportKHR(m_Test->phys, m_Test->queueFamilyIndex, surface, &support)); TEST_ASSERT(support, "Presentation is not supported on surface"); if(vkr != VK_SUCCESS || formats.empty()) { TEST_ERROR("Error getting surface formats: %s", vkh::result_str(vkr)); return false; } surfaceFormat = formats[0]; for(const VkSurfaceFormatKHR &f : formats) { if((f.format == VK_FORMAT_B8G8R8A8_SRGB || f.format == VK_FORMAT_R8G8B8A8_SRGB) && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { surfaceFormat = f; break; } } if(surfaceFormat.format == VK_FORMAT_UNDEFINED) { surfaceFormat.format = VK_FORMAT_B8G8R8A8_SRGB; surfaceFormat.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; } format = surfaceFormat.format; std::vector modes; CHECK_VKR(vkh::getSurfacePresentModesKHR(modes, m_Test->phys, surface)); VkPresentModeKHR mode = VK_PRESENT_MODE_IMMEDIATE_KHR; if(std::find(modes.begin(), modes.end(), mode) == modes.end()) mode = VK_PRESENT_MODE_FIFO_KHR; uint32_t width = 1, height = 1; VkSurfaceCapabilitiesKHR capabilities; CHECK_VKR(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_Test->phys, surface, &capabilities)); width = capabilities.currentExtent.width; width = std::min(width, capabilities.maxImageExtent.width); width = std::max(width, capabilities.minImageExtent.width); height = capabilities.currentExtent.height; height = std::min(height, capabilities.maxImageExtent.height); height = std::max(height, capabilities.minImageExtent.height); viewport = vkh::Viewport(0, 0, (float)width, (float)height, 0.0f, 1.0f); scissor = vkh::Rect2D({0, 0}, {width, height}); CHECK_VKR(vkCreateSwapchainKHR( m_Test->device, vkh::SwapchainCreateInfoKHR(surface, mode, surfaceFormat, {width, height}, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_NULL_HANDLE, capabilities.minImageCount), NULL, &swap)); CHECK_VKR(vkh::getSwapchainImagesKHR(imgs, m_Test->device, swap)); if(rp == VK_NULL_HANDLE) { vkh::RenderPassCreator renderPassCreateInfo; renderPassCreateInfo.attachments.push_back( vkh::AttachmentDescription(format, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL)); renderPassCreateInfo.addSubpass({VkAttachmentReference({0, VK_IMAGE_LAYOUT_GENERAL})}); rp = m_Test->createRenderPass(renderPassCreateInfo); } renderStartSemaphore.resize(imgs.size()); renderEndSemaphore.resize(imgs.size()); imageFences.resize(imgs.size()); for(size_t i = 0; i < renderStartSemaphore.size(); i++) { CHECK_VKR(vkCreateSemaphore(m_Test->device, vkh::SemaphoreCreateInfo(), NULL, &renderStartSemaphore[i])); CHECK_VKR(vkCreateSemaphore(m_Test->device, vkh::SemaphoreCreateInfo(), NULL, &renderEndSemaphore[i])); m_Test->setName(renderStartSemaphore[i], title + " renderStartSemaphore" + std::to_string(i)); m_Test->setName(renderEndSemaphore[i], title + " renderEndSemaphore" + std::to_string(i)); CHECK_VKR(vkCreateFence(m_Test->device, vkh::FenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT), NULL, &imageFences[i])); m_Test->setName(imageFences[i], title + " fence" + std::to_string(i)); } imgviews.resize(imgs.size()); for(size_t i = 0; i < imgs.size(); i++) { CHECK_VKR(vkCreateImageView(m_Test->device, vkh::ImageViewCreateInfo(imgs[i], VK_IMAGE_VIEW_TYPE_2D, format), NULL, &imgviews[i])); } fbs.resize(imgs.size()); for(size_t i = 0; i < imgviews.size(); i++) fbs[i] = m_Test->createFramebuffer(vkh::FramebufferCreateInfo(rp, {imgviews[i]}, scissor.extent)); return true; } void VulkanWindow::Acquire() { if(swap == VK_NULL_HANDLE) return; semIdx = (semIdx + 1) % renderStartSemaphore.size(); // acquire next image stupidly does not properly block, do a manual block vkWaitForFences(m_Test->device, 1, &imageFences[semIdx], VK_FALSE, UINT64_MAX); vkResetFences(m_Test->device, 1, &imageFences[semIdx]); VkResult vkr = vkAcquireNextImageKHR(m_Test->device, swap, UINT64_MAX, renderStartSemaphore[semIdx], imageFences[semIdx], &imgIndex); if(vkr == VK_SUBOPTIMAL_KHR || vkr == VK_ERROR_OUT_OF_DATE_KHR) { DestroySwapchain(); CreateSwapchain(); vkr = vkAcquireNextImageKHR(m_Test->device, swap, UINT64_MAX, renderStartSemaphore[semIdx], VK_NULL_HANDLE, &imgIndex); } } void VulkanWindow::Submit(int index, int totalSubmits, const std::vector &cmds, const std::vector &seccmds, VkQueue q) { VkSemaphore signal = VK_NULL_HANDLE, wait = VK_NULL_HANDLE; if(index == 0) wait = renderStartSemaphore[semIdx]; if(index == totalSubmits - 1) signal = renderEndSemaphore[semIdx]; VulkanCommands::Submit(cmds, seccmds, q, wait, signal); } void VulkanWindow::MultiPresent(VkQueue queue, std::vector windows) { std::vector swaps; std::vector idxs; std::vector waitSems; std::vector vkrs; for(auto it : windows) { if(it->swap == VK_NULL_HANDLE) continue; swaps.push_back(it->swap); idxs.push_back(it->imgIndex); waitSems.push_back(it->renderEndSemaphore[it->semIdx]); vkrs.push_back(VK_SUCCESS); } if(swaps.empty()) return; VkPresentInfoKHR info = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR}; info.swapchainCount = (uint32_t)swaps.size(); info.waitSemaphoreCount = (uint32_t)waitSems.size(); info.pSwapchains = swaps.data(); info.pImageIndices = idxs.data(); info.pWaitSemaphores = waitSems.data(); info.pResults = vkrs.data(); vkQueuePresentKHR(queue, &info); size_t i = 0; for(auto it : windows) { if(it->swap == VK_NULL_HANDLE) continue; it->PostPresent(vkrs[i++]); } } void VulkanWindow::Present(VkQueue queue) { if(swap == VK_NULL_HANDLE) return; VkResult vkr = vkQueuePresentKHR(queue, vkh::PresentInfoKHR(swap, imgIndex, &renderEndSemaphore[semIdx])); PostPresent(vkr); } void VulkanWindow::PostPresent(VkResult vkr) { if(vkr == VK_SUBOPTIMAL_KHR || vkr == VK_ERROR_OUT_OF_DATE_KHR) { DestroySwapchain(); CreateSwapchain(); } else if(vkr != VK_SUCCESS) { VkResult queuePresentError = vkr; CHECK_VKR(queuePresentError); } VulkanCommands::ProcessCompletions(); Acquire(); } void VulkanCommands::ProcessCompletions() { std::set doneFences; std::map fenceStatus; // only test each fence once so we avoid the problem of testing a fence once, finding it's not // ready, then testing it again in a second use and finding that it's now ready, and deleting // it for(VkFence f : fences) fenceStatus[f] = vkGetFenceStatus(m_Test->device, f); for(int level = 0; level < 2; level++) { for(auto it = pendingCommandBuffers[level].begin(); it != pendingCommandBuffers[level].end();) { if(fenceStatus[it->second] == VK_SUCCESS) { freeCommandBuffers[level].push_back(it->first); doneFences.insert(it->second); it = pendingCommandBuffers[level].erase(it); } else { ++it; } } } for(auto it = doneFences.begin(); it != doneFences.end(); ++it) { vkDestroyFence(m_Test->device, *it, NULL); fences.erase(*it); } } void VulkanWindow::DestroySwapchain() { std::lock_guard lock(m_Test->mutex); vkDeviceWaitIdle(m_Test->device); for(size_t i = 0; i < imgs.size(); i++) vkDestroyImageView(m_Test->device, imgviews[i], NULL); vkDestroySwapchainKHR(m_Test->device, swap, NULL); } void VulkanGraphicsTest::getPhysFeatures2(void *nextStruct) { for(const char *ext : enabledInstExts) { if(!strcmp(ext, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) { vkGetPhysicalDeviceFeatures2KHR(phys, vkh::PhysicalDeviceFeatures2KHR().next(nextStruct)); return; } } } void VulkanGraphicsTest::getPhysProperties2(void *nextStruct) { for(const char *ext : enabledInstExts) { if(!strcmp(ext, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) { vkGetPhysicalDeviceProperties2KHR(phys, vkh::PhysicalDeviceProperties2KHR().next(nextStruct)); return; } } } bool VulkanGraphicsTest::hasExt(const char *ext) { return std::find_if(devExts.begin(), devExts.end(), [ext](const char *a) { return !strcmp(a, ext); }) != devExts.end(); } AllocatedImage::AllocatedImage(VulkanGraphicsTest *test, const VkImageCreateInfo &imgInfo, const VmaAllocationCreateInfo &allocInfo) { createInfo = imgInfo; this->test = test; allocator = test->allocator; vmaCreateImage(allocator, &imgInfo, &allocInfo, &image, &alloc, NULL); test->imageAllocs[image] = alloc; } void AllocatedImage::free() { vmaDestroyImage(allocator, image, alloc); test->imageAllocs.erase(image); } AllocatedBuffer::AllocatedBuffer(VulkanGraphicsTest *test, const VkBufferCreateInfo &bufInfo, const VmaAllocationCreateInfo &allocInfo, uint32_t alignment) { this->test = test; allocator = test->allocator; vmaCreateBufferWithAlignment(allocator, &bufInfo, &allocInfo, alignment, &buffer, &alloc, NULL); test->bufferAllocs[buffer] = alloc; if(bufInfo.usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) { VkBufferDeviceAddressInfoKHR info = { VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR, NULL, buffer, }; address = vkGetBufferDeviceAddressKHR(test->device, &info); } } void AllocatedBuffer::free() { vmaDestroyBuffer(allocator, buffer, alloc); test->bufferAllocs.erase(buffer); }