/****************************************************************/ /* *** *** *** *** *** *** *** ****** **** *** *** *** *** *** *** *** ** *** *** ***** *** *** *** *** *** *** ***** ******** *** ***** *** *** *** *** ******* *** ** *** *** *** **** ***** ***** ******* *** *** *** *** *** *** 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) */ #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; 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." ); } // Chapter 9 // Frame buffer { // define our attachment points VkAttachmentDescription pass[1] = { }; 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 ar = {}; ar.attachment = 0; ar.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // create the one main subpass of our renderpass: VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &ar; subpass.pDepthStencilAttachment = NULL; // create our main renderpass: VkRenderPassCreateInfo rpci = {}; rpci.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; rpci.attachmentCount = 1; 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[1] = {0}; 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 = 1; 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 }; { // create our vertex buffer: VkBufferCreateInfo vertexInputBufferInfo = {}; vertexInputBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; // size in bytes of our data // a single triangle so we have 3 vertices vertexInputBufferInfo.size = sizeof(vertex) * 3; 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, 3, 1, 0, 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(..)