/* Copyright © 2024 Intel Corporation
 * SPDX-License-Identifier: MIT
 */

#include "anv_private.h"
#include "anv_api_version.h"

static const struct debug_control debug_control[] = {
   { "bindless",                  ANV_DEBUG_BINDLESS},
   { "desc-dirty",                ANV_DEBUG_DESCRIPTOR_DIRTY},
   { "dgc-dump",                  ANV_DEBUG_DGC_DUMP},
   { "experimental",              ANV_DEBUG_EXPERIMENTAL},
   { "no-gpl",                    ANV_DEBUG_NO_GPL},
   { "no-slab",                   ANV_DEBUG_NO_SLAB},
   { "no-sparse",                 ANV_DEBUG_NO_SPARSE},
   { "sparse-trtt",               ANV_DEBUG_SPARSE_TRTT},
   { "video-decode",              ANV_DEBUG_VIDEO_DECODE},
   { "video-encode",              ANV_DEBUG_VIDEO_ENCODE},
   { "shader-dump",               ANV_DEBUG_SHADER_DUMP},
   { "shader-hash",               ANV_DEBUG_SHADER_HASH},
   { "shader-print",              ANV_DEBUG_SHADER_PRINT},
   { "skip-disk-cache",           ANV_DEBUG_SKIP_DISK_CACHE},
   { NULL,    0 }
};

enum anv_debug anv_debug;

static void
process_anv_debug_variable_once(void)
{
   anv_debug = parse_debug_string(os_get_option("ANV_DEBUG"), debug_control);
}

VkResult anv_EnumerateInstanceVersion(
    uint32_t*                                   pApiVersion)
{
    *pApiVersion = ANV_API_VERSION;
    return VK_SUCCESS;
}

static const struct vk_instance_extension_table instance_extensions = {
   .KHR_device_group_creation                = true,
   .KHR_external_fence_capabilities          = true,
   .KHR_external_memory_capabilities         = true,
   .KHR_external_semaphore_capabilities      = true,
   .KHR_get_physical_device_properties2      = true,
   .EXT_debug_report                         = true,
   .EXT_debug_utils                          = true,

#ifdef ANV_USE_WSI_PLATFORM
   .KHR_get_surface_capabilities2            = true,
   .KHR_surface                              = true,
   .KHR_surface_maintenance1                 = true,
   .KHR_surface_protected_capabilities       = true,
   .EXT_surface_maintenance1                 = true,
   .EXT_swapchain_colorspace                 = true,
#endif
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
   .KHR_wayland_surface                      = true,
#endif
#ifdef VK_USE_PLATFORM_XCB_KHR
   .KHR_xcb_surface                          = true,
#endif
#ifdef VK_USE_PLATFORM_XLIB_KHR
   .KHR_xlib_surface                         = true,
#endif
#ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT
   .EXT_acquire_xlib_display                 = true,
#endif
#ifdef VK_USE_PLATFORM_DISPLAY_KHR
   .KHR_display                              = true,
   .KHR_get_display_properties2              = true,
   .EXT_direct_mode_display                  = true,
   .EXT_display_surface_counter              = true,
   .EXT_acquire_drm_display                  = true,
#endif
#ifndef VK_USE_PLATFORM_WIN32_KHR
   .EXT_headless_surface                     = true,
#endif
};

VkResult anv_EnumerateInstanceExtensionProperties(
    const char*                                 pLayerName,
    uint32_t*                                   pPropertyCount,
    VkExtensionProperties*                      pProperties)
{
   if (pLayerName)
      return vk_error(NULL, VK_ERROR_LAYER_NOT_PRESENT);

   return vk_enumerate_instance_extension_properties(
      &instance_extensions, pPropertyCount, pProperties);
}

