//////////////////////////////////////////////////////////////////////////////// // (c) Copyright, Ubisoft Entertainment Inc. || Created by Harbour // //////////////////////////////////////////////////////////////////////////////// #include "tests/ggp/vulkanTestHelper.h" #if defined(US_TARGET_GGP) #include "tests/ggp/ps_spv.h" #include "tests/ggp/vs_spv.h" #include #include #include #include #define Abort(message, ...) \ { \ PRINT_LINE("%s:%u:%s:" message, __FILE__, __LINE__, \ __FUNCTION__, ##__VA_ARGS__); \ abort(); \ } // Vulkan result helper macro #define CHECK_VK(call) \ { \ VkResult res = call; \ if (res != VK_SUCCESS) { \ Abort("Error in vulkan: %d", res); \ } \ } // Macro for getting and checking the results of Instance extension functions. #define VK_GET_INSTANCE_PROC_ADDR(fp, instance, entryPoint) \ static PFN_##entryPoint fp; \ \ if (fp == NULL) { \ fp = (PFN_##entryPoint)vkGetInstanceProcAddr(instance, #entryPoint); \ if (fp == NULL) { \ Abort("Error getting VkInstance function: " #entryPoint); \ } \ } // Macro for getting and checking the results of Device extension functions. #define VK_GET_DEVICE_PROC_ADDR(fp, device, entryPoint) \ static PFN_##entryPoint fp; \ \ if (fp == NULL) { \ fp = (PFN_##entryPoint)vkGetDeviceProcAddr(device, #entryPoint); \ if (fp == NULL) { \ Abort("Error getting VkDevice function: " #entryPoint); \ } \ } // Simple alloc array macro to clean-up the code #define ALLOC_ARRAY(num, type) (type*)malloc((num) * sizeof(type)); #define VK_DBG_OFF 0 #define VK_DBG_ON 1 #define VK_DBG_REPORT_INFO VK_DBG_OFF #define VK_DBG_REPORT_WARN VK_DBG_OFF #define VK_DBG_REPORT_PERF VK_DBG_OFF #define VK_DBG_REPORT_DEBUG VK_DBG_ON #define VK_DBG_REPORT_ERROR VK_DBG_ON // Simple Debug Report Callback. VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallback( VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) { if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) { #if (VK_DBG_REPORT_INFO == VK_DBG_ON) PRINT_LINE("[%s] : %s (%d)", "INFO", pMessage, messageCode); #endif } else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) { #if (VK_DBG_REPORT_WARN == VK_DBG_ON) PRINT_LINE("[%s] : %s (%d)", "WARN", pMessage, messageCode); #endif } else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) { #if (VK_DBG_REPORT_PERF == VK_DBG_ON) PRINT_LINE("[%s] : %s (%d)", "PERF", pMessage, messageCode); #endif } else if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) { #if (VK_DBG_REPORT_ERROR == VK_DBG_ON) PRINT_LINE("[%s] : %s (%d)", "ERROR", pMessage, messageCode); #endif return VK_TRUE; } else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) { #if (VK_DBG_REPORT_DEBUG == VK_DBG_ON) PRINT_LINE("[%s] : %s (%d)", "DEBUG", pMessage, messageCode); #endif } return VK_FALSE; } typedef struct Vertex { float x, y, z, w; float r, g, b, a; } Vertex; VulkanTestHelper::VulkanTestHelper() : m_data({0}) { } void VulkanTestHelper::init() { initializeVulkan(); setupEnvironment(); createSwapchain(); createBuffers(); createGraphicsPipeline(); recordCommands(); } void VulkanTestHelper::uninit() { // First grab extension function pointers. VK_GET_INSTANCE_PROC_ADDR(fpDestroyDebugReportCallbackEXT, m_data.instance, vkDestroyDebugReportCallbackEXT); VK_GET_INSTANCE_PROC_ADDR(fpDestroySurfaceKHR, m_data.instance, vkDestroySurfaceKHR); VK_GET_DEVICE_PROC_ADDR(fpDestroySwapchainKHR, m_data.device, vkDestroySwapchainKHR); // Tearing down Vulkan is somewhat minor in this example. Mostly it's just // destroying the objects in the reverse order that they were created. Note: // When destroying these objects, wait for the device to become idle first, // otherwise it'll likely crash the program if resources are in flight when // destroyed. vkDeviceWaitIdle(m_data.device); vkDestroyCommandPool(m_data.device, m_data.command_pool, NULL); vkDestroyPipeline(m_data.device, m_data.graphics_pipeline, NULL); vkDestroyPipelineLayout(m_data.device, m_data.pipeline_layout, NULL); vkDestroyBuffer(m_data.device, m_data.vertex_buffer.buffer, NULL); vkFreeMemory(m_data.device, m_data.buffer_memory, NULL); for (uint32_t i = 0; i < SWAP_CHAIN_IMAGE_COUNT; ++i) { vkDestroyFramebuffer(m_data.device, m_data.swapchain_framebuffers[i], NULL); vkDestroyImageView(m_data.device, m_data.swapchain_image_views[i], NULL); } vkDestroyRenderPass(m_data.device, m_data.render_pass, NULL); fpDestroySwapchainKHR(m_data.device, m_data.swapchain, NULL); vkDestroySemaphore(m_data.device, m_data.image_available_semaphore, NULL); vkDestroySemaphore(m_data.device, m_data.render_finished_semaphore, NULL); vkDestroyDevice(m_data.device, NULL); fpDestroySurfaceKHR(m_data.instance, m_data.surface, NULL); if (m_data.debug_callback) fpDestroyDebugReportCallbackEXT(m_data.instance, m_data.debug_callback, NULL); vkDestroyInstance(m_data.instance, NULL); } void VulkanTestHelper::updateFrameToken(const GgpFrameToken& frameToken) { m_data.m_frame_token = frameToken; } void VulkanTestHelper::drawFrame() { // Cache the extension functions from the device to use the Surface and // present // to the screen. VK_GET_DEVICE_PROC_ADDR(fpAcquireNextImageKHR, m_data.device, vkAcquireNextImageKHR); VK_GET_DEVICE_PROC_ADDR(fpQueuePresentKHR, m_data.device, vkQueuePresentKHR); // First thing to do is aqcuire the image from the swapchain. This index // represents the index of usable image. Also, for the purposes of this // example, // it represents which command buffer should be queued. Pass a semaphore in, // to // be signaled when the image has been acquired from the swap chain. uint32_t image_index; CHECK_VK(fpAcquireNextImageKHR( m_data.device, m_data.swapchain, UINT64_MAX, m_data.image_available_semaphore, NULL, &image_index)); VkSemaphore wait_semaphores[1] = { m_data.image_available_semaphore }; VkPipelineStageFlags wait_stages[1] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; VkSemaphore signal_semaphors[1] = { m_data.render_finished_semaphore }; // Build the Queue submit struct with the command buffer to queue, the // semaphores to wait on before processing and the semaphores to signal when // the processing is over. VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; submit_info.pCommandBuffers = &m_data.command_buffers[image_index]; submit_info.commandBufferCount = 1; submit_info.pWaitSemaphores = wait_semaphores; submit_info.waitSemaphoreCount = 1; submit_info.pSignalSemaphores = signal_semaphors; submit_info.signalSemaphoreCount = 1; submit_info.pWaitDstStageMask = wait_stages; CHECK_VK( vkQueueSubmit(m_data.graphics_queue, 1, &submit_info, VK_NULL_HANDLE)); // Once the queue has processed the command buffer, present it to the screen. // In this case, the semaphore we wait on is the semaphore we asked to signal // above. VkPresentInfoKHR present_info = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; present_info.pSwapchains = &m_data.swapchain; present_info.swapchainCount = 1; present_info.pImageIndices = &image_index; present_info.pResults = NULL; present_info.pWaitSemaphores = signal_semaphors; present_info.waitSemaphoreCount = 1; // Attach a metadata struct to the Present call with the frame token // issued in the main loop. VkPresentFrameTokenGGP frame_token_metadata = { VK_STRUCTURE_TYPE_PRESENT_FRAME_TOKEN_GGP }; frame_token_metadata.frameToken = m_data.m_frame_token; present_info.pNext = &frame_token_metadata; CHECK_VK(vkQueuePresentKHR(m_data.present_queue, &present_info)); vkDeviceWaitIdle(m_data.device); } void VulkanTestHelper::initializeVulkan() { //IsRenderDocLoaded(); static const uint32_t kLayerCount = 1; static const char* kLayerNames[kLayerCount] = { "VK_LAYER_LUNARG_standard_validation" }; static const uint32_t kExtensionCount = 3; static const char* kExtensionNames[kExtensionCount] = { VK_EXT_DEBUG_REPORT_EXTENSION_NAME, VK_KHR_SURFACE_EXTENSION_NAME, VK_GGP_STREAM_DESCRIPTOR_SURFACE_EXTENSION_NAME }; VkApplicationInfo app_info = { VK_STRUCTURE_TYPE_APPLICATION_INFO }; app_info.apiVersion = VK_API_VERSION_1_0; app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0); app_info.pApplicationName = "ubiservices_test"; app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0); app_info.pEngineName = "GGP SDK"; VkInstanceCreateInfo create_info = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; create_info.pApplicationInfo = &app_info; create_info.enabledLayerCount = kLayerCount; create_info.ppEnabledLayerNames = kLayerNames; create_info.enabledExtensionCount = kExtensionCount; create_info.ppEnabledExtensionNames = kExtensionNames; CHECK_VK(vkCreateInstance(&create_info, NULL, &m_data.instance)); // For extension functions, those with suffixes such as KHR, GOOGLE, EXT, // etc, should be retrieved from the loader. vkCreateDebugReportCallbackEXT // will be retrieved from the Instance. Below there's a corresponding Destroy // function retrieved from the instance at the time of cleanup. VK_GET_INSTANCE_PROC_ADDR(fpCreateDebugReportCallbackEXT, m_data.instance, vkCreateDebugReportCallbackEXT); // The DebugReportCallbackEXT, will give us all the information available to // the various enabled layers and extensions to help track/debug the // application. VkDebugReportCallbackCreateInfoEXT debug_create_info = { VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT }; // Attach the callback, which can be found above. debug_create_info.pfnCallback = DebugReportCallback; debug_create_info.pUserData = this; // Select the level of debug information called back. debug_create_info.flags = VK_DEBUG_REPORT_INFORMATION_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_DEBUG_BIT_EXT; CHECK_VK(fpCreateDebugReportCallbackEXT( m_data.instance, &debug_create_info, NULL, &m_data.debug_callback)); // Once the instance is created, the surface can be created. The surface is // the platform specific system window. In GGP, it's the object that takes // your rendered data and compresses and transmits it to the client. VkStreamDescriptorSurfaceCreateInfoGGP surface_create_info = { VK_STRUCTURE_TYPE_STREAM_DESCRIPTOR_SURFACE_CREATE_INFO_GGP }; surface_create_info.streamDescriptor = kGgpPrimaryStreamDescriptor; VK_GET_INSTANCE_PROC_ADDR(fpCreateStreamDescriptorSurfaceGGP, m_data.instance, vkCreateStreamDescriptorSurfaceGGP); CHECK_VK(fpCreateStreamDescriptorSurfaceGGP( m_data.instance, &surface_create_info, NULL, &m_data.surface)); } void VulkanTestHelper::setupEnvironment() { VK_GET_INSTANCE_PROC_ADDR(fpGetPhysicalDeviceSurfaceSupportKHR, m_data.instance, vkGetPhysicalDeviceSurfaceSupportKHR); VK_GET_INSTANCE_PROC_ADDR(fpGetPhysicalDeviceSurfaceCapabilitiesKHR, m_data.instance, vkGetPhysicalDeviceSurfaceCapabilitiesKHR); VK_GET_INSTANCE_PROC_ADDR(fpGetPhysicalDeviceSurfaceFormatsKHR, m_data.instance, vkGetPhysicalDeviceSurfaceFormatsKHR); VK_GET_INSTANCE_PROC_ADDR(fpGetPhysicalDeviceSurfacePresentModesKHR, m_data.instance, vkGetPhysicalDeviceSurfacePresentModesKHR); // Enumerate physical devices associated with this instance. VkPhysicalDevice* devices; uint32_t device_count; CHECK_VK(vkEnumeratePhysicalDevices(m_data.instance, &device_count, NULL)); devices = ALLOC_ARRAY(device_count, VkPhysicalDevice); CHECK_VK( vkEnumeratePhysicalDevices(m_data.instance, &device_count, devices)); // On the GGP platform, the first device is the GPU. Select that to setup // the environment. VkPhysicalDevice ggp_device = devices[0]; free(devices); // Cache the features to enable. For the purposes of this example, enable all // features. VkPhysicalDeviceFeatures enable_features; vkGetPhysicalDeviceFeatures(ggp_device, &enable_features); // Cache the graphics and present queue indices. m_data.graphics_queue_idx = -1; m_data.present_queue_idx = -1; VkQueueFamilyProperties* queue_props; uint32_t queue_prop_count; vkGetPhysicalDeviceQueueFamilyProperties(ggp_device, &queue_prop_count, NULL); queue_props = ALLOC_ARRAY(queue_prop_count, VkQueueFamilyProperties); vkGetPhysicalDeviceQueueFamilyProperties(ggp_device, &queue_prop_count, queue_props); for (uint32_t i = 0; i < queue_prop_count; ++i) { VkQueueFamilyProperties* prop = &queue_props[i]; if (prop->queueCount > 0) { // If this queue supports graphics, cache the index of the queue. if (prop->queueFlags & VK_QUEUE_GRAPHICS_BIT) { m_data.graphics_queue_idx = i; } // Use the function pointer retrieved from the instance above to query // 'Present' support. VkBool32 supports_present = VK_FALSE; CHECK_VK(fpGetPhysicalDeviceSurfaceSupportKHR( ggp_device, i, m_data.surface, &supports_present)); if (supports_present == VK_TRUE) { m_data.present_queue_idx = i; } if (m_data.graphics_queue_idx >= 0 && m_data.present_queue_idx >= 0) { break; } } } free(queue_props); if (m_data.graphics_queue_idx < 0 || m_data.present_queue_idx < 0) { Abort("Couldn't find Graphics Queue: [%d] or Present Queue: [%d}", m_data.graphics_queue_idx, m_data.present_queue_idx); } // Now gather the remaining information of the GGP surface to build the // Swapchain, ImageViews and Framebuffers below. CHECK_VK(fpGetPhysicalDeviceSurfaceCapabilitiesKHR( ggp_device, m_data.surface, &m_data.surface_capabilities)); VkSurfaceFormatKHR* surf_formats; uint32_t surf_format_count; CHECK_VK(fpGetPhysicalDeviceSurfaceFormatsKHR(ggp_device, m_data.surface, &surf_format_count, NULL)); surf_formats = ALLOC_ARRAY(surf_format_count, VkSurfaceFormatKHR); CHECK_VK(fpGetPhysicalDeviceSurfaceFormatsKHR( ggp_device, m_data.surface, &surf_format_count, surf_formats)); VkPresentModeKHR* surf_present_modes; uint32_t surf_mode_count; CHECK_VK(fpGetPhysicalDeviceSurfacePresentModesKHR( ggp_device, m_data.surface, &surf_mode_count, NULL)); surf_present_modes = ALLOC_ARRAY(surf_mode_count, VkPresentModeKHR); CHECK_VK(fpGetPhysicalDeviceSurfacePresentModesKHR( ggp_device, m_data.surface, &surf_mode_count, surf_present_modes)); // Build the SwapchainCreateInfo struct with the queried attributes. // This allows the application to recreate the swap chain multiple times // if there are any changes to the surface, i.e. the window is resized, etc, // without having to query the devices again. m_data.swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; m_data.swapchain_create_info.pNext = NULL; m_data.swapchain_create_info.flags = 0; m_data.swapchain_create_info.imageExtent = m_data.surface_capabilities.currentExtent; m_data.swapchain_create_info.preTransform = m_data.surface_capabilities.currentTransform; m_data.swapchain_create_info.imageFormat = surf_formats[0].format; m_data.swapchain_create_info.imageColorSpace = surf_formats[0].colorSpace; m_data.swapchain_create_info.presentMode = surf_present_modes[0]; m_data.swapchain_create_info.imageArrayLayers = 1; m_data.swapchain_create_info.minImageCount = SWAP_CHAIN_IMAGE_COUNT; m_data.swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; m_data.swapchain_create_info.clipped = VK_TRUE; m_data.swapchain_create_info.oldSwapchain = VK_NULL_HANDLE; m_data.swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; m_data.swapchain_create_info.surface = m_data.surface; // If the graphics queue and the present queue don't have the same index, // then the swapchain would need to be shared between both queues. In that // case, imageSharingMode would be CONCURRENT. Otherwise, it's exclusive // and the queueFamilyIndices is ignored. if (m_data.graphics_queue_idx != m_data.present_queue_idx) { uint32_t queue_family_indices[2] = { (uint32_t)m_data.graphics_queue_idx, (uint32_t)m_data.present_queue_idx }; m_data.swapchain_create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; m_data.swapchain_create_info.pQueueFamilyIndices = queue_family_indices; m_data.swapchain_create_info.queueFamilyIndexCount = 2; } else { m_data.swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; m_data.swapchain_create_info.pQueueFamilyIndices = NULL; m_data.swapchain_create_info.queueFamilyIndexCount = 0; } free(surf_formats); free(surf_present_modes); // Query the devices' memory properties. This example caches the heap that // has // the attributes of device local, host visible and coherent memory. VkPhysicalDeviceMemoryProperties mem_props; vkGetPhysicalDeviceMemoryProperties(ggp_device, &mem_props); const VkMemoryPropertyFlags heap_flag = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; for (uint32_t i = 0, end = mem_props.memoryTypeCount; i < end; ++i) { VkMemoryPropertyFlags props = mem_props.memoryTypes[i].propertyFlags; if ((props & heap_flag) == heap_flag) { m_data.memory_type_index = mem_props.memoryTypes[i].heapIndex; } } // Rather than enumerating all physical device extensions, we only request // the minimum we need. If they're unavailable, initialization will fail // anyway. const uint32_t total_ext_names = 3; const char** ext_names = ALLOC_ARRAY(total_ext_names, const char*); ext_names[0] = VK_KHR_SWAPCHAIN_EXTENSION_NAME; ext_names[1] = VK_KHR_MAINTENANCE1_EXTENSION_NAME; ext_names[2] = VK_GGP_FRAME_TOKEN_EXTENSION_NAME; // Build the VkQueueCreateInfo, which sets up the queues used on the device. // Only submit CreateInfo for unique queue indices. If graphics and present // are the same index only create the graphics. uint32_t queues_to_create_count = m_data.graphics_queue_idx != m_data.present_queue_idx ? 2 : 1; VkDeviceQueueCreateInfo* queue_create_infos = ALLOC_ARRAY(queues_to_create_count, VkDeviceQueueCreateInfo); uint32_t queue_indices[2] = { (uint32_t)m_data.graphics_queue_idx, (uint32_t)m_data.present_queue_idx }; float priorities = 1.0f; for (uint32_t i = 0; i < queues_to_create_count; ++i) { queue_create_infos[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queue_create_infos[i].pNext = NULL; queue_create_infos[i].flags = 0; queue_create_infos[i].queueFamilyIndex = queue_indices[i]; queue_create_infos[i].queueCount = 1; queue_create_infos[i].pQueuePriorities = &priorities; } VkDeviceCreateInfo device_create_info = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; device_create_info.queueCreateInfoCount = queues_to_create_count; device_create_info.pQueueCreateInfos = queue_create_infos; device_create_info.pEnabledFeatures = &enable_features; // Note: Layers for VkDevice deprecated. device_create_info.ppEnabledLayerNames = NULL; device_create_info.enabledLayerCount = 0; device_create_info.enabledExtensionCount = total_ext_names; device_create_info.ppEnabledExtensionNames = ext_names; // After blindly loading extensions here without checking for support first, // handle the // potential error result with a useful log message. VkResult res = vkCreateDevice(ggp_device, &device_create_info, NULL, &m_data.device); if (res == VK_ERROR_EXTENSION_NOT_PRESENT) { PRINT_LINE("vkCreateDevice failed: required extension(s) not supported by " "physical device."); PRINT_LINE("Required extensions:"); for (uint32_t i = 0; i < device_create_info.enabledExtensionCount; ++i) { PRINT_LINE("- %s", device_create_info.ppEnabledExtensionNames[i]); } abort(); } else if (res != VK_SUCCESS) { Abort("vkCreateDevice failed: %d", res); } free(queue_create_infos); free(ext_names); // Once the device is created, cache the requested queues by index. vkGetDeviceQueue(m_data.device, m_data.graphics_queue_idx, 0, &m_data.graphics_queue); vkGetDeviceQueue(m_data.device, m_data.present_queue_idx, 0, &m_data.present_queue); // Create a command pool that is used to allocate device commands, which are // submitted to these queues. VkCommandPoolCreateInfo command_pool_create_info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; // Need the queue index that CommandBuffers from this pool will be submitted // to. In this case, all CommandBuffers allocated from this Pool will be // submitted to the Graphics Queue. command_pool_create_info.queueFamilyIndex = m_data.graphics_queue_idx; CHECK_VK(vkCreateCommandPool(m_data.device, &command_pool_create_info, NULL, &m_data.command_pool)); // Finally, create the two Semaphores needed by each frame to synchronize // swapchain access and present ready. VkSemaphoreCreateInfo semaphore_create_info = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; CHECK_VK(vkCreateSemaphore(m_data.device, &semaphore_create_info, NULL, &m_data.image_available_semaphore)); CHECK_VK(vkCreateSemaphore(m_data.device, &semaphore_create_info, NULL, &m_data.render_finished_semaphore)); } void VulkanTestHelper::createSwapchain() { VK_GET_DEVICE_PROC_ADDR(fpCreateSwapchainKHR, m_data.device, vkCreateSwapchainKHR); VK_GET_DEVICE_PROC_ADDR(fpGetSwapchainImagesKHR, m_data.device, vkGetSwapchainImagesKHR); // Use the cached swapchainCreateInfo struct to create the Swapchain. CHECK_VK(vkCreateSwapchainKHR(m_data.device, &m_data.swapchain_create_info, NULL, &m_data.swapchain)); // Next, the Swapchain Images need to be cached. uint32_t reported_images_count; CHECK_VK(vkGetSwapchainImagesKHR(m_data.device, m_data.swapchain, &reported_images_count, NULL)); if (SWAP_CHAIN_IMAGE_COUNT != reported_images_count) { Abort("Number of reported Swapchain images not the same as expected."); } CHECK_VK(vkGetSwapchainImagesKHR(m_data.device, m_data.swapchain, &reported_images_count, m_data.swapchain_images)); // Now create the RenderPass. This object contains all the relevant // color/depth // /stencil/multisampling/attachment information for rendering as well as // possible dependencies on other subpasses. VkRenderPassCreateInfo render_pass_create_info = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO }; VkAttachmentDescription attach_description = {}; attach_description.format = m_data.swapchain_create_info.imageFormat; attach_description.samples = VK_SAMPLE_COUNT_1_BIT; attach_description.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attach_description.storeOp = VK_ATTACHMENT_STORE_OP_STORE; attach_description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attach_description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attach_description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attach_description.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; render_pass_create_info.pAttachments = &attach_description; render_pass_create_info.attachmentCount = 1; // Then describe the layout and bind points of each attachment. VkAttachmentReference color_attachment_reference = {}; color_attachment_reference.attachment = 0; color_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass_description = {}; subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass_description.pColorAttachments = &color_attachment_reference; subpass_description.colorAttachmentCount = 1; render_pass_create_info.subpassCount = 1; render_pass_create_info.pSubpasses = &subpass_description; CHECK_VK(vkCreateRenderPass(m_data.device, &render_pass_create_info, NULL, &m_data.render_pass)); // To use the swapchain images as targets for rendering, ImageViews and // Framebuffers for each SwapchainImage must be created. for (uint32_t i = 0; i < SWAP_CHAIN_IMAGE_COUNT; ++i) { // The ImageView needs an Image to use, as well as the format and // components // to expect. VkImageViewCreateInfo image_view_create_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; image_view_create_info.image = m_data.swapchain_images[i]; image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; image_view_create_info.format = m_data.swapchain_create_info.imageFormat; image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; image_view_create_info.subresourceRange.baseArrayLayer = 0; image_view_create_info.subresourceRange.layerCount = 1; image_view_create_info.subresourceRange.baseMipLevel = 0; image_view_create_info.subresourceRange.levelCount = 1; image_view_create_info.components.r = VK_COMPONENT_SWIZZLE_R; image_view_create_info.components.g = VK_COMPONENT_SWIZZLE_G; image_view_create_info.components.b = VK_COMPONENT_SWIZZLE_B; image_view_create_info.components.a = VK_COMPONENT_SWIZZLE_A; CHECK_VK(vkCreateImageView(m_data.device, &image_view_create_info, NULL, &m_data.swapchain_image_views[i])); // Now that the ImageView is created, set it as the attachment to the // Framebuffer. Also, set the RenderPass, created above, which explains // the attachements. VkImageView attachments[1] = { m_data.swapchain_image_views[i] }; VkFramebufferCreateInfo framebuffer_create_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO }; framebuffer_create_info.renderPass = m_data.render_pass; framebuffer_create_info.pAttachments = attachments; framebuffer_create_info.attachmentCount = 1; framebuffer_create_info.width = m_data.surface_capabilities.currentExtent.width; framebuffer_create_info.height = m_data.surface_capabilities.currentExtent.height; framebuffer_create_info.layers = 1; CHECK_VK(vkCreateFramebuffer(m_data.device, &framebuffer_create_info, NULL, &m_data.swapchain_framebuffers[i])); } // Finally, allocate Command buffers for each SwapchainImage to allow one // frame // to be processed while another is being presented. VkCommandBufferAllocateInfo command_buffer_alloc_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; command_buffer_alloc_info.commandPool = m_data.command_pool; command_buffer_alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; command_buffer_alloc_info.commandBufferCount = SWAP_CHAIN_IMAGE_COUNT; CHECK_VK(vkAllocateCommandBuffers( m_data.device, &command_buffer_alloc_info, m_data.command_buffers)); } void VulkanTestHelper::createBuffers() { VkDeviceSize total_mem_size = 0; // This sample only uses one buffer (a vertex buffer with the triangle // list). To create the buffer, set the queues that'll consume them and the // size in bytes and usage of the Buffer. VkBufferCreateInfo buffer_create_info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; buffer_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; buffer_create_info.size = sizeof(Vertex) * 3; buffer_create_info.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; CHECK_VK(vkCreateBuffer(m_data.device, &buffer_create_info, NULL, &m_data.vertex_buffer.buffer)); // After the buffer is created, query the Memory Requirements of the buffer. // This contains the alignment requirements and the device size in bytes. VkMemoryRequirements mem_reqs; vkGetBufferMemoryRequirements(m_data.device, m_data.vertex_buffer.buffer, &mem_reqs); m_data.vertex_buffer.offset = 0; m_data.vertex_buffer.size = mem_reqs.size; total_mem_size = m_data.vertex_buffer.size; // Allocate the memory from the earlier chosen heap. VkMemoryAllocateInfo mem_alloc_info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; mem_alloc_info.allocationSize = total_mem_size; mem_alloc_info.memoryTypeIndex = m_data.memory_type_index; CHECK_VK(vkAllocateMemory(m_data.device, &mem_alloc_info, NULL, &m_data.buffer_memory)); CHECK_VK(vkBindBufferMemory(m_data.device, m_data.vertex_buffer.buffer, m_data.buffer_memory, m_data.vertex_buffer.offset)); // Simple triangle vertices Vertex vertices[3] = { {0.0f, 0.75f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f}, {-0.5f, -0.75f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f}, {0.5f, -0.75f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f} }; // Once the memory for the buffers is bound, map the memory to ingest the // vert // data. uint8_t* mapped_mem; CHECK_VK(vkMapMemory(m_data.device, m_data.buffer_memory, 0, VK_WHOLE_SIZE, 0, (void**)&mapped_mem)); memcpy(mapped_mem, &vertices[0].x, sizeof(Vertex) * 3); vkUnmapMemory(m_data.device, m_data.buffer_memory); } void VulkanTestHelper::createGraphicsPipeline() { // The Graphics Pipeline is the largest struct yet. It contains all of the // programmable and fixed-function settings for the hardware. VkGraphicsPipelineCreateInfo pipeline_create_info = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; // There are 2 shader stages, Vertex and Pixel, in this example. First, the // ShaderModules are created. Then they're used to setup the // PipelineShaderStage // structure. const uint32_t kShaderStages = 2; VkPipelineShaderStageCreateInfo shader_stage_create_info[kShaderStages] = {}; // Create the Vertex ShaderModule. const char* vs_relative_file_path = "shaders/hello_ggp_vs.spv"; VkShaderModule vs_shader_module; createShaderModule(vs_relative_file_path, &vs_shader_module); shader_stage_create_info[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shader_stage_create_info[0].pNext = NULL; shader_stage_create_info[0].flags = 0; shader_stage_create_info[0].pName = "VSMain"; shader_stage_create_info[0].module = vs_shader_module; shader_stage_create_info[0].stage = VK_SHADER_STAGE_VERTEX_BIT; shader_stage_create_info[0].pSpecializationInfo = NULL; // Create the Pixel ShaderModule. const char* ps_relative_file_path = "shaders/hello_ggp_ps.spv"; VkShaderModule ps_shader_module; createShaderModule(ps_relative_file_path, &ps_shader_module); shader_stage_create_info[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shader_stage_create_info[1].pNext = NULL; shader_stage_create_info[1].flags = 0; shader_stage_create_info[1].pName = "PSMain"; shader_stage_create_info[1].module = ps_shader_module; shader_stage_create_info[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; shader_stage_create_info[1].pSpecializationInfo = NULL; pipeline_create_info.stageCount = kShaderStages; pipeline_create_info.pStages = shader_stage_create_info; // Next is the unfortunately named PipelineLayout, which is concerned with // ShaderStage resources. Since there's no DescriptorSets or PushConstants in // use, create it with nulls. VkPipelineLayoutCreateInfo pipeline_layout_create_info = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; CHECK_VK(vkCreatePipelineLayout(m_data.device, &pipeline_layout_create_info, NULL, &m_data.pipeline_layout)); pipeline_create_info.layout = m_data.pipeline_layout; // Lay out the Vertex attribs and binding information. These structures // describe the vertices that the ShaderStage will consume. VkVertexInputBindingDescription vert_binding_desc = {}; vert_binding_desc.binding = 0; vert_binding_desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; vert_binding_desc.stride = sizeof(Vertex); VkVertexInputAttributeDescription vert_attribute_desc[2]; vert_attribute_desc[0].binding = 0; vert_attribute_desc[0].format = VK_FORMAT_R32G32B32A32_SFLOAT; vert_attribute_desc[0].location = 0; vert_attribute_desc[0].offset = offsetof(Vertex, x); vert_attribute_desc[1].binding = 0; vert_attribute_desc[1].format = VK_FORMAT_R32G32B32A32_SFLOAT; vert_attribute_desc[1].location = 1; vert_attribute_desc[1].offset = offsetof(Vertex, r); VkPipelineVertexInputStateCreateInfo vert_input_create_info = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; vert_input_create_info.pVertexBindingDescriptions = &vert_binding_desc; vert_input_create_info.vertexBindingDescriptionCount = 1; vert_input_create_info.pVertexAttributeDescriptions = vert_attribute_desc; vert_input_create_info.vertexAttributeDescriptionCount = 2; pipeline_create_info.pVertexInputState = &vert_input_create_info; // Next, describe the primitive to use in assembling the vertices. VkPipelineInputAssemblyStateCreateInfo input_assembly_create_info = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO }; input_assembly_create_info.primitiveRestartEnable = VK_FALSE; input_assembly_create_info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; pipeline_create_info.pInputAssemblyState = &input_assembly_create_info; VkPipelineViewportStateCreateInfo viewport_state_create_info = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; viewport_state_create_info.pViewports = NULL; viewport_state_create_info.viewportCount = 1; viewport_state_create_info.pScissors = NULL; viewport_state_create_info.scissorCount = 1; pipeline_create_info.pViewportState = &viewport_state_create_info; // Describe the rasterization state for the pipeline. VkPipelineRasterizationStateCreateInfo raster_create_info = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; raster_create_info.polygonMode = VK_POLYGON_MODE_FILL; raster_create_info.lineWidth = 1.0f; raster_create_info.cullMode = VK_CULL_MODE_NONE; raster_create_info.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; pipeline_create_info.pRasterizationState = &raster_create_info; // Dynamic states are those that can be updated during the command buffer // pass, // such as viewport and scissor. const uint32_t kDynamicStatesCount = 2; VkDynamicState states[kDynamicStatesCount] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamic_state_create_info = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO }; dynamic_state_create_info.pDynamicStates = states; dynamic_state_create_info.dynamicStateCount = kDynamicStatesCount; pipeline_create_info.pDynamicState = &dynamic_state_create_info; // Describe the multi-sample state. For the purposes of this example, // multi-sampling will be disabled. VkPipelineMultisampleStateCreateInfo multi_sample_create_info = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO }; multi_sample_create_info.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; pipeline_create_info.pMultisampleState = &multi_sample_create_info; // Describe the depth/stencil state. For the purposes of this example, // depth/stencil will be disabled VkPipelineDepthStencilStateCreateInfo depth_stencil_create_info = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO }; depth_stencil_create_info.maxDepthBounds = 1.0f; depth_stencil_create_info.minDepthBounds = 0.0f; depth_stencil_create_info.depthCompareOp = VK_COMPARE_OP_ALWAYS; pipeline_create_info.pDepthStencilState = &depth_stencil_create_info; // Describe the blending state for each attachment. For this example, there's // only one attachment and blending is disabled but need to set the color // write // mask. VkPipelineColorBlendAttachmentState color_blend_attach_state = {}; color_blend_attach_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; VkPipelineColorBlendStateCreateInfo color_blend_create_info = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; color_blend_create_info.logicOpEnable = VK_FALSE; color_blend_create_info.logicOp = VK_LOGIC_OP_NO_OP; color_blend_create_info.attachmentCount = 1; color_blend_create_info.pAttachments = &color_blend_attach_state; color_blend_create_info.blendConstants[0] = 0.0f; color_blend_create_info.blendConstants[1] = 0.0f; color_blend_create_info.blendConstants[2] = 0.0f; color_blend_create_info.blendConstants[3] = 0.0f; pipeline_create_info.pColorBlendState = &color_blend_create_info; // Set the RenderPass that was created earlier. pipeline_create_info.renderPass = m_data.render_pass; pipeline_create_info.subpass = 0; // Tessellation is disabled because it isn't used. pipeline_create_info.pTessellationState = NULL; // From the spec, deriving from a parent pipeline will allow it to be more // efficient to switch/bind between children of the same parent. pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE; pipeline_create_info.basePipelineIndex = 0; CHECK_VK(vkCreateGraphicsPipelines(m_data.device, NULL, 1, &pipeline_create_info, NULL, &m_data.graphics_pipeline)); // Once the pipeline is built, destroy the shader modules. vkDestroyShaderModule(m_data.device, vs_shader_module, NULL); vkDestroyShaderModule(m_data.device, ps_shader_module, NULL); } void VulkanTestHelper::recordCommands() { // This example builds a command buffer per frame to keep one processing // while // the other is presenting. VkRect2D render_area = {}; render_area.offset.x = 0; render_area.offset.y = 0; render_area.extent.width = m_data.surface_capabilities.currentExtent.width; render_area.extent.height = m_data.surface_capabilities.currentExtent.height; VkClearValue clear_value = {}; // clear_value.color.float32[0] = 1.0f; // clear_value.color.float32[1] = 0.375f; // clear_value.color.float32[2] = 0.308f; clear_value.color.float32[0] = 0.0f; clear_value.color.float32[1] = 0.75f; clear_value.color.float32[2] = 1.0f; clear_value.color.float32[3] = 1.0f; // Note: the negative viewport here requires the VK_KHR_maintenance1 // extension, or similar functionality. VkViewport viewport = {}; viewport.x = 0.0f; viewport.y = (float)m_data.surface_capabilities.currentExtent.height; viewport.width = (float)m_data.surface_capabilities.currentExtent.width; viewport.height = -(float)m_data.surface_capabilities.currentExtent.height; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; VkRect2D scissor = {}; scissor.offset.x = 0; scissor.offset.y = 0; scissor.extent.width = m_data.surface_capabilities.currentExtent.width; scissor.extent.height = m_data.surface_capabilities.currentExtent.height; VkBuffer buffers[1] = { m_data.vertex_buffer.buffer }; VkDeviceSize offsets[1] = {}; // Iterate the number of images. for (uint32_t i = 0; i < SWAP_CHAIN_IMAGE_COUNT; ++i) { // Begin the Command Buffer recording. VkCommandBufferBeginInfo command_begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; command_begin_info.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; command_begin_info.pInheritanceInfo = NULL; CHECK_VK(vkBeginCommandBuffer(m_data.command_buffers[i], &command_begin_info)); // Begin the render pass using the framebuffer, renderPass, render area, // and // clear value. VkRenderPassBeginInfo render_pass_begin_info = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; render_pass_begin_info.renderPass = m_data.render_pass; render_pass_begin_info.renderArea = render_area; render_pass_begin_info.framebuffer = m_data.swapchain_framebuffers[i]; render_pass_begin_info.pClearValues = &clear_value; render_pass_begin_info.clearValueCount = 1; vkCmdBeginRenderPass(m_data.command_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); // Bind the vertices with the expected layout given to the pipeline. vkCmdBindVertexBuffers(m_data.command_buffers[i], 0, 1, buffers, offsets); // Bind the pipeline created above. Make sure it's bound to the correct // bind // point. vkCmdBindPipeline(m_data.command_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, m_data.graphics_pipeline); vkCmdSetViewport(m_data.command_buffers[i], 0, 1, &viewport); vkCmdSetScissor(m_data.command_buffers[i], 0, 1, &scissor); // Then draw. In this case we have 3 vertices and 1 instance of it. vkCmdDraw(m_data.command_buffers[i], 3, 1, 0, 0); // End the render pass. vkCmdEndRenderPass(m_data.command_buffers[i]); // And finally, if there are no other commands, end the command buffer // recording. CHECK_VK(vkEndCommandBuffer(m_data.command_buffers[i])); } } void VulkanTestHelper::createShaderModule(const char* relative_file_path, VkShaderModule* module) { uint8_t* binary = NULL; size_t file_size = 0; //#if !defined(INCLUDE_SPV_IN_EXECUTABLE) // char file_path[256]; // const char* asset_dir = GgpAssetDirectory(); // // memset(file_path, 0, sizeof(file_path)); // strcat(file_path, asset_dir); // strcat(file_path, relative_file_path); // // FILE* file = fopen(file_path, "r"); // if (!file) Abort("could not open file for reading: \"%s\"", file_path); // fseek(file, 0L, SEEK_END); // file_size = ftell(file); // rewind(file); // binary = ALLOC_ARRAY(file_size, uint8_t); // fread((char*)binary, 1, file_size, file); // fclose(file); // const bool should_free_binary = true; //#else if (!strcmp("shaders/hello_ggp_vs.spv", relative_file_path)) { binary = hello_ggp_vs_spv; file_size = hello_ggp_vs_spv_size; } else { binary = hello_ggp_ps_spv; file_size = hello_ggp_ps_spv_size; } const bool should_free_binary = false; //#endif // Create the ShaderModule by casting the binary to a uint32_t ptr and // setting // the codesize to the size in bytes. VkShaderModuleCreateInfo shader_module_create_info = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; shader_module_create_info.pCode = (uint32_t*)binary; shader_module_create_info.codeSize = file_size; CHECK_VK(vkCreateShaderModule(m_data.device, &shader_module_create_info, NULL, module)); if (should_free_binary) { free(binary); } } #endif // defined(US_TARGET_GGP)