|  | // Copyright 2019 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | // | 
|  | // | 
|  | // | 
|  |  | 
|  | #include "vk_app_state.h" | 
|  |  | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #include "utils.h" | 
|  | #include "vk_strings.h" | 
|  | #include "vk_surface.h" | 
|  | #include "vk_utils.h" | 
|  |  | 
|  | #if VK_USE_PLATFORM_FUCHSIA | 
|  | #include <lib/async-loop/default.h> | 
|  | #include <lib/async-loop/loop.h> | 
|  | #include <lib/trace-provider/provider.h> | 
|  | #endif | 
|  |  | 
|  | // | 
|  | // Generic const char * vector. Used for extensions and layout names. | 
|  | // | 
|  |  | 
|  | typedef struct | 
|  | { | 
|  | uint32_t      count; | 
|  | uint32_t      capacity; | 
|  | const char ** items; | 
|  | } StringList; | 
|  |  | 
|  | #define STRING_LIST_INITIALIZER                                                                    \ | 
|  | {                                                                                                \ | 
|  | } | 
|  |  | 
|  | void | 
|  | string_list_append(StringList * list, const char * value) | 
|  | { | 
|  | ASSERT_MSG(value != NULL, "Invalid NULL string value.\n"); | 
|  | if (list->count == list->capacity) | 
|  | { | 
|  | uint32_t      new_capacity = list->capacity + (list->capacity >> 1) + 4; | 
|  | const char ** new_items    = realloc(list->items, new_capacity * sizeof(list->items[0])); | 
|  | ASSERT_MSG(new_items != NULL, "Out of memory.\n"); | 
|  | list->capacity = new_capacity; | 
|  | list->items    = new_items; | 
|  | } | 
|  | list->items[list->count++] = value; | 
|  | } | 
|  |  | 
|  | bool | 
|  | string_list_contains(const StringList * list, const char * value) | 
|  | { | 
|  | ASSERT_MSG(value != NULL, "Invalid NULL string value.\n"); | 
|  | for (uint32_t nn = 0; nn < list->count; ++nn) | 
|  | { | 
|  | if (!strcmp(list->items[nn], value)) | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void | 
|  | string_list_add(StringList * list, const char * value) | 
|  | { | 
|  | ASSERT_MSG(value != NULL, "Invalid NULL string value.\n"); | 
|  | if (!string_list_contains(list, value)) | 
|  | string_list_append(list, value); | 
|  | } | 
|  |  | 
|  | void | 
|  | string_list_add_n(StringList * list, uint32_t count, const char * const * values) | 
|  | { | 
|  | for (uint32_t nn = 0; nn < count; ++nn) | 
|  | string_list_add(list, values[nn]); | 
|  | } | 
|  |  | 
|  | void | 
|  | string_list_free(StringList * list) | 
|  | { | 
|  | if (list->capacity > 0) | 
|  | { | 
|  | free(list->items); | 
|  | list->items    = NULL; | 
|  | list->capacity = 0u; | 
|  | list->count    = 0u; | 
|  | } | 
|  | } | 
|  |  | 
|  | // | 
|  | // Instance specific info | 
|  | // | 
|  |  | 
|  | typedef struct | 
|  | { | 
|  | uint32_t            count; | 
|  | VkLayerProperties * layers; | 
|  | } LayersList; | 
|  |  | 
|  | static void | 
|  | layers_list_init(LayersList * list) | 
|  | { | 
|  | vk(EnumerateInstanceLayerProperties(&list->count, NULL)); | 
|  | list->layers = calloc(list->count, sizeof(list->layers[0])); | 
|  | vk(EnumerateInstanceLayerProperties(&list->count, list->layers)); | 
|  | } | 
|  |  | 
|  | static bool | 
|  | layers_list_contains(const LayersList * list, const char * name) | 
|  | { | 
|  | for (uint32_t nn = 0; nn < list->count; ++nn) | 
|  | { | 
|  | if (!strcmp(list->layers[nn].layerName, name)) | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static void | 
|  | layers_list_destroy(LayersList * list) | 
|  | { | 
|  | if (list->count) | 
|  | { | 
|  | free(list->layers); | 
|  | list->layers = NULL; | 
|  | list->count  = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | typedef struct | 
|  | { | 
|  | uint32_t                count; | 
|  | VkExtensionProperties * extensions; | 
|  | } ExtensionsList; | 
|  |  | 
|  | static void | 
|  | extensions_list_init(ExtensionsList * list, const char * layer_name) | 
|  | { | 
|  | vk(EnumerateInstanceExtensionProperties(layer_name, &list->count, NULL)); | 
|  | if (list->count) | 
|  | { | 
|  | list->extensions = calloc(list->count, sizeof(list->extensions[0])); | 
|  | vk(EnumerateInstanceExtensionProperties(layer_name, &list->count, list->extensions)); | 
|  | } | 
|  | } | 
|  |  | 
|  | static bool | 
|  | extensions_list_contains(const ExtensionsList * list, const char * name) | 
|  | { | 
|  | for (uint32_t nn = 0; nn < list->count; ++nn) | 
|  | { | 
|  | if (!strcmp(list->extensions[nn].extensionName, name)) | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static void | 
|  | extensions_list_destroy(ExtensionsList * list) | 
|  | { | 
|  | if (list->count) | 
|  | { | 
|  | free(list->extensions); | 
|  | list->extensions = NULL; | 
|  | list->count      = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | typedef struct | 
|  | { | 
|  | LayersList       layers; | 
|  | ExtensionsList   extensions; | 
|  | ExtensionsList * layer_extensions; | 
|  |  | 
|  | } InstanceInfo; | 
|  |  | 
|  | static InstanceInfo | 
|  | instance_info_create(void) | 
|  | { | 
|  | InstanceInfo info = {}; | 
|  |  | 
|  | layers_list_init(&info.layers); | 
|  | extensions_list_init(&info.extensions, NULL); | 
|  | info.layer_extensions = calloc(info.layers.count, sizeof(info.layer_extensions[0])); | 
|  | for (uint32_t nn = 0; nn < info.layers.count; ++nn) | 
|  | extensions_list_init(&info.layer_extensions[nn], info.layers.layers[nn].layerName); | 
|  |  | 
|  | return info; | 
|  | } | 
|  |  | 
|  | static void | 
|  | instance_info_destroy(InstanceInfo * info) | 
|  | { | 
|  | if (info->layers.count > 0) | 
|  | { | 
|  | for (uint32_t nn = 0; nn < info->layers.count; ++nn) | 
|  | extensions_list_destroy(&info->layer_extensions[nn]); | 
|  |  | 
|  | free(info->layer_extensions); | 
|  | info->layer_extensions = NULL; | 
|  | } | 
|  |  | 
|  | layers_list_destroy(&info->layers); | 
|  | extensions_list_destroy(&info->extensions); | 
|  | } | 
|  |  | 
|  | static bool | 
|  | instance_info_has_layer(const InstanceInfo * info, const char * layer_name) | 
|  | { | 
|  | return layers_list_contains(&info->layers, layer_name); | 
|  | } | 
|  |  | 
|  | static bool | 
|  | instance_info_has_extension(const InstanceInfo * info, const char * extension_name) | 
|  | { | 
|  | if (extensions_list_contains(&info->extensions, extension_name)) | 
|  | return true; | 
|  |  | 
|  | for (uint32_t nn = 0; nn < info->layers.count; ++nn) | 
|  | { | 
|  | if (extensions_list_contains(&info->layer_extensions[nn], extension_name)) | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool | 
|  | instance_info_validate_layers_and_extensions(const InstanceInfo * info, | 
|  | const StringList *   layers, | 
|  | const StringList *   extensions) | 
|  | { | 
|  | bool success = true; | 
|  |  | 
|  | // Check layers. | 
|  | for (uint32_t nn = 0; nn < layers->count; ++nn) | 
|  | { | 
|  | const char * name = layers->items[nn]; | 
|  | if (!instance_info_has_layer(info, name)) | 
|  | { | 
|  | fprintf(stderr, "Missing Vulkan layer: %s\n", name); | 
|  | success = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Check extensions. | 
|  | for (uint32_t nn = 0; nn < extensions->count; ++nn) | 
|  | { | 
|  | const char * name = extensions->items[nn]; | 
|  | if (!instance_info_has_extension(info, name)) | 
|  | { | 
|  | fprintf(stderr, "Missing Vulkan extensions: %s\n", name); | 
|  | success = false; | 
|  | } | 
|  | } | 
|  | return success; | 
|  | } | 
|  |  | 
|  | static void | 
|  | instance_info_print(const InstanceInfo * info) | 
|  | { | 
|  | printf("Instance info:\n"); | 
|  | for (uint32_t n = 0; n < info->layers.count; ++n) | 
|  | { | 
|  | printf("  layer %s (spec version %u)\n", | 
|  | info->layers.layers[n].layerName, | 
|  | info->layers.layers[n].specVersion); | 
|  | } | 
|  |  | 
|  | for (uint32_t n = 0; n < info->extensions.count; ++n) | 
|  | { | 
|  | printf("  extension %s (spec version %u)\n", | 
|  | info->extensions.extensions[n].extensionName, | 
|  | info->extensions.extensions[n].specVersion); | 
|  | } | 
|  |  | 
|  | for (uint32_t n = 0; n < info->layers.count; ++n) | 
|  | { | 
|  | const char *           layer_name = info->layers.layers[n].layerName; | 
|  | const ExtensionsList * list       = &info->layer_extensions[n]; | 
|  | for (uint32_t m = 0; m < list->count; ++m) | 
|  | { | 
|  | printf("  layer(%s) extension %s (spec version %u)\n", | 
|  | layer_name, | 
|  | list->extensions[m].extensionName, | 
|  | list->extensions[m].specVersion); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // | 
|  | // Physical device list. | 
|  | // | 
|  |  | 
|  | typedef struct | 
|  | { | 
|  | uint32_t           count; | 
|  | VkPhysicalDevice * devices; | 
|  | } GpuList; | 
|  |  | 
|  | static void | 
|  | gpu_list_init(GpuList * list, VkInstance instance) | 
|  | { | 
|  | list->count   = 0; | 
|  | list->devices = NULL; | 
|  |  | 
|  | vk(EnumeratePhysicalDevices(instance, &list->count, NULL)); | 
|  | if (list->count > 0) | 
|  | { | 
|  | list->devices = malloc(list->count * sizeof(list->devices[0])); | 
|  | vk(EnumeratePhysicalDevices(instance, &list->count, list->devices)); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | gpu_list_destroy(GpuList * list) | 
|  | { | 
|  | if (list->count) | 
|  | { | 
|  | free(list->devices); | 
|  | list->devices = NULL; | 
|  | list->count   = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | // | 
|  | // Device specific info | 
|  | // | 
|  |  | 
|  | typedef struct | 
|  | { | 
|  | uint32_t                extensions_count; | 
|  | VkExtensionProperties * extensions; | 
|  |  | 
|  | } DeviceInfo; | 
|  |  | 
|  | static DeviceInfo | 
|  | device_info_create(VkInstance instance, VkPhysicalDevice physical_device) | 
|  | { | 
|  | GET_VULKAN_INSTANCE_PROC_ADDR(vkEnumerateDeviceExtensionProperties); | 
|  |  | 
|  | DeviceInfo info = {}; | 
|  |  | 
|  | vk(EnumerateDeviceExtensionProperties(physical_device, NULL, &info.extensions_count, NULL)); | 
|  | info.extensions = calloc(info.extensions_count, sizeof(info.extensions[0])); | 
|  | vk(EnumerateDeviceExtensionProperties(physical_device, | 
|  | NULL, | 
|  | &info.extensions_count, | 
|  | info.extensions)); | 
|  |  | 
|  | return info; | 
|  | } | 
|  |  | 
|  | static bool | 
|  | device_info_has_extension(const DeviceInfo * info, const char * extension_name) | 
|  | { | 
|  | for (uint32_t nn = 0; nn < info->extensions_count; ++nn) | 
|  | { | 
|  | if (!strcmp(info->extensions[nn].extensionName, extension_name)) | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static void | 
|  | device_info_destroy(DeviceInfo * info) | 
|  | { | 
|  | if (info->extensions_count > 0) | 
|  | { | 
|  | free(info->extensions); | 
|  | info->extensions       = NULL; | 
|  | info->extensions_count = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | // | 
|  | // Pipeline cache support. | 
|  | // | 
|  |  | 
|  | #ifdef __Fuchsia__ | 
|  | #define PIPELINE_CACHE_FILE_PATH "/cache/.vk_cache" | 
|  | #else | 
|  | #define PIPELINE_CACHE_FILE_PATH "/tmp/vk_app_pipeline_cache" | 
|  | #endif | 
|  |  | 
|  | // Load file from |file_path| into a new heap block. On success return true | 
|  | // and sets |*p_data| and |*p_data_size| accordingly. Caller must call free() | 
|  | // to release the block later. On failure, simply return false. | 
|  | static bool | 
|  | file_read(const char * file_path, void ** p_data, size_t * p_data_size) | 
|  | { | 
|  | FILE * f = fopen(file_path, "rb"); | 
|  | if (!f) | 
|  | return false; | 
|  |  | 
|  | bool success = false; | 
|  | if (fseek(f, 0, SEEK_END) != 0) | 
|  | goto EXIT; | 
|  |  | 
|  | const size_t size = (size_t)ftell(f); | 
|  | if (size == 0) | 
|  | { | 
|  | *p_data      = NULL; | 
|  | *p_data_size = size; | 
|  | success      = true; | 
|  | goto EXIT; | 
|  | } | 
|  |  | 
|  | void * data = malloc(size); | 
|  | if (!data) | 
|  | goto EXIT; | 
|  |  | 
|  | if (fread(data, size, 1, f) != 1) | 
|  | { | 
|  | free(data); | 
|  | goto EXIT; | 
|  | } | 
|  |  | 
|  | *p_data      = data; | 
|  | *p_data_size = size; | 
|  | success      = true; | 
|  |  | 
|  | EXIT: | 
|  | fclose(f); | 
|  | return success; | 
|  | } | 
|  |  | 
|  | // Write |data_size| bytes from |data| into a file at |file_path|. | 
|  | // On success, return true. | 
|  | static bool | 
|  | file_write(const char * file_path, const void * data, size_t data_size) | 
|  | { | 
|  | FILE * f = fopen(file_path, "wb"); | 
|  | if (!f) | 
|  | return false; | 
|  |  | 
|  | bool success = fwrite(data, data_size, 1, f) == 1; | 
|  | fclose(f); | 
|  |  | 
|  | return success; | 
|  | } | 
|  |  | 
|  | // Try to load the pipeline cache data from |file_path| and return the | 
|  | // corresponding VkPipelineCache handle, or VK_NULL_HANDLE if there is no | 
|  | // file or if it could not be read of loaded properly. | 
|  | static VkPipelineCache | 
|  | pipeline_cache_load(const char *                  file_path, | 
|  | VkDevice                      device, | 
|  | const VkAllocationCallbacks * allocator) | 
|  | { | 
|  | void * data      = NULL; | 
|  | size_t data_size = 0; | 
|  |  | 
|  | // Ignore file read errors to create an empty cache in case of failure. | 
|  | (void)file_read(file_path, &data, &data_size); | 
|  |  | 
|  | VkPipelineCache pipeline_cache; | 
|  | VkResult        vk_res = vkCreatePipelineCache(device, | 
|  | &(const VkPipelineCacheCreateInfo){ | 
|  | .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, | 
|  | .initialDataSize = data_size, | 
|  | .pInitialData    = data, | 
|  | }, | 
|  | allocator, | 
|  | &pipeline_cache); | 
|  |  | 
|  | free(data); | 
|  | return (vk_res == VK_SUCCESS) ? pipeline_cache : VK_NULL_HANDLE; | 
|  | } | 
|  |  | 
|  | // Try to save a pipeline cache into |file_path|. Return true on success, | 
|  | // and false on failure. Note that if the handle is VK_NULL_HANDLE, this | 
|  | // does not do anything and returns true. | 
|  | static bool | 
|  | pipeline_cache_save(VkPipelineCache               pipeline_cache, | 
|  | const char *                  file_path, | 
|  | VkDevice                      device, | 
|  | const VkAllocationCallbacks * allocator) | 
|  | { | 
|  | if (pipeline_cache == VK_NULL_HANDLE) | 
|  | return true; | 
|  |  | 
|  | size_t   data_size = 0; | 
|  | VkResult res       = vkGetPipelineCacheData(device, pipeline_cache, &data_size, NULL); | 
|  | if (res != VK_SUCCESS) | 
|  | return false; | 
|  |  | 
|  | bool success = false; | 
|  |  | 
|  | void * data = NULL; | 
|  | if (!data_size) | 
|  | { | 
|  | success = true; | 
|  | goto EXIT; | 
|  | } | 
|  |  | 
|  | data = malloc(data_size); | 
|  | if (!data) | 
|  | goto EXIT; | 
|  |  | 
|  | res = vkGetPipelineCacheData(device, pipeline_cache, &data_size, data); | 
|  | if (res != VK_SUCCESS) | 
|  | { | 
|  | goto EXIT; | 
|  | } | 
|  |  | 
|  | success = file_write(file_path, data, data_size); | 
|  |  | 
|  | EXIT: | 
|  | free(data); | 
|  | vkDestroyPipelineCache(device, pipeline_cache, allocator); | 
|  | return success; | 
|  | } | 
|  |  | 
|  | // | 
|  | // RenderDoc capture support. | 
|  | // | 
|  |  | 
|  | #if !VK_USE_PLATFORM_FUCHSIA | 
|  | #define USE_RENDERDOC_CAPTURE 1 | 
|  | #endif | 
|  |  | 
|  | #if USE_RENDERDOC_CAPTURE | 
|  |  | 
|  | #include <dlfcn.h> | 
|  |  | 
|  | typedef void (*pRENDERDOC_GetAPIVersion)(int * major, int * minor, int * patch); | 
|  |  | 
|  | // Use this to convert a RenderDoc pointer type to void* | 
|  | #define IGNORED_PTR(type) void * | 
|  |  | 
|  | typedef enum | 
|  | { | 
|  | eRENDERDOC_API_Version_1_1_2 = 10102, | 
|  | } RENDERDOC_Version; | 
|  |  | 
|  | typedef void (*pRENDERDOC_StartFrameCapture)(void * device, void * wndHandle); | 
|  |  | 
|  | typedef uint32_t (*pRENDERDOC_EndFrameCapture)(void * device, void * wndHandle); | 
|  |  | 
|  | // eRENDERDOC_API_Version_1_1_2 | 
|  | typedef struct | 
|  | { | 
|  | pRENDERDOC_GetAPIVersion GetAPIVersion; | 
|  |  | 
|  | IGNORED_PTR(pRENDERDOC_SetCaptureOptionU32) SetCaptureOptionU32; | 
|  | IGNORED_PTR(pRENDERDOC_SetCaptureOptionF32) SetCaptureOptionF32; | 
|  |  | 
|  | IGNORED_PTR(pRENDERDOC_GetCaptureOptionU32) GetCaptureOptionU32; | 
|  | IGNORED_PTR(pRENDERDOC_GetCaptureOptionF32) GetCaptureOptionF32; | 
|  |  | 
|  | IGNORED_PTR(pRENDERDOC_SetFocusToggleKeys) SetFocusToggleKeys; | 
|  | IGNORED_PTR(pRENDERDOC_SetCaptureKeys) SetCaptureKeys; | 
|  |  | 
|  | IGNORED_PTR(pRENDERDOC_GetOverlayBits) GetOverlayBits; | 
|  | IGNORED_PTR(pRENDERDOC_MaskOverlayBits) MaskOverlayBits; | 
|  |  | 
|  | IGNORED_PTR(pRENDERDOC_Shutdown) Shutdown; | 
|  | IGNORED_PTR(pRENDERDOC_UnloadCrashHandler) UnloadCrashHandler; | 
|  |  | 
|  | IGNORED_PTR(pRENDERDOC_SetCaptureFilePathTemplate) SetCaptureFilePathTemplate; | 
|  | IGNORED_PTR(pRENDERDOC_GetCaptureFilePathTemplate) GetCaptureFilePathTemplate; | 
|  |  | 
|  | IGNORED_PTR(pRENDERDOC_GetNumCaptures) GetNumCaptures; | 
|  | IGNORED_PTR(pRENDERDOC_GetCapture) GetCapture; | 
|  |  | 
|  | IGNORED_PTR(pRENDERDOC_TriggerCapture) TriggerCapture; | 
|  |  | 
|  | IGNORED_PTR(pRENDERDOC_IsTargetControlConnected) IsTargetControlConnected; | 
|  | IGNORED_PTR(pRENDERDOC_LaunchReplayUI) LaunchReplayUI; | 
|  |  | 
|  | IGNORED_PTR(pRENDERDOC_SetActiveWindow) SetActiveWindow; | 
|  |  | 
|  | pRENDERDOC_StartFrameCapture StartFrameCapture; | 
|  |  | 
|  | IGNORED_PTR(pRENDERDOC_IsFrameCapturing) IsFrameCapturing; | 
|  |  | 
|  | pRENDERDOC_EndFrameCapture EndFrameCapture; | 
|  |  | 
|  | IGNORED_PTR(pRENDERDOC_TriggerMultiFrameCapture) TriggerMultiFrameCapture; | 
|  | } RENDERDOC_API_1_1_2; | 
|  |  | 
|  | #undef IGNORED_PTR | 
|  |  | 
|  | typedef int (*pRENDERDOC_GetAPI)(RENDERDOC_Version version, void ** outAPIPointers); | 
|  |  | 
|  | static RENDERDOC_API_1_1_2 * s_renderdoc_api = NULL; | 
|  | static void *                s_renderdoc_lib = NULL; | 
|  |  | 
|  | static void | 
|  | renderdoc_capture_setup(bool debug) | 
|  | { | 
|  | s_renderdoc_lib = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD); | 
|  | if (!s_renderdoc_lib) | 
|  | { | 
|  | if (debug) | 
|  | fprintf(stderr, "RenderDoc is not running, capture is impossible!\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | pRENDERDOC_GetAPI RENDERDOC_GetAPI = | 
|  | (pRENDERDOC_GetAPI)dlsym(s_renderdoc_lib, "RENDERDOC_GetAPI"); | 
|  |  | 
|  | int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void **)&s_renderdoc_api); | 
|  | ASSERT_MSG(ret == 1, "Could not retrieve RenderDoc API info!\n"); | 
|  |  | 
|  | if (!s_renderdoc_api) | 
|  | { | 
|  | if (debug) | 
|  | fprintf(stderr, "RenderDoc API not available, capture is impossible!\n"); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (debug) | 
|  | printf("ENABLING RENDERDOC CAPTURE\n"); | 
|  |  | 
|  | s_renderdoc_api->StartFrameCapture(NULL, NULL); | 
|  | } | 
|  |  | 
|  | static void | 
|  | renderdoc_capture_teardown(void) | 
|  | { | 
|  | if (s_renderdoc_api != NULL) | 
|  | { | 
|  | s_renderdoc_api->EndFrameCapture(NULL, NULL); | 
|  | } | 
|  | if (s_renderdoc_lib != NULL) | 
|  | dlclose(s_renderdoc_lib); | 
|  | } | 
|  |  | 
|  | #endif  // USE_RENDERDOC_CAPTURE | 
|  |  | 
|  | // | 
|  | // | 
|  | // | 
|  |  | 
|  | typedef struct | 
|  | { | 
|  | uint32_t                  count; | 
|  | VkQueueFamilyProperties * props; | 
|  | } QueueFamilies; | 
|  |  | 
|  | static void | 
|  | queue_families_init(QueueFamilies * families, VkPhysicalDevice physical_device) | 
|  | { | 
|  | vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &families->count, NULL); | 
|  | families->props = calloc(families->count, sizeof(families->props[0])); | 
|  | vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &families->count, families->props); | 
|  | } | 
|  |  | 
|  | static void | 
|  | queue_families_destroy(QueueFamilies * families) | 
|  | { | 
|  | if (families->count) | 
|  | { | 
|  | free(families->props); | 
|  | families->props = NULL; | 
|  | families->count = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | static bool | 
|  | queue_families_find_for_flags(const QueueFamilies * families, | 
|  | uint32_t              wanted_flags, | 
|  | uint32_t *            pFamily) | 
|  | { | 
|  | for (uint32_t nn = 0; nn < families->count; ++nn) | 
|  | { | 
|  | if (families->props[nn].queueCount > 0 && | 
|  | (families->props[nn].queueFlags & wanted_flags) == wanted_flags) | 
|  | { | 
|  | *pFamily = nn; | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // | 
|  | //  Debug report support | 
|  | // | 
|  |  | 
|  | // TODO(digit): Change this to use debug utils instead. | 
|  |  | 
|  | static VkBool32 VKAPI_PTR | 
|  | debug_report_callback(VkDebugReportFlagsEXT      flags, | 
|  | VkDebugReportObjectTypeEXT objectType, | 
|  | uint64_t                   object, | 
|  | size_t                     location, | 
|  | int32_t                    messageCode, | 
|  | char const *               pLayerPrefix, | 
|  | char const *               pMessage, | 
|  | void *                     pUserData) | 
|  | { | 
|  | #define LIST_LEVELS(macro) macro(WARNING) macro(PERFORMANCE_WARNING) macro(ERROR) macro(DEBUG) | 
|  |  | 
|  | const char * flag = NULL; | 
|  |  | 
|  | switch (flags) | 
|  | { | 
|  | // For each level, set |flag| appropriately. | 
|  | #define CASE_FOR_LEVEL(name)                                                                       \ | 
|  | case VK_DEBUG_REPORT_##name##_BIT_EXT:                                                           \ | 
|  | flag = "VK_DEBUG_REPORT_" #name "_BIT_EXT";                                                    \ | 
|  | break; | 
|  |  | 
|  | LIST_LEVELS(CASE_FOR_LEVEL) | 
|  |  | 
|  | #undef CASE_FOR_LEVEL | 
|  |  | 
|  | default:; | 
|  | } | 
|  | if (flag) | 
|  | { | 
|  | fprintf(stderr, "%s %s %s\n", flag, pLayerPrefix, pMessage); | 
|  | } | 
|  | return VK_FALSE; | 
|  | } | 
|  |  | 
|  | static bool | 
|  | setup_debug_report(VkInstance instance, VkDebugReportCallbackEXT * pCallback) | 
|  | { | 
|  | GET_VULKAN_INSTANCE_PROC_ADDR(vkCreateDebugReportCallbackEXT); | 
|  | if (!vkCreateDebugReportCallbackEXT) | 
|  | return false; | 
|  |  | 
|  | vk(CreateDebugReportCallbackEXT( | 
|  | instance, | 
|  | &(const VkDebugReportCallbackCreateInfoEXT){ | 
|  | .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, | 
|  | .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, | 
|  | .pfnCallback = debug_report_callback, | 
|  | .pUserData   = NULL, | 
|  | }, | 
|  | NULL, | 
|  | pCallback)); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // | 
|  | //  Fuchsia specific application state. | 
|  | // | 
|  |  | 
|  | #if VK_USE_PLATFORM_FUCHSIA | 
|  | typedef struct | 
|  | { | 
|  | async_loop_t *     async_loop; | 
|  | trace_provider_t * trace_provider; | 
|  | } FuchsiaState; | 
|  |  | 
|  | static void | 
|  | fuchsia_state_init(FuchsiaState * state, bool need_tracing) | 
|  | { | 
|  | zx_status_t status = | 
|  | async_loop_create(&kAsyncLoopConfigNoAttachToCurrentThread, &state->async_loop); | 
|  | ASSERT_MSG(status == ZX_OK, "async_loop_create failed.\n"); | 
|  |  | 
|  | status = async_loop_start_thread(state->async_loop, "loop", NULL); | 
|  | ASSERT_MSG(status == ZX_OK, "async_loop_start_thread failed.\n"); | 
|  |  | 
|  | // NOTE: Creating the trace provider can fail randomly on the CQ, so only | 
|  | // try to do it when needed (see https://crbug.com/fuchsia/41918). | 
|  | if (need_tracing) | 
|  | { | 
|  | async_dispatcher_t * dispatcher = async_loop_get_dispatcher(state->async_loop); | 
|  | state->trace_provider           = trace_provider_create_with_fdio(dispatcher); | 
|  | ASSERT_MSG(state->trace_provider != NULL, "trace_provider_create failed.\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | fuchsia_state_destroy(FuchsiaState * state) | 
|  | { | 
|  | if (state->trace_provider) | 
|  | trace_provider_destroy(state->trace_provider); | 
|  |  | 
|  | async_loop_destroy(state->async_loop); | 
|  | } | 
|  | #endif  // VK_USE_PLATFORM_FUCHSIA | 
|  |  | 
|  | // | 
|  | // | 
|  | // | 
|  |  | 
|  | typedef struct | 
|  | { | 
|  | VkDebugReportCallbackEXT drc; | 
|  | #if VK_USE_PLATFORM_FUCHSIA | 
|  | FuchsiaState fuchsia; | 
|  | #endif | 
|  |  | 
|  | } AppStateInternal; | 
|  |  | 
|  | bool | 
|  | vk_app_state_init(vk_app_state_t * app_state, const vk_app_state_config_t * config) | 
|  | { | 
|  | #if USE_RENDERDOC_CAPTURE | 
|  | renderdoc_capture_setup(config->enable_debug_report); | 
|  | #endif | 
|  |  | 
|  | *app_state = (const vk_app_state_t){}; | 
|  |  | 
|  | InstanceInfo instance_info = instance_info_create(); | 
|  | if (config->enable_debug_report) | 
|  | { | 
|  | // For debugging only! | 
|  | instance_info_print(&instance_info); | 
|  | } | 
|  |  | 
|  | // | 
|  | // create a Vulkan instances | 
|  | // | 
|  | VkApplicationInfo const app_info = { | 
|  |  | 
|  | .sType              = VK_STRUCTURE_TYPE_APPLICATION_INFO, | 
|  | .pNext              = NULL, | 
|  | .pApplicationName   = config->app_name ? config->app_name : "VK Test", | 
|  | .applicationVersion = 0, | 
|  | .pEngineName        = config->engine_name ? config->engine_name : "Graphics Compute VK", | 
|  | .engineVersion      = 0, | 
|  | .apiVersion         = VK_API_VERSION_1_1 | 
|  | }; | 
|  |  | 
|  | StringList enabled_layers     = STRING_LIST_INITIALIZER; | 
|  | StringList enabled_extensions = STRING_LIST_INITIALIZER; | 
|  |  | 
|  | bool enable_validation = config->enable_validation || config->enable_debug_report; | 
|  |  | 
|  | if (enable_validation) | 
|  | { | 
|  | // VK_LAYER_KHRONOS_validation is the new hotness to use, but not | 
|  | // all Vulkan installs support it yet, so fallback to | 
|  | // VK_LAYER_LUNARG_standard_validation if it is not available. | 
|  | static const char * const validation_layer_names[] = { | 
|  | "VK_LAYER_KHRONOS_validation", | 
|  | "VK_LAYER_LUNARG_standard_validation", | 
|  | }; | 
|  | for (uint32_t nn = 0; nn < ARRAY_SIZE(validation_layer_names); ++nn) | 
|  | { | 
|  | const char * layer_name = validation_layer_names[nn]; | 
|  | if (instance_info_has_layer(&instance_info, layer_name)) | 
|  | { | 
|  | string_list_append(&enabled_layers, layer_name); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (config->enable_debug_report) | 
|  | { | 
|  | if (instance_info_has_extension(&instance_info, VK_EXT_DEBUG_REPORT_EXTENSION_NAME)) | 
|  | { | 
|  | string_list_append(&enabled_extensions, VK_EXT_DEBUG_REPORT_EXTENSION_NAME); | 
|  | } | 
|  | app_state->has_debug_report = true; | 
|  | } | 
|  |  | 
|  | bool require_swapchain = config->require_swapchain; | 
|  |  | 
|  | vk_surface_requirements_t surface_requirements = {}; | 
|  |  | 
|  | if (require_swapchain) | 
|  | { | 
|  | string_list_append(&enabled_extensions, VK_KHR_SURFACE_EXTENSION_NAME); | 
|  |  | 
|  | // NOTE: On Fuchsia, swapchain extensions are provided by a layer. | 
|  | // For now, only use the layer allowing presenting to the framebuffer | 
|  | // directly (another layer is provided to display in a window, but this one | 
|  | // is far more work to get everything working). | 
|  | vk_surface_get_requirements(config->disable_swapchain_present, &surface_requirements); | 
|  |  | 
|  | const vk_surface_requirements_t * reqs = &surface_requirements; | 
|  | for (uint32_t nn = 0; nn < reqs->num_layers; ++nn) | 
|  | string_list_append(&enabled_layers, reqs->layer_names[nn]); | 
|  | for (uint32_t nn = 0; nn < reqs->num_extensions; ++nn) | 
|  | string_list_append(&enabled_extensions, reqs->extension_names[nn]); | 
|  | } | 
|  | else if (config->disable_swapchain_present) | 
|  | { | 
|  | fprintf(stderr, | 
|  | "WARNING: disable_swapchain_present ignored, since require_swapchain isn't set!\n"); | 
|  | } | 
|  |  | 
|  | VkInstanceCreateInfo const instance_create_info = { | 
|  |  | 
|  | .sType                   = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, | 
|  | .pNext                   = NULL, | 
|  | .flags                   = 0, | 
|  | .pApplicationInfo        = &app_info, | 
|  | .enabledLayerCount       = enabled_layers.count, | 
|  | .ppEnabledLayerNames     = enabled_layers.items, | 
|  | .enabledExtensionCount   = enabled_extensions.count, | 
|  | .ppEnabledExtensionNames = enabled_extensions.items, | 
|  | }; | 
|  |  | 
|  | if (!instance_info_validate_layers_and_extensions(&instance_info, | 
|  | &enabled_layers, | 
|  | &enabled_extensions)) | 
|  | { | 
|  | // NOTE: Error message printed by called function above. | 
|  | instance_info_destroy(&instance_info); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | vk(CreateInstance(&instance_create_info, NULL, &app_state->instance)); | 
|  |  | 
|  | if (config->enable_debug_report) | 
|  | vk_instance_create_info_print(&instance_create_info); | 
|  |  | 
|  | string_list_free(&enabled_layers); | 
|  | string_list_free(&enabled_extensions); | 
|  |  | 
|  | if (surface_requirements.free_func) | 
|  | surface_requirements.free_func(&surface_requirements); | 
|  |  | 
|  | instance_info_destroy(&instance_info); | 
|  |  | 
|  | // | 
|  | // | 
|  | // | 
|  |  | 
|  | ASSERT_MSG(sizeof(AppStateInternal) <= sizeof(app_state->internal_storage), | 
|  | "Please increase the size of vk_app_state_t::internal_storage\n"); | 
|  |  | 
|  | AppStateInternal * internal = (AppStateInternal *)app_state->internal_storage; | 
|  |  | 
|  | if (app_state->has_debug_report) | 
|  | { | 
|  | if (!setup_debug_report(app_state->instance, &internal->drc)) | 
|  | { | 
|  | fprintf(stderr, "WARNING: vkCreateDebugReportCallbackEXT not supported by Vulkan ICD!"); | 
|  | app_state->has_debug_report = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | // | 
|  | // Prepare Vulkan environment for Spinel | 
|  | // | 
|  | app_state->d           = VK_NULL_HANDLE; | 
|  | app_state->ac          = NULL; | 
|  | app_state->pc          = VK_NULL_HANDLE; | 
|  | app_state->pd          = VK_NULL_HANDLE; | 
|  | app_state->qfi         = UINT32_MAX; | 
|  | app_state->compute_qfi = UINT32_MAX; | 
|  |  | 
|  | // | 
|  | // acquire all physical devices | 
|  | // | 
|  | GpuList gpus; | 
|  | gpu_list_init(&gpus, app_state->instance); | 
|  |  | 
|  | if (gpus.count == 0) | 
|  | { | 
|  | fprintf(stderr, "No Vulkan device available!\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Grab device configuration through callback is necessary. | 
|  | vk_device_config_t device_config = config->device_config; | 
|  | if (config->device_config_callback != NULL) | 
|  | { | 
|  | bool found = false; | 
|  | for (uint32_t nn = 0; nn < gpus.count; ++nn) | 
|  | { | 
|  | device_config = (const vk_device_config_t){ | 
|  | .features = { | 
|  | .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | if (config->device_config_callback(config->device_config_opaque, | 
|  | app_state->instance, | 
|  | gpus.devices[nn], | 
|  | &device_config)) | 
|  | { | 
|  | device_config.physical_device = gpus.devices[nn]; | 
|  | found                         = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Find the appropriate physical device | 
|  | if (device_config.physical_device == VK_NULL_HANDLE) | 
|  | { | 
|  | // | 
|  | // If vendor_id if provided, look for corresponding matches. | 
|  | if (device_config.vendor_id != 0) | 
|  | { | 
|  | for (uint32_t nn = 0; nn < gpus.count; ++nn) | 
|  | { | 
|  | VkPhysicalDeviceProperties props; | 
|  | vkGetPhysicalDeviceProperties(gpus.devices[nn], &props); | 
|  |  | 
|  | if (props.vendorID != device_config.vendor_id) | 
|  | continue; | 
|  |  | 
|  | if (device_config.device_id != 0 && props.deviceID != device_config.device_id) | 
|  | continue; | 
|  |  | 
|  | // Found a match! | 
|  | device_config.physical_device = gpus.devices[nn]; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (!device_config.physical_device) | 
|  | { | 
|  | fprintf(stderr, | 
|  | "Device with (vendorID=%X,deviceID=%X) not found.\n", | 
|  | device_config.vendor_id, | 
|  | device_config.device_id); | 
|  | gpu_list_destroy(&gpus); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | // Use the first one by default. | 
|  | device_config.physical_device = gpus.devices[0]; | 
|  | } | 
|  | } | 
|  |  | 
|  | gpu_list_destroy(&gpus); | 
|  |  | 
|  | // | 
|  | // get the physical device's memory props | 
|  | // | 
|  | ASSERT_MSG(device_config.physical_device != VK_NULL_HANDLE, "Missing physical device.\n"); | 
|  |  | 
|  | app_state->pd = device_config.physical_device; | 
|  | vkGetPhysicalDeviceProperties(app_state->pd, &app_state->pdp); | 
|  | vkGetPhysicalDeviceMemoryProperties(app_state->pd, &app_state->pdmp); | 
|  |  | 
|  | // | 
|  | // get image properties | 
|  | // | 
|  | // vkGetPhysicalDeviceImageFormatProperties() | 
|  | // | 
|  | // vk(GetPhysicalDeviceImageFormatProperties(phy_device, | 
|  | // | 
|  |  | 
|  | // | 
|  | // Find appropriate queue families. | 
|  | // | 
|  |  | 
|  | uint32_t graphics_family = UINT32_MAX; | 
|  | uint32_t compute_family  = UINT32_MAX; | 
|  |  | 
|  | const VkQueueFlags wanted_combined_queues = device_config.required_combined_queues; | 
|  | VkQueueFlags       wanted_queues = wanted_combined_queues | device_config.required_queues; | 
|  |  | 
|  | // Enabling the swapchain requires the graphics queue. | 
|  | // Otherwise, use the graphics queue by default if none was asked. | 
|  | if (require_swapchain || !wanted_queues) | 
|  | wanted_queues |= VK_QUEUE_GRAPHICS_BIT; | 
|  |  | 
|  | { | 
|  | QueueFamilies families; | 
|  | queue_families_init(&families, app_state->pd); | 
|  |  | 
|  | // First, try to find combined queues if requested. | 
|  | if (wanted_combined_queues) | 
|  | { | 
|  | uint32_t family; | 
|  | if (!queue_families_find_for_flags(&families, wanted_combined_queues, &family)) | 
|  | { | 
|  | queue_families_destroy(&families); | 
|  | fprintf(stderr, "This device does not supported the required combined queues!\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if ((wanted_combined_queues & VK_QUEUE_GRAPHICS_BIT) != 0) | 
|  | graphics_family = family; | 
|  |  | 
|  | if ((wanted_combined_queues & VK_QUEUE_COMPUTE_BIT) != 0) | 
|  | compute_family = family; | 
|  | } | 
|  |  | 
|  | // Then find other queues if requested. | 
|  | VkQueueFlags single_queues = wanted_queues & ~wanted_combined_queues; | 
|  | if (single_queues) | 
|  | { | 
|  | uint32_t family; | 
|  | // First, try to find combined queues if any. | 
|  | if (queue_families_find_for_flags(&families, single_queues, &family)) | 
|  | { | 
|  | if ((single_queues & VK_QUEUE_GRAPHICS_BIT) != 0 && graphics_family == UINT32_MAX) | 
|  | { | 
|  | graphics_family = family; | 
|  | } | 
|  | if ((single_queues & VK_QUEUE_COMPUTE_BIT) != 0 && compute_family == UINT32_MAX) | 
|  | { | 
|  | compute_family = family; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | // Otherwise, try each bit in isolation. | 
|  | if ((single_queues & VK_QUEUE_GRAPHICS_BIT) != 0 && graphics_family == UINT32_MAX) | 
|  | { | 
|  | (void)queue_families_find_for_flags(&families, | 
|  | VK_QUEUE_GRAPHICS_BIT, | 
|  | &graphics_family); | 
|  | } | 
|  | if ((single_queues & VK_QUEUE_COMPUTE_BIT) != 0 && compute_family == UINT32_MAX) | 
|  | { | 
|  | (void)queue_families_find_for_flags(&families, | 
|  | VK_QUEUE_COMPUTE_BIT, | 
|  | &compute_family); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | queue_families_destroy(&families); | 
|  | } | 
|  |  | 
|  | // Sanity checks. | 
|  | if ((wanted_queues & VK_QUEUE_GRAPHICS_BIT) != 0 && graphics_family == UINT32_MAX) | 
|  | { | 
|  | fprintf(stderr, "This device does not provide a graphics queue!\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if ((wanted_queues & VK_QUEUE_COMPUTE_BIT) != 0 && compute_family == UINT32_MAX) | 
|  | { | 
|  | fprintf(stderr, "This device does not provide a compute queue!\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (require_swapchain) | 
|  | { | 
|  | if (!vk_physical_device_supports_presentation(app_state->instance, | 
|  | app_state->pd, | 
|  | graphics_family)) | 
|  | { | 
|  | fprintf(stderr, "This device does not support presentation/display!\n"); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | app_state->qfi         = graphics_family; | 
|  | app_state->compute_qfi = compute_family; | 
|  |  | 
|  | // | 
|  | // create queues | 
|  | // | 
|  | uint32_t queue_families[2]  = {}; | 
|  | uint32_t queue_family_count = 0; | 
|  |  | 
|  | if (graphics_family != UINT32_MAX) | 
|  | { | 
|  | queue_families[queue_family_count++] = graphics_family; | 
|  | } | 
|  | if (compute_family != UINT32_MAX && compute_family != graphics_family) | 
|  | { | 
|  | queue_families[queue_family_count++] = compute_family; | 
|  | } | 
|  |  | 
|  | VkDeviceQueueCreateInfo queue_family_info[2]; | 
|  | for (uint32_t nn = 0; nn < queue_family_count; ++nn) | 
|  | { | 
|  | queue_family_info[nn] = (const VkDeviceQueueCreateInfo){ | 
|  | .sType            = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, | 
|  | .pNext            = NULL, | 
|  | .flags            = 0, | 
|  | .queueFamilyIndex = queue_families[nn], | 
|  | .queueCount       = 1, | 
|  | .pQueuePriorities = &(const float){ 1.0f }, | 
|  | }; | 
|  | } | 
|  |  | 
|  | StringList device_extensions = STRING_LIST_INITIALIZER; | 
|  |  | 
|  | // | 
|  | // Enable optional device extensions if they are available first. | 
|  | // | 
|  | { | 
|  | static const char subgroup_size_control_ext[] = "VK_EXT_subgroup_size_control"; | 
|  | static const char amd_shader_info_ext[]       = VK_AMD_SHADER_INFO_EXTENSION_NAME; | 
|  |  | 
|  | VkInstance instance    = app_state->instance; | 
|  | DeviceInfo device_info = device_info_create(instance, app_state->pd); | 
|  |  | 
|  | // VK_EXT_subgroup_size_control | 
|  | if (config->enable_subgroup_size_control && | 
|  | device_info_has_extension(&device_info, subgroup_size_control_ext)) | 
|  | { | 
|  | string_list_append(&device_extensions, subgroup_size_control_ext); | 
|  | app_state->has_subgroup_size_control = true; | 
|  | } | 
|  |  | 
|  | // VK_AMD_shader_info | 
|  | if (config->enable_amd_statistics && | 
|  | device_info_has_extension(&device_info, amd_shader_info_ext)) | 
|  | { | 
|  | string_list_append(&device_extensions, amd_shader_info_ext); | 
|  | app_state->has_amd_statistics = true; | 
|  | } | 
|  |  | 
|  | device_info_destroy(&device_info); | 
|  | } | 
|  |  | 
|  | // | 
|  | // Enable swapchain device extension if needed. | 
|  | // | 
|  | if (require_swapchain) | 
|  | { | 
|  | string_list_append(&device_extensions, VK_KHR_SWAPCHAIN_EXTENSION_NAME); | 
|  | } | 
|  |  | 
|  | // | 
|  | // Merge required device extensions now. | 
|  | string_list_add_n(&device_extensions, | 
|  | device_config.extensions_count, | 
|  | device_config.extensions_names); | 
|  |  | 
|  | // |device_config.features.sType| is likely to be 0 here since that what | 
|  | // default initialization of a struct will do, but it needs to be fixed up | 
|  | // properly before calling CreateDevice. | 
|  | if (!device_config.features.sType) | 
|  | { | 
|  | device_config.features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; | 
|  | } | 
|  |  | 
|  | VkDeviceCreateInfo const device_info = { | 
|  |  | 
|  | .sType                   = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, | 
|  | .pNext                   = &device_config.features, | 
|  | .flags                   = 0, | 
|  | .queueCreateInfoCount    = queue_family_count, | 
|  | .pQueueCreateInfos       = queue_family_info, | 
|  | .enabledLayerCount       = 0, | 
|  | .ppEnabledLayerNames     = NULL, | 
|  | .enabledExtensionCount   = device_extensions.count, | 
|  | .ppEnabledExtensionNames = device_extensions.items, | 
|  | }; | 
|  |  | 
|  | vk(CreateDevice(app_state->pd, &device_info, app_state->ac, &app_state->d)); | 
|  |  | 
|  | if (config->enable_debug_report) | 
|  | vk_device_create_info_print(&device_info); | 
|  |  | 
|  | string_list_free(&device_extensions); | 
|  |  | 
|  | // | 
|  | // create the pipeline cache | 
|  | // | 
|  | if (config->enable_pipeline_cache) | 
|  | { | 
|  | app_state->pc = pipeline_cache_load(PIPELINE_CACHE_FILE_PATH, app_state->d, app_state->ac); | 
|  | } | 
|  |  | 
|  | #if VK_USE_PLATFORM_FUCHSIA | 
|  | fuchsia_state_init(&internal->fuchsia, config->enable_tracing); | 
|  | #endif | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void | 
|  | vk_app_state_destroy(vk_app_state_t * app_state) | 
|  | { | 
|  | if (app_state->pc != VK_NULL_HANDLE) | 
|  | { | 
|  | (void) | 
|  | pipeline_cache_save(app_state->pc, PIPELINE_CACHE_FILE_PATH, app_state->d, app_state->ac); | 
|  | } | 
|  |  | 
|  | vkDestroyDevice(app_state->d, app_state->ac); | 
|  |  | 
|  | AppStateInternal * internal = (AppStateInternal *)app_state->internal_storage; | 
|  |  | 
|  | if (app_state->has_debug_report && internal) | 
|  | { | 
|  | VkInstance instance = app_state->instance; | 
|  | GET_VULKAN_INSTANCE_PROC_ADDR(vkDestroyDebugReportCallbackEXT); | 
|  | vkDestroyDebugReportCallbackEXT(app_state->instance, internal->drc, NULL); | 
|  | } | 
|  |  | 
|  | #if VK_USE_PLATFORM_FUCHSIA | 
|  | fuchsia_state_destroy(&internal->fuchsia); | 
|  | #endif | 
|  |  | 
|  | vkDestroyInstance(app_state->instance, NULL); | 
|  |  | 
|  | #if USE_RENDERDOC_CAPTURE | 
|  | renderdoc_capture_teardown(); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | vk_queue_families_t | 
|  | vk_app_state_get_queue_families(const vk_app_state_t * app_state) | 
|  | { | 
|  | vk_queue_families_t result; | 
|  | uint32_t            count = 0; | 
|  | if (app_state->qfi != UINT32_MAX) | 
|  | result.indices[count++] = app_state->qfi; | 
|  |  | 
|  | if (app_state->compute_qfi != UINT32_MAX) | 
|  | { | 
|  | ASSERT(count == 0 || count == 1); | 
|  | if (count == 0 || app_state->compute_qfi != result.indices[0]) | 
|  | result.indices[count++] = app_state->compute_qfi; | 
|  | } | 
|  |  | 
|  | result.count = count; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | void | 
|  | vk_app_state_print(const vk_app_state_t * app_state) | 
|  | { | 
|  | const uint32_t vendor_id = app_state->pdp.vendorID; | 
|  | const uint32_t device_id = app_state->pdp.deviceID; | 
|  |  | 
|  | printf("Device (vendor_id, device_id)=(0x%X, 0x%0X)\n", vendor_id, device_id); | 
|  | printf("  VkInstance:            %p\n", app_state->instance); | 
|  | printf("  Allocation callbacks:  %p\n", app_state->ac); | 
|  | printf("  VkPhysicalDevice:      %p\n", app_state->pd); | 
|  | printf("  VkDevice:              %p\n", app_state->d); | 
|  |  | 
|  | printf("  VkPhysicalDeviceProperties:\n"); | 
|  | printf("     apiVersion:       0x%x\n", app_state->pdp.apiVersion); | 
|  | printf("     driverVersion:    0x%x\n", app_state->pdp.driverVersion); | 
|  | printf("     vendorID:         0x%x\n", app_state->pdp.vendorID); | 
|  | printf("     deviceID:         0x%x\n", app_state->pdp.deviceID); | 
|  | printf("     deviceType:       %s\n", | 
|  | vk_physical_device_type_to_string(app_state->pdp.deviceType)); | 
|  | printf("     deviceName:       %s\n", app_state->pdp.deviceName); | 
|  |  | 
|  | printf("  VkPhysicalDeviceMemoryProperties:\n"); | 
|  | for (uint32_t n = 0; n < app_state->pdmp.memoryHeapCount; ++n) | 
|  | { | 
|  | printf("      heap index=%-2d %s\n", | 
|  | n, | 
|  | vk_memory_heap_to_string(&app_state->pdmp.memoryHeaps[n])); | 
|  | } | 
|  | for (uint32_t n = 0; n < app_state->pdmp.memoryTypeCount; ++n) | 
|  | { | 
|  | printf("      type index=%-2d %s\n", | 
|  | n, | 
|  | vk_memory_type_to_string(&app_state->pdmp.memoryTypes[n])); | 
|  | } | 
|  |  | 
|  | printf("  has_debug_report:           %s\n", app_state->has_debug_report ? "true" : "false"); | 
|  | printf("  has_amd_statistics:         %s\n", app_state->has_amd_statistics ? "true" : "false"); | 
|  | printf("  has_debug_report:           %s\n", app_state->has_debug_report ? "true" : "false"); | 
|  | printf("  has_subgroup_size_control:  %s\n", | 
|  | app_state->has_subgroup_size_control ? "true" : "false"); | 
|  |  | 
|  | printf("  Queue families:\n"); | 
|  | printf("    Graphics:  %s\n", vk_queue_family_index_to_string(app_state->qfi)); | 
|  | printf("    Compute:   %s\n", vk_queue_family_index_to_string(app_state->compute_qfi)); | 
|  | } |