static void
anv_drirc_shader_cb(const void *hash_data,
                    uint32_t hash_size,
                    const driOptionInfo *option,
                    const driOptionValue *value,
                    void *shaderOptionCallbackData)
{
   /* Should always be 8 bytes or more. Our compiler prog_data only holds the
    * first 64bits, so just use that for the hash table.
    */
   assert(hash_size >= 8);
   uint64_t shader_hash = ((uint64_t *)hash_data)[0];

   struct anv_instance *instance = shaderOptionCallbackData;

   if (instance->shader_workarounds == NULL)
      instance->shader_workarounds = _mesa_hash_table_u64_create(NULL);

   struct anv_shader_workaround *workaround =
      _mesa_hash_table_u64_search(instance->shader_workarounds, shader_hash);
   if (workaround == NULL) {
      workaround = vk_zalloc(&instance->vk.alloc,
                             sizeof(*workaround), 8,
                             VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE);
      if (workaround == NULL) {
         instance->drirc_status = vk_error(NULL, VK_ERROR_OUT_OF_HOST_MEMORY);
         return;
      }
      _mesa_hash_table_u64_insert(instance->shader_workarounds,
                                  shader_hash, workaround);
   }

   assert(workaround != NULL);

   if (strcmp(option->name, "force_vk_typed_barrier_after_dispatch_to_compute") == 0)
      workaround->force_typed_barrier_after_dispatch_to_compute = true;
   else if (strcmp(option->name, "force_vk_untyped_barrier_after_dispatch_to_compute") == 0)
      workaround->force_untyped_barrier_after_dispatch_to_compute = true;
   else if (strcmp(option->name, "force_vk_typed_barrier_after_dispatch_to_top") == 0)
      workaround->force_typed_barrier_after_dispatch_to_top = true;
   else if (strcmp(option->name, "force_vk_untyped_barrier_after_dispatch_to_top") == 0)
      workaround->force_untyped_barrier_after_dispatch_to_top = true;
   else if (strcmp(option->name, "brw_prefer_simd32_fs") == 0)
      workaround->prefer_simd32_fs = true;
   else if (strcmp(option->name, "anv_xe2_force_simd32_cs") == 0)
      workaround->force_xe2_simd32_cs = true;
   else
      UNREACHABLE("invalid shader option");
}

static VkResult
anv_init_dri_options(struct anv_instance *instance)
{
   instance->drirc_status = VK_SUCCESS;

   anv_parse_dri_options(&instance->drirc,
                         &(driConfigFileParseParams) {
                            .driverName = "anv",
                            .applicationName = instance->vk.app_info.app_name,
                            .applicationVersion = instance->vk.app_info.app_version,
                            .engineName = instance->vk.app_info.engine_name,
                            .engineVersion = instance->vk.app_info.engine_version,
                            .shaderOptionCallback = anv_drirc_shader_cb,
                            .shaderOptionCallbackData = instance,
                         });

   if (instance->drirc_status != VK_SUCCESS)
      return instance->drirc_status;

    if (instance->vk.app_info.engine_name &&
        !strcmp(instance->vk.app_info.engine_name, "DXVK")) {
       /* Since 2.3.1+, DXVK uses the application version to signal D3D9. */
       const bool is_d3d9 = instance->vk.app_info.app_version & 0x1;

       /* This driconf bit enables D3D10+ behaviour for texture coordinate
        * rounding. As D3D9 wants the Vulkan behaviour instead, apply the
        * workaround only to D3D10+.
        */
       instance->drirc.debug.force_filter_addr_rounding &= !is_d3d9;
    }

    switch (instance->drirc.perf.stack_ids) {
    case 256:
    case 512:
    case 1024:
    case 2048:
       break;
    default:
       mesa_logw("Invalid value provided for drirc anv_stack_id=%u, reverting to 512.",
                 instance->drirc.perf.stack_ids);
       instance->drirc.perf.stack_ids = 512;
       break;
    }

   switch(instance->drirc.perf.rt_dispatch_timeout) {
   case 64:
   case 128:
   case 192:
   case 256:
   case 384:
   case 512:
   case 640:
   case 768:
   case 896:
   case 1024:
   case 1152:
   case 1280:
   case 1408:
   case 1536:
   case 1664:
   case 1792:
   case 1920:
   case 2048:
   case 4096:
      break;
   default:
       mesa_logw("Invalid value provided for drirc anv_rt_dispatch_timeout=%u, reverting to 512.",
                 instance->drirc.perf.rt_dispatch_timeout);
       instance->drirc.perf.rt_dispatch_timeout = 512;
       break;
   }

   return VK_SUCCESS;
}

