/****************************************************************/ /* *** *** *** *** *** *** *** ****** **** *** *** *** *** *** *** *** ** *** *** ***** *** *** *** *** *** *** ***** ******** *** ***** *** *** *** *** ******* *** ** *** *** *** **** ***** ***** ******* *** *** *** *** *** *** Vulkan Graphics API: in 20 Minutes (Coffee Break Series) Beginners Taste Minimalistic examples to build upon and experiment with to develop an understanding of the underlying API /* /****************************************************************/ /* About: Self contained demo (i.e., all the code is in main.cpp) - however! You also need to include the two binary shader files (vertex and shader) '''' + Extension chapter 01 - modify the original book code to include a include depth buffer '''' */ #define _CRT_SECURE_NO_WARNINGS // use fopen(..) #include #include #include using namespace std; // Windows version of the Vulkan API #define VK_USE_PLATFORM_WIN32_KHR // this is if we do not declared prototypes, // and want to load dynamically! //#define VK_NO_PROTOTYPES #include "vulkan.h" #pragma comment(lib, "C:\\VulkanSDK\\1.0.17.0\\Bin32\\vulkan-1.lib") // or static lib doesn't depend on dll //#pragma comment(lib, "VKstatic.1.lib") // Do we want to enable the added Vuklan debug? #define ENABLE_VULKAN_DEBUG_CALLBACK // For variable argument functions ,e.g., dprintf(..), #include #include #include // vsprintf_s //Saving debug information to a log file/screen/.. inline void dprintf(const char *fmt, ...) { va_list parms; static char buf[2048]; // Try to print in the allocated space. va_start(parms, fmt); vsprintf_s(buf, fmt, parms); va_end(parms); // Write the information out to a txt file #if 0 FILE *fp = fopen("output.txt", "a+"); fprintf(fp, "%s", buf); fclose(fp); #endif // Output to the visual studio window OutputDebugStringA( buf ); }// End dprintf(..) // Debug defines (custom asserts) #if defined(_WIN32) #define DBG_ASSERT(f) { if(!(f)){ __debugbreak(); }; } #else #define DBG_ASSERT(f) { if(!(f)){ DBG_ASSERT(0); }; } #endif #define DBG_VALID(f) { if( (f)!=(f) ){DBG_ASSERT(false);} } #define DBG_ASSERT_MSG( val, errmsg ) \ if(!(val)){ \ DBG_ASSERT( false ) \ } #define DBG_ASSERT_VULKAN( val ) \ DBG_ASSERT( VK_SUCCESS == val ) #define DBG_ASSERT_VULKAN_MSG( val, errmsg ) \ if(!((VK_SUCCESS == val))){ \ DBG_ASSERT( false ) \ } #ifdef ENABLE_VULKAN_DEBUG_CALLBACK // Debug callback // Set this function as a debug callback when we initialize // Vulkan to let us know if something went wrong VKAPI_ATTR VkBool32 VKAPI_CALL MyDebugReportCallback( VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData ) { dprintf( pLayerPrefix ); dprintf( " " ); dprintf( pMessage ); dprintf( "\n" ); DBG_ASSERT(false); return VK_FALSE; }// End MyDebugReportCallback(..) #endif // Initial window dimensions // Chapter 3 int g_width = 800; int g_height = 600; // Chapter 3 HWND g_windowHandle = NULL; // Chapter 4 VkInstance g_instance = NULL; VkSurfaceKHR g_surface = NULL; // Chapter 5 VkPhysicalDevice g_physicalDevice = VK_NULL_HANDLE; // Chapter 6 VkDevice g_device = NULL; // Chapter 7 VkSwapchainKHR g_swapChain = NULL; // Chapter 7 VkImage* g_presentImages = NULL; // Chapter 7 VkImageView* g_presentImageViews = NULL; // Chapter 8 VkCommandBuffer g_drawCmdBuffer = NULL; VkQueue g_presentQueue = NULL; // Chapter 9 VkRenderPass g_renderPass = NULL; VkFramebuffer* g_frameBuffers = NULL; // Chapter 11 VkBuffer g_vertexInputBuffer = NULL; // Chapter 12 VkShaderModule g_vertexShaderModule = NULL; VkShaderModule g_fragmentShaderModule = NULL; float g_cameraZ = 10.0f; float g_cameraZDir = -1.0f; float * g_projectionMatrix = NULL; float * g_viewMatrix = NULL; float * g_modelMatrix = NULL; VkBuffer g_buffer = NULL; VkDeviceMemory g_memory = NULL; // Chapter 13 VkDescriptorSet g_descriptorSet = NULL; VkDescriptorSetLayout g_setLayout = NULL; // Chapater 14 VkPipeline g_pipeline = NULL; VkPipelineLayout g_pipelineLayout = NULL; // Extension A (Depth Buffer) VkImage g_depthImage = NULL; VkImageView g_depthImageView = NULL; // Multiple triangles (cube) // A cube has 6 faces with 2 triangles each, so this makes 6*2=12 triangles, and 12*3 vertices static const float g_verticesForCube[] = { -1.0f,-1.0f,-1.0f, // triangle 1 : begin -1.0f,-1.0f, 1.0f, -1.0f, 1.0f, 1.0f, // triangle 1 : end 1.0f, 1.0f,-1.0f, // triangle 2 : begin -1.0f,-1.0f,-1.0f, -1.0f, 1.0f,-1.0f, // triangle 2 : end 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,-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, 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, 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, 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, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f,-1.0f, 1.0f }; const int g_numTriangles = 12; void VulkanCreate() { // Vulkan setup/initialization // Initialize VULKAN // // Chapter 4 // const char *layers[] = { "VK_LAYER_NV_optimus" }; #ifdef ENABLE_VULKAN_DEBUG_CALLBACK // access debug callbacks const char *extensions[] = { "VK_KHR_surface", "VK_KHR_win32_surface", "VK_EXT_debug_report"}; #else const char *extensions[] = { "VK_KHR_surface", "VK_KHR_win32_surface" }; #endif { VkApplicationInfo ai = { }; ai.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; ai.pApplicationName = "Hello Vulkan"; ai.engineVersion = 1; ai.apiVersion = VK_API_VERSION_1_0; VkInstanceCreateInfo ici = { }; ici.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; ici.flags = 0; ici.pNext = 0; ici.pApplicationInfo = &ai; ici.enabledLayerCount = 1; ici.ppEnabledLayerNames = layers; ici.enabledExtensionCount = 2; #ifdef ENABLE_VULKAN_DEBUG_CALLBACK // access debug callbacks ici.enabledExtensionCount = 3; #endif ici.ppEnabledExtensionNames = extensions; VkResult result = vkCreateInstance( &ici, NULL, &g_instance ); DBG_ASSERT_VULKAN_MSG( result, "Failed to create vulkan instance." ); DBG_ASSERT(g_instance!=NULL); } // Optional - if we want Vulkan to tell us if something is wrong // we must set the callback #ifdef ENABLE_VULKAN_DEBUG_CALLBACK { // Register our error logging function (defined at the top of the file) VkDebugReportCallbackEXT error_callback = VK_NULL_HANDLE; VkDebugReportCallbackEXT warning_callback = VK_NULL_HANDLE; PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT = NULL; *(void **)&vkCreateDebugReportCallbackEXT = vkGetInstanceProcAddr( g_instance, "vkCreateDebugReportCallbackEXT" ); DBG_ASSERT(vkCreateDebugReportCallbackEXT); VkDebugReportCallbackCreateInfoEXT cb_create_info = {}; cb_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT; cb_create_info.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT; cb_create_info.pfnCallback = &MyDebugReportCallback; VkResult result = vkCreateDebugReportCallbackEXT(g_instance, &cb_create_info, nullptr, &error_callback); DBG_ASSERT_VULKAN_MSG(result, "vkCreateDebugReportCallbackEXT(ERROR) failed"); // Capture warning as well as errors cb_create_info.flags = VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT; cb_create_info.pfnCallback = &MyDebugReportCallback; result = vkCreateDebugReportCallbackEXT(g_instance, &cb_create_info, nullptr, &warning_callback); DBG_ASSERT_VULKAN_MSG(result, "vkCreateDebugReportCallbackEXT(WARN) failed"); } #endif // We need to define what type of surface we'll be // rendering to - this will depend on our computer // and operating system // Chapter 4 { // setup parameters for our new windows // surface we'll render into: VkWin32SurfaceCreateInfoKHR sci = {}; sci.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; // parameter is NULL, GetModuleHandle returns a handle // to the file used to create the calling process sci.hinstance = GetModuleHandle(NULL); // We should use our `created' window handle (HWND) sci.hwnd = g_windowHandle; VkResult result = vkCreateWin32SurfaceKHR( g_instance, &sci, NULL, &g_surface ); DBG_ASSERT_VULKAN_MSG( result, "Could not create surface." ); DBG_ASSERT(g_surface!=NULL); } // Chapter 5 { // Query how many devices are present in the system uint32_t deviceCount = 0; VkResult result = vkEnumeratePhysicalDevices(g_instance, &deviceCount, NULL); DBG_ASSERT_VULKAN_MSG(result, "Failed to query the number of physical devices present"); // There has to be at least one device present DBG_ASSERT_MSG(0 != deviceCount, "Couldn't detect any device present with Vulkan support"); // Get the physical devices vector physicalDevices(deviceCount); result = vkEnumeratePhysicalDevices(g_instance, &deviceCount, &physicalDevices[0]); DBG_ASSERT_VULKAN_MSG(result, "Faied to enumerate physical devices present"); DBG_ASSERT(physicalDevices.size()>0); // Use the first available device g_physicalDevice = physicalDevices[0]; // Enumerate all physical devices and print out the details for (uint32_t i = 0; i < deviceCount; ++i) { VkPhysicalDeviceProperties deviceProperties; memset(&deviceProperties, 0, sizeof deviceProperties); vkGetPhysicalDeviceProperties(physicalDevices[i], &deviceProperties); dprintf("Driver Version: %d\n", deviceProperties.driverVersion); dprintf("Device Name: %s\n", deviceProperties.deviceName); dprintf("Device Type: %d\n", deviceProperties.deviceType); dprintf("API Version: %d.%d.%d\n", (deviceProperties.apiVersion>>22)&0x3FF, (deviceProperties.apiVersion>>12)&0x3FF, (deviceProperties.apiVersion&0xFFF)); }//End for i } // Chapter 6 { // Fill up the physical device memory properties: VkPhysicalDeviceMemoryProperties memoryProperties; vkGetPhysicalDeviceMemoryProperties( g_physicalDevice, &memoryProperties ); // Here's where we initialize our queues VkDeviceQueueCreateInfo queueCreateInfo = {}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; // Use the first queue family in the family list queueCreateInfo.queueFamilyIndex = 0; queueCreateInfo.queueCount = 1; float queuePriorities[] = { 1.0f }; queueCreateInfo.pQueuePriorities = queuePriorities; const char *deviceExtensions[] = { "VK_KHR_swapchain" }; VkDeviceCreateInfo dci = {}; dci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; // Set queue(s) into the device dci.queueCreateInfoCount = 1; dci.pQueueCreateInfos = &queueCreateInfo; dci.enabledLayerCount = 0; dci.ppEnabledLayerNames = layers; dci.enabledExtensionCount = 1; dci.ppEnabledExtensionNames = deviceExtensions; VkPhysicalDeviceFeatures features = {}; features.shaderClipDistance = VK_TRUE; dci.pEnabledFeatures = &features; // Ideally, we'd want to enumerate and find the best // device, however, we just use the first device // `physicalDevices[0]' for our sample, which we // stored in the previous chapter VkResult result = vkCreateDevice( g_physicalDevice, &dci, NULL, &g_device ); DBG_ASSERT_VULKAN_MSG( result, "Failed to create logical device!" ); } // Chapter 7 // Create swap-chain { // swap-chain creation: VkSurfaceCapabilitiesKHR surfaceCapabilities = {}; vkGetPhysicalDeviceSurfaceCapabilitiesKHR( g_physicalDevice, g_surface, &surfaceCapabilities ); VkExtent2D surfaceResolution = surfaceCapabilities.currentExtent; g_width = surfaceResolution.width; g_height = surfaceResolution.height; VkSwapchainCreateInfoKHR ssci = {}; ssci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; ssci.surface = g_surface; // We'll use 2 for `double' buffering ssci.minImageCount = 2; ssci.imageFormat = VK_FORMAT_B8G8R8A8_UNORM; ssci.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; ssci.imageExtent = surfaceResolution; ssci.imageArrayLayers = 1; ssci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; ssci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; ssci.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; ssci.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; ssci.presentMode = VK_PRESENT_MODE_MAILBOX_KHR; // If we want clipping outside the extents ssci.clipped = true; ssci.oldSwapchain = NULL; VkResult result = vkCreateSwapchainKHR( g_device, &ssci, NULL, &g_swapChain ); DBG_ASSERT_VULKAN_MSG( result, "Failed to create swapchain." ); } // Chapter 7 // Create our images 'double' buffering { uint32_t imageCount = 0; vkGetSwapchainImagesKHR( g_device, g_swapChain, &imageCount, NULL ); DBG_ASSERT(imageCount==2); // this should be 2 for double-buffering g_presentImages = new VkImage[imageCount]; // link the images to the swapchain VkResult result = vkGetSwapchainImagesKHR( g_device, g_swapChain, &imageCount, g_presentImages ); DBG_ASSERT_VULKAN_MSG( result, "Failed to create swap-chain images" ); } // Chapter 7 { // We have 2 for double buffering g_presentImageViews = new VkImageView[2]; for( uint32_t i = 0; i < 2; ++i ) { // create VkImageViews for our swap chain // VkImages buffers: VkImageViewCreateInfo ivci = {}; ivci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; ivci.viewType = VK_IMAGE_VIEW_TYPE_2D; ivci.format = VK_FORMAT_B8G8R8A8_UNORM; ivci.components.r = VK_COMPONENT_SWIZZLE_R; ivci.components.g = VK_COMPONENT_SWIZZLE_G; ivci.components.b = VK_COMPONENT_SWIZZLE_B; ivci.components.a = VK_COMPONENT_SWIZZLE_A; ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; ivci.subresourceRange.baseMipLevel = 0; ivci.subresourceRange.levelCount = 1; ivci.subresourceRange.baseArrayLayer = 0; ivci.subresourceRange.layerCount = 1; ivci.image = g_presentImages[i]; VkResult result = vkCreateImageView( g_device, &ivci, NULL, &g_presentImageViews[i] ); DBG_ASSERT_VULKAN_MSG( result, "Could not create ImageView." ); }// End for i } // Chapter 8 { uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(g_physicalDevice, &queueFamilyCount, NULL); vector familyProperties(queueFamilyCount); vkGetPhysicalDeviceQueueFamilyProperties(g_physicalDevice, &queueFamilyCount, &familyProperties[0]); // Print the families for (uint32_t j = 0; j < queueFamilyCount; ++j) { dprintf("Count of Queues: %d\n", familyProperties[j].queueCount); dprintf("Supported operationg on this queue:\n"); if (familyProperties[j].queueFlags & VK_QUEUE_GRAPHICS_BIT) dprintf("\t\t Graphics\n"); if (familyProperties[j].queueFlags & VK_QUEUE_COMPUTE_BIT) dprintf("\t\t Compute\n"); if (familyProperties[j].queueFlags & VK_QUEUE_TRANSFER_BIT) dprintf("\t\t Transfer\n"); if (familyProperties[j].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) dprintf("\t\t Sparse Binding\n"); }// End for j } // Chapter 8 // Give our device some commands (orders) { // we can now get the device queue we will be // submitting work to: vkGetDeviceQueue( g_device, 0, 0, &g_presentQueue ); // create our command buffers: VkCommandPoolCreateInfo cpci = {}; cpci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; cpci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; cpci.queueFamilyIndex = 0; VkCommandPool commandPool; VkResult result = vkCreateCommandPool( g_device, &cpci, NULL, &commandPool ); DBG_ASSERT_VULKAN_MSG( result, "Failed to create command pool." ); VkCommandBufferAllocateInfo cbai = {}; cbai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cbai.commandPool = commandPool; cbai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cbai.commandBufferCount = 1; result = vkAllocateCommandBuffers( g_device, &cbai, &g_drawCmdBuffer ); DBG_ASSERT_VULKAN_MSG( result, "Failed to allocate draw command buffer." ); } // Extension A (Depth Buffer) { // create a depth image: VkImageCreateInfo imageCreateInfo = {}; imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.format = VK_FORMAT_D16_UNORM; VkExtent3D f = { g_width, g_height, 1 }; imageCreateInfo.extent = f; // { context.width, context.height, 1 }; imageCreateInfo.mipLevels = 1; imageCreateInfo.arrayLayers = 1; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageCreateInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageCreateInfo.queueFamilyIndexCount = 0; imageCreateInfo.pQueueFamilyIndices = NULL; imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; VkResult result = vkCreateImage( g_device, &imageCreateInfo, NULL, &g_depthImage ); DBG_ASSERT_VULKAN_MSG( result, "Failed to create depth image." ); VkMemoryRequirements memoryRequirements = {}; vkGetImageMemoryRequirements( g_device, g_depthImage, &memoryRequirements ); // Allocate memory for our depth buffer VkMemoryAllocateInfo imageAllocateInfo = {}; imageAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; imageAllocateInfo.allocationSize = memoryRequirements.size; // memoryTypeBits is a bitfield where if bit i is set, it means that // the VkMemoryType i of the VkPhysicalDeviceMemoryProperties structure // satisfies the memory requirements: // read the device memory properties VkPhysicalDeviceMemoryProperties memoryProperties; vkGetPhysicalDeviceMemoryProperties( g_physicalDevice, &memoryProperties ); uint32_t memoryTypeBits = memoryRequirements.memoryTypeBits; VkMemoryPropertyFlags desiredMemoryFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; for( uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i ) { VkMemoryType memoryType = memoryProperties.memoryTypes[i]; if( memoryTypeBits & 1 ) { if( ( memoryType.propertyFlags & desiredMemoryFlags ) == desiredMemoryFlags ) { imageAllocateInfo.memoryTypeIndex = i; break; } } memoryTypeBits = memoryTypeBits >> 1; } VkDeviceMemory imageMemory = { 0 }; result = vkAllocateMemory( g_device, &imageAllocateInfo, NULL, &imageMemory ); DBG_ASSERT_VULKAN_MSG( result, "Failed to allocate device memory." ); result = vkBindImageMemory( g_device, g_depthImage, imageMemory, 0 ); DBG_ASSERT_VULKAN_MSG( result, "Failed to bind image memory." ); // before using this depth buffer we must change it's layout: { VkFence submitFence; VkFenceCreateInfo fenceCreateInfo = {}; fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; vkCreateFence( g_device, &fenceCreateInfo, NULL, &submitFence ); VkCommandBufferBeginInfo beginInfo = {}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer( g_drawCmdBuffer, &beginInfo ); VkImageMemoryBarrier layoutTransitionBarrier = {}; layoutTransitionBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; layoutTransitionBarrier.srcAccessMask = 0; layoutTransitionBarrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; layoutTransitionBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; layoutTransitionBarrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; layoutTransitionBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; layoutTransitionBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; layoutTransitionBarrier.image = g_depthImage; VkImageSubresourceRange resourceRange = { VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1 }; layoutTransitionBarrier.subresourceRange = resourceRange; vkCmdPipelineBarrier( g_drawCmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &layoutTransitionBarrier ); vkEndCommandBuffer( g_drawCmdBuffer ); VkPipelineStageFlags waitStageMash[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.waitSemaphoreCount = 0; submitInfo.pWaitSemaphores = NULL; submitInfo.pWaitDstStageMask = waitStageMash; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &g_drawCmdBuffer; submitInfo.signalSemaphoreCount = 0; submitInfo.pSignalSemaphores = NULL; result = vkQueueSubmit( g_presentQueue, 1, &submitInfo, submitFence ); vkWaitForFences( g_device, 1, &submitFence, VK_TRUE, UINT64_MAX ); vkResetFences( g_device, 1, &submitFence ); vkResetCommandBuffer( g_drawCmdBuffer, 0 ); } // create the depth image view: VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; VkImageViewCreateInfo imageViewCreateInfo = {}; imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; imageViewCreateInfo.image = g_depthImage; imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; imageViewCreateInfo.format = imageCreateInfo.format; VkComponentMapping g = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY }; imageViewCreateInfo.components = g; imageViewCreateInfo.subresourceRange.aspectMask = aspectMask; imageViewCreateInfo.subresourceRange.baseMipLevel = 0; imageViewCreateInfo.subresourceRange.levelCount = 1; imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; imageViewCreateInfo.subresourceRange.layerCount = 1; result = vkCreateImageView( g_device, &imageViewCreateInfo, NULL, &g_depthImageView ); DBG_ASSERT_VULKAN_MSG( result, "Failed to create image view." ); } // Chapter 9 // Frame buffer { // define our attachment points // 0 - colour screen buffer VkAttachmentDescription pass[2] = { }; pass[0].format = VK_FORMAT_B8G8R8A8_UNORM; pass[0].samples = VK_SAMPLE_COUNT_1_BIT; pass[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; pass[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; pass[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; pass[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; pass[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; pass[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentReference car = {}; car.attachment = 0; car.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // 1 - depth buffer pass[1].format = VK_FORMAT_D16_UNORM; pass[1].samples = VK_SAMPLE_COUNT_1_BIT; pass[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; pass[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; pass[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; pass[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; pass[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; pass[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkAttachmentReference dar = {}; dar.attachment = 1; dar.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; // create the one main subpass of our renderpass: VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &car; subpass.pDepthStencilAttachment = &dar; // create our main renderpass: VkRenderPassCreateInfo rpci = {}; rpci.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; rpci.attachmentCount = 2; // colour and depth rpci.pAttachments = pass; rpci.subpassCount = 1; rpci.pSubpasses = &subpass; VkResult result = vkCreateRenderPass( g_device, &rpci, NULL, &g_renderPass ); DBG_ASSERT_VULKAN_MSG( result, "Failed to create renderpass" ); // create our frame buffers: VkImageView frameBufferAttachments[2] = {0}; frameBufferAttachments[1] = g_depthImageView; VkFramebufferCreateInfo fbci = {}; fbci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; fbci.renderPass = g_renderPass; // must be equal to the attachment count on render pass fbci.attachmentCount = 2; fbci.pAttachments = frameBufferAttachments; fbci.width = g_width; fbci.height = g_height; fbci.layers = 1; // create a framebuffer per swap chain imageView: g_frameBuffers = new VkFramebuffer[ 2 ]; for( uint32_t i = 0; i < 2; ++i ) { frameBufferAttachments[0] = g_presentImageViews[ i ]; result = vkCreateFramebuffer( g_device, &fbci, NULL, &g_frameBuffers[i] ); DBG_ASSERT_VULKAN_MSG( result, "Failed to create framebuffer."); }// End for i } // Chapter 11 // Our handle to our created vertex buffer and // its data // Vertex info struct vertex { float x, y, z, w; // position float r, g, b; // color }; { const int numberOfVertices = sizeof( g_verticesForCube ) / (sizeof( float ) * 3); const int numberOfTrianges = numberOfVertices / 3; DBG_ASSERT(numberOfTrianges == g_numTriangles ); // create our vertex buffer: VkBufferCreateInfo vertexInputBufferInfo = {}; vertexInputBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; // size in bytes of our data // a single triangle requires 3 vertices vertexInputBufferInfo.size = sizeof(vertex) * numberOfVertices; vertexInputBufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; vertexInputBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; vertexInputBufferInfo.queueFamilyIndexCount = 0; vertexInputBufferInfo.pQueueFamilyIndices = NULL; VkResult result = vkCreateBuffer( g_device, &vertexInputBufferInfo, NULL, &g_vertexInputBuffer ); DBG_ASSERT_VULKAN_MSG( result, "Failed to create vertex input buffer." ); // allocate memory for buffers: VkMemoryRequirements vertexBufferMemoryRequirements = {}; vkGetBufferMemoryRequirements( g_device, g_vertexInputBuffer, &vertexBufferMemoryRequirements ); VkMemoryAllocateInfo bufferAllocateInfo = {}; bufferAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; bufferAllocateInfo.pNext = NULL; bufferAllocateInfo.allocationSize = vertexBufferMemoryRequirements.size; // read the device memory properties VkPhysicalDeviceMemoryProperties memoryProperties; vkGetPhysicalDeviceMemoryProperties( g_physicalDevice, &memoryProperties ); for( uint32_t i = 0; i = 1.0 ) aa = 0; // activate render pass: VkClearValue clearValue[] = { { 1.0f, aa, 1.0f, 1.0f }, { 1.0, 0.0 } }; VkRenderPassBeginInfo renderPassBeginInfo = {}; renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassBeginInfo.renderPass = g_renderPass; renderPassBeginInfo.framebuffer = g_frameBuffers[ nextImageIdx ]; VkOffset2D a = {0, 0}; VkExtent2D b = {g_width, g_height}; VkRect2D c = {a,b}; renderPassBeginInfo.renderArea = c; renderPassBeginInfo.clearValueCount = 2; renderPassBeginInfo.pClearValues = clearValue; vkCmdBeginRenderPass( g_drawCmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE ); { // to come later - call draw commands // to present geometry data/triangles } vkCmdEndRenderPass( g_drawCmdBuffer ); } vkEndCommandBuffer( g_drawCmdBuffer ); // present: VkFence renderFence; VkFenceCreateInfo fenceCreateInfo = {}; fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; vkCreateFence( g_device, &fenceCreateInfo, NULL, &renderFence ); VkSubmitInfo si = {}; si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; si.waitSemaphoreCount = 0; si.pWaitSemaphores = VK_NULL_HANDLE; si.pWaitDstStageMask = NULL; si.commandBufferCount = 1; si.pCommandBuffers = &g_drawCmdBuffer; si.signalSemaphoreCount = 0; si.pSignalSemaphores = VK_NULL_HANDLE; vkQueueSubmit( g_presentQueue, 1, &si, renderFence ); vkWaitForFences( g_device, 1, &renderFence, VK_TRUE, UINT64_MAX ); vkDestroyFence( g_device, renderFence, NULL ); VkPresentInfoKHR pi = {}; pi.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; pi.pNext = NULL; pi.waitSemaphoreCount = 0; pi.pWaitSemaphores = VK_NULL_HANDLE; pi.swapchainCount = 1; pi.pSwapchains = &g_swapChain; pi.pImageIndices = &nextImageIdx; pi.pResults = NULL; vkQueuePresentKHR( g_presentQueue, &pi ); #endif #if 1 // Chapter 14 // Oscillates the triangle backwards and // forwards (towards and away from the // camera) if ( g_cameraZ <= 1 ) { g_cameraZ = 1; g_cameraZDir = 1; } else if ( g_cameraZ >= 10 ) { g_cameraZ = 10; g_cameraZDir = -1; } g_cameraZ += g_cameraZDir * 0.01f; g_viewMatrix[11] = g_cameraZ; // update shader uniforms: void *matrixMapped; vkMapMemory( g_device, g_memory, 0, VK_WHOLE_SIZE, 0, &matrixMapped ); memcpy( matrixMapped, g_projectionMatrix, sizeof(float) * 16 ); memcpy( ((float *)matrixMapped + 16), g_viewMatrix, sizeof(float) * 16 ); memcpy( ((float *)matrixMapped + 32), g_modelMatrix, sizeof(float) * 16 ); VkMappedMemoryRange memoryRange = {}; memoryRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; memoryRange.memory = g_memory; memoryRange.offset = 0; memoryRange.size = VK_WHOLE_SIZE; vkFlushMappedMemoryRanges( g_device, 1, &memoryRange ); vkUnmapMemory( g_device, g_memory ); uint32_t nextImageIdx; vkAcquireNextImageKHR( g_device, g_swapChain, UINT64_MAX, VK_NULL_HANDLE, VK_NULL_HANDLE, &nextImageIdx ); VkCommandBufferBeginInfo beginInfo = {}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer( g_drawCmdBuffer, &beginInfo ); // barrier for reading from uniform buffer after all // writing is done: VkMemoryBarrier uniformMemoryBarrier = {}; uniformMemoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; uniformMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; uniformMemoryBarrier.dstAccessMask = VK_ACCESS_UNIFORM_READ_BIT; vkCmdPipelineBarrier( g_drawCmdBuffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 1, &uniformMemoryBarrier, 0, NULL, 0, NULL ); // change image layout VkImageMemoryBarrier layoutTransitionBarrier = {}; layoutTransitionBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; layoutTransitionBarrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; layoutTransitionBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; layoutTransitionBarrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; layoutTransitionBarrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; layoutTransitionBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; layoutTransitionBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; layoutTransitionBarrier.image = g_presentImages[ nextImageIdx ]; VkImageSubresourceRange resourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; layoutTransitionBarrier.subresourceRange = resourceRange; vkCmdPipelineBarrier( g_drawCmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &layoutTransitionBarrier ); // activate render pass: VkClearValue clearValue[] = { { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0, 0.0 } }; VkRenderPassBeginInfo renderPassBeginInfo = {}; renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassBeginInfo.renderPass = g_renderPass; renderPassBeginInfo.framebuffer = g_frameBuffers[ nextImageIdx ]; VkOffset2D a={0, 0}; VkExtent2D b ={g_width, g_height}; VkRect2D c = {a,b}; renderPassBeginInfo.renderArea = c; renderPassBeginInfo.clearValueCount = 2; renderPassBeginInfo.pClearValues = clearValue; vkCmdBeginRenderPass( g_drawCmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE ); // bind the graphics pipeline to the command buffer. // Any vkDraw command afterwards is affected by this // pipeline. vkCmdBindPipeline( g_drawCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, g_pipeline ); // take care of dynamic state: VkViewport viewport = { 0, 0, (float)g_width, (float)g_height, 0, 1 }; vkCmdSetViewport( g_drawCmdBuffer, 0, 1, &viewport ); VkRect2D scissor = { 0, 0, g_width, g_height }; vkCmdSetScissor( g_drawCmdBuffer, 0, 1, &scissor); // bind our shader parameters: vkCmdBindDescriptorSets( g_drawCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, g_pipelineLayout, 0, 1, &g_descriptorSet, 0, NULL ); // render the triangle: VkDeviceSize offsets = { 0 }; vkCmdBindVertexBuffers( g_drawCmdBuffer, 0, 1, &g_vertexInputBuffer, &offsets ); vkCmdDraw( g_drawCmdBuffer, // vertex count g_numTriangles * 3, // instance count g_numTriangles, // first index 0, // first instance 0 ); vkCmdEndRenderPass( g_drawCmdBuffer ); // change layout back to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR VkImageMemoryBarrier prePresentBarrier = {}; prePresentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; prePresentBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; prePresentBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; prePresentBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; prePresentBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; prePresentBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; prePresentBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; VkImageSubresourceRange d = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; prePresentBarrier.subresourceRange = d; prePresentBarrier.image = g_presentImages[ nextImageIdx ]; vkCmdPipelineBarrier( g_drawCmdBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &prePresentBarrier ); vkEndCommandBuffer( g_drawCmdBuffer ); // present: VkFence renderFence; VkFenceCreateInfo fenceCreateInfo = {}; fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; vkCreateFence( g_device, &fenceCreateInfo, NULL, &renderFence ); VkPipelineStageFlags waitStageMash = { VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT }; VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.waitSemaphoreCount = 0; submitInfo.pWaitSemaphores = VK_NULL_HANDLE; submitInfo.pWaitDstStageMask = &waitStageMash; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &g_drawCmdBuffer; submitInfo.signalSemaphoreCount = 0; submitInfo.pSignalSemaphores = VK_NULL_HANDLE; vkQueueSubmit( g_presentQueue, 1, &submitInfo, renderFence ); vkWaitForFences( g_device, 1, &renderFence, VK_TRUE, UINT64_MAX ); vkDestroyFence( g_device, renderFence, NULL ); VkPresentInfoKHR presentInfo = {}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.pNext = NULL; presentInfo.waitSemaphoreCount = 0; presentInfo.pWaitSemaphores = VK_NULL_HANDLE; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = &g_swapChain; presentInfo.pImageIndices = &nextImageIdx; presentInfo.pResults = NULL; vkQueuePresentKHR( g_presentQueue, &presentInfo ); #endif }// End VulkanRender(..) // Win32 message callback LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch( uMsg ) { // called when we close the application case WM_CLOSE: { // posts a WM_QUIT message PostQuitMessage( 0 ); } break; // windows lets us know we need to redraw case WM_PAINT: { // Override so drawing is managed // by us VulkanRender(); } break; default: { break; } break; }// End switch(..) // a pass-through for now. We will return to this // callback return DefWindowProc(hwnd, uMsg, wParam, lParam); }// End WindowProc(..) // Win32 Program Entry Point // Chapter 3 int CALLBACK WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { // defines our window properties WNDCLASSEX windowClass = {}; windowClass.cbSize = sizeof(WNDCLASSEX); windowClass.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW; windowClass.lpfnWndProc = WindowProc; windowClass.hInstance = hInstance; windowClass.lpszClassName = "VulkanWindowClass"; RegisterClassEx( &windowClass ); g_windowHandle = CreateWindowEx( NULL, "VulkanWindowClass", "Hello Vulkan", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, g_width, g_height, NULL, NULL, hInstance, NULL ); DBG_ASSERT(g_windowHandle!=NULL); VulkanCreate(); MSG msg; bool done = false; while( !done ) { // Continually force the window to be // redrawn as long as no other Win32 // messages are pending PeekMessage( &msg, NULL, NULL, NULL, PM_REMOVE ); if( msg.message == WM_QUIT ) { done = true; } else { TranslateMessage( &msg ); DispatchMessage( &msg ); } // We constantly tell windows to refresh the // screen - i.e., to send a WM_PAINT message RedrawWindow( g_windowHandle, NULL, NULL, RDW_INTERNALPAINT ); } VulkanRelease(); // return the last message we got from PeekMessage // before quitting return msg.wParam;; }// End WinMain(..)