JD2022-TU1/main/extern/UbiServices/custom/tests/ggp/vulkanTestHelper.cpp

1027 lines
No EOL
44 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// (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 <ggp_c/media_stream_types.h>
#include <ggp_c/frame_token_types.h>
#include <vulkan/vulkan_ggp.h>
#include <ggp_c/application.h>
#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)