// Copyright (c) 2018-2026 Jean-Philippe Bruyère // // This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) #include "vkvg_device_internal.h" #include "vkvg_context_internal.h" #include "vkvg_surface_internal.h" #include "vkh_queue.h" #include "vkh_phyinfo.h" #include "vk_mem_alloc.h" #define TRY_LOAD_DEVICE_EXT(ext) \ { \ if (vkh_phyinfo_try_get_extension_properties(pi, #ext, NULL)) \ enabledExts[enabledExtsCount++] = #ext; \ } #define _CHECK_INST_EXT(ext) \ if (vkh_instance_extension_supported(#ext)) { \ if (pExtensions) \ pExtensions[*pExtCount] = #ext; \ (*pExtCount)++; \ } #define _CHECK_DEV_EXT(ext) \ { \ if (_get_dev_extension_is_supported(pExtensionProperties, extensionCount, #ext)) { \ if (pExtensions) \ pExtensions[*pExtCount] = #ext; \ (*pExtCount)++; \ } \ } void vkvg_device_set_context_cache_size(VkvgDevice dev, uint32_t maxCount) { if (maxCount == dev->cachedContextMaxCount) return; dev->cachedContextMaxCount = maxCount; _cached_ctx* cur = dev->cachedContextLast; while (cur && dev->cachedContextCount > dev->cachedContextMaxCount) { _release_context_ressources(cur->ctx); _cached_ctx* prev = cur; cur = cur->pNext; free(prev); dev->cachedContextCount--; } dev->cachedContextLast = cur; } void _device_init(VkvgDevice dev, const vkvg_device_create_info_t* info) { dev->vkDev = info->vkdev; dev->phy = info->phy; dev->instance = info->inst; dev->cachedContextMaxCount = VKVG_MAX_CACHED_CONTEXT_COUNT; #if VKVG_DBG_STATS dev->debug_stats = (vkvg_debug_stats_t){0}; #endif VkFormat format = FB_COLOR_FORMAT; _device_check_best_image_tiling(dev, format); if (dev->status != VKVG_STATUS_SUCCESS) return; if (!_device_init_function_pointers(dev)) { dev->status = VKVG_STATUS_NULL_POINTER; return; } VkhPhyInfo phyInfos = vkh_phyinfo_create(dev->phy, NULL); dev->phyMemProps = phyInfos->memProps; dev->gQueue = vkh_queue_create(dev->vkhDev, info->qFamIdx, info->qIndex); VkPhysicalDeviceProperties phyProps = vkh_phyinfo_get_properties(phyInfos); dev->minUniformBufferOffsetAlignment = phyProps.limits.minUniformBufferOffsetAlignment; dev->framebufferColorSampleCounts = phyProps.limits.framebufferColorSampleCounts; // mtx_init (&dev->gQMutex, mtx_plain); vkh_phyinfo_destroy(phyInfos); dev->cmdPool = vkh_cmd_pool_create(dev->vkhDev, dev->gQueue->familyIndex, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT); dev->cmd = vkh_cmd_buff_create(dev->vkhDev, dev->cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY); dev->fence = vkh_fence_create_signaled(dev->vkhDev); //_device_create_pipeline_cache(dev); _device_createDescriptorSetLayout (dev); _fonts_cache_create (dev, info->fontsDirectories); _device_allocalte_dummy_descriptors(dev); if (dev->threadAware) { mtx_init(&dev->mutex, mtx_plain); mtx_init(&dev->fontCache->mutex, mtx_plain); dev->threadAware = true; } #ifdef DEBUG #if defined(__linux__) && defined(__GLIBC__) _linux_register_error_handler(); #endif #ifdef VKVG_DBG_UTILS vkh_device_set_object_name(dev->vkhDev, VK_OBJECT_TYPE_COMMAND_POOL, (uint64_t)dev->cmdPool, "Device Cmd Pool"); vkh_device_set_object_name(dev->vkhDev, VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)dev->cmd, "Device Cmd Buff"); vkh_device_set_object_name(dev->vkhDev, VK_OBJECT_TYPE_FENCE, (uint64_t)dev->fence, "Device Fence"); vkh_device_set_object_name(dev->vkhDev, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, (uint64_t)dev->dslSrc, "DSLayout SOURCE"); vkh_device_set_object_name(dev->vkhDev, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, (uint64_t)dev->dslFont, "DSLayout FONT"); vkh_device_set_object_name(dev->vkhDev, VK_OBJECT_TYPE_SAMPLER, (uint64_t)dev->smpFont, "FONT Sampler"); vkh_device_set_object_name(dev->vkhDev, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, (uint64_t)dev->dslGrad, "DSLayout GRADIENT"); vkh_device_set_object_name(dev->vkhDev, VK_OBJECT_TYPE_PIPELINE_LAYOUT, (uint64_t)dev->pipelineLayout, "PLLayout dev"); #endif #endif dev->status = VKVG_STATUS_SUCCESS; } void vkvg_get_required_instance_extensions(const char** pExtensions, uint32_t* pExtCount) { *pExtCount = 0; vkh_instance_extensions_check_init(); #if defined(DEBUG) && defined(VKVG_DBG_UTILS) _CHECK_INST_EXT(VK_EXT_debug_utils) #endif _CHECK_INST_EXT(VK_KHR_get_physical_device_properties2) vkh_instance_extensions_check_release(); } bool _get_dev_extension_is_supported(VkExtensionProperties* pExtensionProperties, uint32_t extensionCount, const char* name) { for (uint32_t i = 0; i < extensionCount; i++) { if (strcmp(name, pExtensionProperties[i].extensionName) == 0) return true; } return false; } vkvg_status_t vkvg_get_required_device_extensions(VkPhysicalDevice phy, const char** pExtensions, uint32_t* pExtCount) { VkExtensionProperties* pExtensionProperties; uint32_t extensionCount; *pExtCount = 0; VK_CHECK_RESULT(vkEnumerateDeviceExtensionProperties(phy, NULL, &extensionCount, NULL)); pExtensionProperties = (VkExtensionProperties*)malloc(extensionCount * sizeof(VkExtensionProperties)); VK_CHECK_RESULT(vkEnumerateDeviceExtensionProperties(phy, NULL, &extensionCount, pExtensionProperties)); // https://vulkan.lunarg.com/doc/view/1.2.162.0/mac/1.2-extensions/vkspec.html#VK_KHR_portability_subset _CHECK_DEV_EXT(VK_KHR_portability_subset); VkPhysicalDeviceFeatures2 phyFeat2 = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2}; #ifdef VKVG_ENABLE_VK_SCALAR_BLOCK_LAYOUT // ensure feature is implemented by driver. VkPhysicalDeviceScalarBlockLayoutFeatures scalarBlockLayoutSupport = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES}; phyFeat2.pNext = &scalarBlockLayoutSupport; #endif #ifdef VKVG_ENABLE_VK_TIMELINE_SEMAPHORE VkPhysicalDeviceTimelineSemaphoreFeatures timelineSemaphoreSupport = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES}; timelineSemaphoreSupport.pNext = phyFeat2.pNext; phyFeat2.pNext = &timelineSemaphoreSupport; #endif vkGetPhysicalDeviceFeatures2(phy, &phyFeat2); #ifdef VKVG_ENABLE_VK_SCALAR_BLOCK_LAYOUT if (!scalarBlockLayoutSupport.scalarBlockLayout) { LOGE("CREATE Device failed, vkvg compiled with VKVG_ENABLE_VK_SCALAR_BLOCK_LAYOUT and feature is " "not implemented for physical device.\n"); free(pExtensionProperties); return VKVG_STATUS_DEVICE_ERROR; } _CHECK_DEV_EXT(VK_EXT_scalar_block_layout) #endif #ifdef VKVG_ENABLE_VK_TIMELINE_SEMAPHORE if (!timelineSemaphoreSupport.timelineSemaphore) { LOGE("CREATE Device failed, VK_SEMAPHORE_TYPE_TIMELINE not supported.\n"); free(pExtensionProperties); return VKVG_STATUS_DEVICE_ERROR; } _CHECK_DEV_EXT(VK_KHR_timeline_semaphore) #endif _CHECK_DEV_EXT(VK_EXT_extended_dynamic_state) free(pExtensionProperties); return VKVG_STATUS_SUCCESS; } // enabledFeature12 is guarantied to be the first in pNext chain const void* vkvg_get_device_requirements(VkPhysicalDeviceFeatures* pEnabledFeatures) { pEnabledFeatures->fillModeNonSolid = VK_TRUE; pEnabledFeatures->sampleRateShading = VK_TRUE; pEnabledFeatures->logicOp = VK_TRUE; void* pNext = NULL; #ifdef VK_VERSION_1_2 static VkPhysicalDeviceVulkan12Features enabledFeatures12 = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES #ifdef VKVG_ENABLE_VK_SCALAR_BLOCK_LAYOUT ,.scalarBlockLayout = VK_TRUE #endif #ifdef VKVG_ENABLE_VK_TIMELINE_SEMAPHORE , .timelineSemaphore = VK_TRUE #endif ,.descriptorBindingPartiallyBound = VK_TRUE }; enabledFeatures12.pNext = pNext; pNext = &enabledFeatures12; #else #ifdef VKVG_ENABLE_VK_SCALAR_BLOCK_LAYOUT static VkPhysicalDeviceScalarBlockLayoutFeaturesEXT scalarBlockFeat = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES_EXT, .scalarBlockLayout = VK_TRUE}; scalarBlockFeat.pNext = pNext; pNext = &scalarBlockFeat; #endif #ifdef VKVG_ENABLE_VK_TIMELINE_SEMAPHORE static VkPhysicalDeviceTimelineSemaphoreFeaturesKHR timelineSemaFeat = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR, .timelineSemaphore = VK_TRUE}; timelineSemaFeat.pNext = pNext; pNext = &timelineSemaFeat; #endif #endif return pNext; } VkvgDevice vkvg_device_create(vkvg_device_create_info_t* info) { LOG(VKVG_LOG_INFO, "CREATE Device\n"); if (!info) { LOGE("CREATE Device failed, provided vkvg_device_create_info_t is null\n"); return (VkvgDevice)&_vkvg_status_invalid_dev_ci; } VkvgDevice dev = (vkvg_device*)calloc(1, sizeof(vkvg_device)); if (!dev) { LOGE("CREATE Device failed, no memory\n"); return (VkvgDevice)&_vkvg_status_no_memory; } dev->threadAware = info->threadAware; if (!info->vkdev) { const char* enabledExts[10]; const char* enabledLayers[10]; uint32_t enabledExtsCount = 0, enabledLayersCount = 0, phyCount = 0; vkh_layers_check_init(); #ifdef VKVG_USE_VALIDATION if (vkh_layer_is_present("VK_LAYER_KHRONOS_validation")) enabledLayers[enabledLayersCount++] = "VK_LAYER_KHRONOS_validation"; #endif #ifdef VKVG_USE_RENDERDOC if (vkh_layer_is_present("VK_LAYER_RENDERDOC_Capture")) enabledLayers[enabledLayersCount++] = "VK_LAYER_RENDERDOC_Capture"; #endif vkh_layers_check_release(); vkvg_get_required_instance_extensions(enabledExts, &enabledExtsCount); #ifdef VK_VERSION_1_2 VkhApp app = vkh_app_create(1, 2, "vkvg", enabledLayersCount, enabledLayers, enabledExtsCount, enabledExts); #else VkhApp app = vkh_app_create(1, 1, "vkvg", enabledLayersCount, enabledLayers, enabledExtsCount, enabledExts); #endif #if defined(DEBUG) && defined(VKVG_DBG_UTILS) vkh_app_enable_debug_messenger(app, VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, NULL); #endif VkhPhyInfo* phys = vkh_app_get_phyinfos(app, &phyCount, VK_NULL_HANDLE); if (phyCount == 0) { dev->status = VKVG_STATUS_DEVICE_ERROR; vkh_app_destroy(app); return dev; } VkhPhyInfo pi = 0; if (!_device_try_get_phyinfo(phys, phyCount, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, &pi)) if (!_device_try_get_phyinfo(phys, phyCount, VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, &pi)) pi = phys[0]; uint32_t qCount = 0; float qPriorities[] = {0.0}; VkDeviceQueueCreateInfo pQueueInfos[] = {{0}, {0}, {0}}; if (vkh_phyinfo_create_queues(pi, pi->gQueue, 1, qPriorities, &pQueueInfos[qCount])) qCount++; enabledExtsCount = 0; if (vkvg_get_required_device_extensions(pi->phy, enabledExts, &enabledExtsCount) != VKVG_STATUS_SUCCESS) { LOGE("CREATE Device failed, vkvg_get_required_device_extensions failure.\n"); dev->status = VKVG_STATUS_DEVICE_ERROR; vkh_app_free_phyinfos(phyCount, phys); vkh_app_destroy(app); return dev; } VkPhysicalDeviceFeatures enabledFeatures = {0}; const void* pNext = vkvg_get_device_requirements(&enabledFeatures); VkDeviceCreateInfo device_info = {.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .queueCreateInfoCount = qCount, .pQueueCreateInfos = (VkDeviceQueueCreateInfo*)&pQueueInfos, .enabledExtensionCount = enabledExtsCount, .ppEnabledExtensionNames = enabledExts, .pEnabledFeatures = &enabledFeatures, .pNext = pNext}; dev->vkhDev = vkh_device_create(app, pi, &device_info); dev->holdVkhDev = true; info->inst = vkh_app_get_inst(app); info->phy = vkh_device_get_phy(dev->vkhDev); info->vkdev = vkh_device_get_vkdev(dev->vkhDev); info->qFamIdx = pi->gQueue; info->qIndex = 0; vkh_app_free_phyinfos(phyCount, phys); } else { dev->vkhDev = vkh_device_import(info->inst, info->phy, info->vkdev); } _device_init(dev, info); dev->surfaceClasses = array_create_SurfaceClass(2); atomic_init(&dev->references, 1); return dev; } void vkvg_device_destroy(VkvgDevice dev) { if (vkvg_device_status(dev)) { LOGE("DESTROY Device failed, see Status for info"); return; } if (atomic_fetch_sub_explicit(&dev->references, 1, memory_order_acq_rel) != 1) return; LOG(VKVG_LOG_INFO, "DESTROY Device\n"); if (dev->cachedContextCount > 0) { _cached_ctx* cur = dev->cachedContextLast; while (cur) { assert(cur->ctx->status == VKVG_STATUS_IN_CACHE); cur->ctx->status = VKVG_STATUS_SUCCESS; _release_context_ressources(cur->ctx); _cached_ctx* prev = cur; cur = cur->pNext; free(prev); } } vkDeviceWaitIdle(dev->vkDev); for (int i = 0; i < dev->surfaceClasses.count; ++i) { _destroy_surface_class (dev->surfaceClasses.elements[i]); } array_destroy_SurfaceClass(&dev->surfaceClasses); vkDestroyDescriptorPool(dev->vkDev, dev->descriptorPool, NULL); vkDestroyDescriptorSetLayout(dev->vkDev, dev->dslGrad, NULL); vkDestroyDescriptorSetLayout(dev->vkDev, dev->dslFont, NULL); vkDestroyDescriptorSetLayout(dev->vkDev, dev->dslSrc, NULL); vkDestroyPipelineLayout(dev->vkDev, dev->pipelineLayout, NULL); vkDestroyPipelineCache(dev->vkDev, dev->pipelineCache, NULL); vkDestroySampler(dev->vkDev, dev->smpFont, NULL); vkWaitForFences(dev->vkDev, 1, &dev->fence, VK_TRUE, UINT64_MAX); vkDestroyFence(dev->vkDev, dev->fence, NULL); vkFreeCommandBuffers(dev->vkDev, dev->cmdPool, 1, &dev->cmd); vkDestroyCommandPool(dev->vkDev, dev->cmdPool, NULL); vkh_queue_destroy(dev->gQueue); _font_cache_destroy(dev); if (dev->threadAware) { mtx_destroy(&dev->mutex); mtx_destroy(&dev->fontCache->mutex); } if (dev->holdVkhDev) { VkhApp app = vkh_device_get_app(dev->vkhDev); vkh_device_destroy(dev->vkhDev); vkh_app_destroy(app); } else { vkh_device_destroy(dev->vkhDev); } free(dev); dev = NULL; } vkvg_status_t vkvg_device_status(VkvgDevice dev) { return !dev ? VKVG_STATUS_NULL_POINTER : dev->status; } VkvgDevice vkvg_device_reference(VkvgDevice dev) { if (!vkvg_device_status(dev)) atomic_fetch_add_explicit(&dev->references, 1, memory_order_relaxed); return dev; } uint32_t vkvg_device_get_reference_count(VkvgDevice dev) { if (vkvg_device_status(dev)) return 0; return atomic_load_explicit(&dev->references, memory_order_relaxed); } #if VKVG_DBG_STATS vkvg_debug_stats_t vkvg_device_get_stats(VkvgDevice dev) { return vkvg_device_status(dev) ? (vkvg_debug_stats_t){0} : dev->debug_stats; } void vkvg_device_reset_stats(VkvgDevice dev) { if (vkvg_device_status(dev)) return; dev->debug_stats = (vkvg_debug_stats_t){0}; } #endif