VkResult anv_CreateInstance(
    const VkInstanceCreateInfo*                 pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkInstance*                                 pInstance)
{
   struct anv_instance *instance;
   VkResult result;

   assert(pCreateInfo->sType == VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO);

   if (pAllocator == NULL)
      pAllocator = vk_default_allocator();

   instance = vk_zalloc(pAllocator, sizeof(*instance), 8,
                        VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE);
   if (!instance)
      return vk_error(NULL, VK_ERROR_OUT_OF_HOST_MEMORY);

   struct vk_instance_dispatch_table dispatch_table;
   vk_instance_dispatch_table_from_entrypoints(
      &dispatch_table, &anv_instance_entrypoints, true);
   vk_instance_dispatch_table_from_entrypoints(
      &dispatch_table, &wsi_instance_entrypoints, false);

   result = vk_instance_init(&instance->vk, &instance_extensions,
                             &dispatch_table, pCreateInfo, pAllocator);
   if (result != VK_SUCCESS) {
      vk_free(pAllocator, instance);
      return vk_error(NULL, result);
   }

   result = anv_init_dri_options(instance);
   if (result != VK_SUCCESS)
      goto fail_init;

   instance->vk.physical_devices.try_create_for_drm = anv_physical_device_try_create;
   instance->vk.physical_devices.destroy = anv_physical_device_destroy;

   VG(VALGRIND_CREATE_MEMPOOL(instance, 0, false));

   static once_flag process_anv_debug_variable_flag = ONCE_FLAG_INIT;
   call_once(&process_anv_debug_variable_flag,
             process_anv_debug_variable_once);

   process_intel_debug_variable();
   instance->vk.enable_debug_logging = INTEL_DEBUG(DEBUG_PERF);

   intel_driver_ds_init();

   *pInstance = anv_instance_to_handle(instance);

   return VK_SUCCESS;

 fail_init:
   vk_instance_finish(&instance->vk);
   vk_free(&instance->vk.alloc, instance);

   return result;
}

void anv_DestroyInstance(
    VkInstance                                  _instance,
    const VkAllocationCallbacks*                pAllocator)
{
   ANV_FROM_HANDLE(anv_instance, instance, _instance);

   if (!instance)
      return;

   VG(VALGRIND_DESTROY_MEMPOOL(instance));

   driDestroyOptionCache(&instance->drirc.options);
   driDestroyOptionInfo(&instance->drirc.available_options);

   if (instance->shader_workarounds) {
      hash_table_u64_foreach(instance->shader_workarounds, entry)
         vk_free(&instance->vk.alloc, entry.data);
      _mesa_hash_table_u64_destroy(instance->shader_workarounds);
   }

   vk_instance_finish(&instance->vk);
   vk_free(&instance->vk.alloc, instance);
}

PFN_vkVoidFunction anv_GetInstanceProcAddr(
    VkInstance                                  _instance,
    const char*                                 pName)
{
   ANV_FROM_HANDLE(anv_instance, instance, _instance);
   return vk_instance_get_proc_addr(instance ? &instance->vk : NULL,
                                    &anv_instance_entrypoints,
                                    pName);
}

/* With version 1+ of the loader interface the ICD should expose
 * vk_icdGetInstanceProcAddr to work around certain LD_PRELOAD issues seen in apps.
 */
PUBLIC
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vk_icdGetInstanceProcAddr(
    VkInstance                                  instance,
    const char*                                 pName)
{
   return anv_GetInstanceProcAddr(instance, pName);
}
