blob: 05d2fe7848b0dc5dcefce1bbcae7daef5a34ac9a [file] [log] [blame]
// 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));
}