#include <malloc.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include <runtime/runtime.h>
#include <runtime/config.h>
#include <runtime/timing.h>

#include "device.h"
#include "physical_resource_manager.h"

#define TARGET_API_VERSION VK_API_VERSION_1_3

RT_CVAR_I(r_VkEnableAPIAllocTracking,
          "Enable tracking of allocations done by the vulkan api. [0/1] Default: 0",
          0);

RT_CVAR_S(r_VkPhysDeviceName, "Name of the selected physical device. Default: \"\"", "");

RT_CVAR_I(r_VkMaxFramesInFlight, "Maximum number of frames in flight. [2/3] Default: 2", 2);

static VkAllocationCallbacks _tracking_alloc_cbs;

static const char *AllocationScopeToString(VkSystemAllocationScope scope) {
    switch (scope) {
    case VK_SYSTEM_ALLOCATION_SCOPE_COMMAND:
        return "COMMAND";
    case VK_SYSTEM_ALLOCATION_SCOPE_OBJECT:
        return "OBJECT";
    case VK_SYSTEM_ALLOCATION_SCOPE_CACHE:
        return "CACHE";
    case VK_SYSTEM_ALLOCATION_SCOPE_DEVICE:
        return "DEVICE";
    case VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE:
        return "INSTANCE";
    default:
        return "UNKNOWN";
    }
}

static void *
TrackAllocation(void *userData, size_t size, size_t alignment, VkSystemAllocationScope scope) {
    rtLog("VK",
          "Allocation. Size: %zu, Alignment: %zu, Scope: %s",
          size,
          alignment,
          AllocationScopeToString(scope));
#ifdef _WIN32
    return _aligned_malloc(size, alignment);
#else
    return aligned_alloc(alignment, size);
#endif
}

static void *TrackReallocation(void *userData,
                               void *original,
                               size_t size,
                               size_t alignment,
                               VkSystemAllocationScope scope) {
    rtLog("VK",
          "Reallocation. Size: %zu, Alignment: %zu, Scope: %s",
          size,
          alignment,
          AllocationScopeToString(scope));
    return realloc(original, size);
}

static void TrackFree(void *userData, void *memory) {
    free(memory);
}

static VkBool32 VKAPI_PTR
DebugUtilsMessengerCb(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
                      VkDebugUtilsMessageTypeFlagsEXT types,
                      const VkDebugUtilsMessengerCallbackDataEXT *callbackData,
                      void *userData) {
    if (severity < VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)
        return VK_FALSE;

    const char *severity_str = "<UNKNOWN>";
    if (severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)
        severity_str = "WARNING";
    else if (severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
        severity_str = "ERROR";
    rtLog("VK", "[%s] %s", severity_str, callbackData->pMessage);
    if (severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
        RT_DEBUGBREAK;
    return VK_FALSE;
}

static rt_result CreateInstance(rt_vk_device *dev) {
    VkResult result = volkInitialize();
    if (result != VK_SUCCESS) {
        rtReportError("vk", "Initialization failed: volkInitialize()");
        return 1;
    }

    VkApplicationInfo app_info = {
        .apiVersion         = TARGET_API_VERSION,
        .applicationVersion = 0x00001000,
        .engineVersion      = 0x00001000,
        .pEngineName        = "voyageEngine",
        .pApplicationName   = "Voyage",
    };

    const char *extensions[] = {
        VK_KHR_SURFACE_EXTENSION_NAME,
#ifdef _WIN32
        "VK_KHR_win32_surface",
#elif defined(RT_USE_XLIB)
        "VK_KHR_xlib_surface",
#endif

#ifdef RT_DEBUG
        VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
#endif
    };

    const char *layers[1];
    unsigned int layer_count = 0;
#ifdef RT_DEBUG
    /* Search for layers we want to enable */
    uint32_t available_layer_count = 0;
    result = vkEnumerateInstanceLayerProperties(&available_layer_count, NULL);
    if (result == VK_SUCCESS) {
        VkLayerProperties *props = calloc(available_layer_count, sizeof(VkLayerProperties));
        if (props) {
            vkEnumerateInstanceLayerProperties(&available_layer_count, props);
            for (uint32_t i = 0; i < available_layer_count; ++i) {
                if (strcmp(props[i].layerName, "VK_LAYER_KHRONOS_validation") == 0) {
                    layers[0]   = "VK_LAYER_KHRONOS_validation";
                    layer_count = 1;
                    break;
                }
            }
            free(props);
        } else {
            rtLog("VK", "Failed to allocate storage for instance layer properties.");
        }
    } else {
        rtLog("VK", "vkEnumerateInstanceLayerProperties failed.");
    }
#endif

    VkInstanceCreateInfo instance_info = {
        .sType                   = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
        .pApplicationInfo        = &app_info,
        .ppEnabledExtensionNames = extensions,
        .enabledExtensionCount   = RT_ARRAY_COUNT(extensions),
        .ppEnabledLayerNames     = layers,
        .enabledLayerCount       = layer_count,
    };
    result = vkCreateInstance(&instance_info, dev->alloc_cb, &dev->instance);
    if (result != VK_SUCCESS) {
        rtReportError("VK", "Failed to create the vulkan instance.");
        return 1;
    }
    volkLoadInstance(dev->instance);

#ifdef RT_DEBUG
    /* Create the debug utils messenger */
    VkDebugUtilsMessengerCreateInfoEXT messenger_info = {
        .sType           = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
        .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
                           VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
        .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
                       VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
        .pfnUserCallback = DebugUtilsMessengerCb,
    };
    vkCreateDebugUtilsMessengerEXT(dev->instance,
                                   &messenger_info,
                                   dev->alloc_cb,
                                   &dev->messenger);
#endif
    return RT_SUCCESS;
}

static rt_result CreateSurface(const rt_renderer_window_info *info, rt_vk_device *dev) {
#ifdef _WIN32
    dev->native_window.hInstance            = info->hInstance;
    dev->native_window.hWnd                 = info->hWnd;
    VkWin32SurfaceCreateInfoKHR surface_info = {
        .sType     = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
        .hinstance = info->hInstance,
        .hwnd      = info->hWnd,
    };
    if (vkCreateWin32SurfaceKHR(dev->instance, &surface_info, dev->alloc_cb, &dev->surface) ==
        VK_SUCCESS)
        return RT_SUCCESS;
    else
        return RT_UNKNOWN_ERROR;
#elif defined(RT_USE_XLIB)
    dev->native_window.display             = info->display;
    dev->native_window.window              = info->window;
    VkXlibSurfaceCreateInfoKHR surface_info = {
        .sType  = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
        .dpy    = info->display,
        .window = info->window,
    };
    if (vkCreateXlibSurfaceKHR(dev->instance, &surface_info, dev->alloc_cb, &dev->surface) == VK_SUCCESS)
        return RT_SUCCESS;
    else
        return RT_UNKNOWN_ERROR;
#endif
}

typedef struct {
    uint32_t graphics;
    uint32_t compute;
    uint32_t present;
    uint32_t transfer;
} rt_queue_indices;

static rt_queue_indices RetrieveQueueIndices(VkPhysicalDevice phys_dev, VkSurfaceKHR surface) {
    rt_queue_indices indices = {.graphics = UINT32_MAX,
                                .compute  = UINT32_MAX,
                                .present  = UINT32_MAX,
                                .transfer = UINT32_MAX};

    uint32_t count = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(phys_dev, &count, NULL);
    VkQueueFamilyProperties *props = calloc(count, sizeof(VkQueueFamilyProperties));
    if (!props) {
        return indices;
    }
    vkGetPhysicalDeviceQueueFamilyProperties(phys_dev, &count, props);
    for (uint32_t i = 0; i < count; ++i) {
        if (props[i].queueCount == 0)
            continue;
        if ((props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)
            indices.graphics = i;
        if ((props[i].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0)
            indices.compute = i;
        if ((props[i].queueFlags & VK_QUEUE_TRANSFER_BIT) != 0)
            indices.transfer = i;

        VkBool32 present_supported = VK_FALSE;
        vkGetPhysicalDeviceSurfaceSupportKHR(phys_dev, i, surface, &present_supported);
        if (present_supported)
            indices.present = i;
    }

    if (indices.transfer == UINT32_MAX && indices.graphics != UINT32_MAX)
        indices.transfer = indices.graphics;
    else if (indices.transfer == UINT32_MAX && indices.compute != UINT32_MAX)
        indices.transfer = indices.compute;

    free(props);
    return indices;
}

static bool CheckDeviceExtensionSupported(VkPhysicalDevice phys_dev) {
    const char *required_extensions[] = {
        VK_KHR_SWAPCHAIN_EXTENSION_NAME,
    };

    uint32_t extension_count;
    vkEnumerateDeviceExtensionProperties(phys_dev, NULL, &extension_count, NULL);

    VkExtensionProperties *supported_extensions =
        calloc(extension_count, sizeof(VkExtensionProperties));
    if (!supported_extensions)
        return false;
    vkEnumerateDeviceExtensionProperties(phys_dev, NULL, &extension_count, supported_extensions);

    bool supported = true;
    for (uint32_t i = 0; i < RT_ARRAY_COUNT(required_extensions); ++i) {
        bool found = false;
        for (uint32_t j = 0; j < extension_count; ++j) {
            if (strncmp(supported_extensions[j].extensionName,
                        required_extensions[i],
                        VK_MAX_EXTENSION_NAME_SIZE) == 0) {
                found = true;
                break;
            }
        }
        if (!found) {
            supported = false;
            VkPhysicalDeviceProperties props;
            vkGetPhysicalDeviceProperties(phys_dev, &props);
            rtLog("VK", "Device %s does not support the required extension %s",
                  props.deviceName,
                  required_extensions[i]);
            goto out;
        }
    }

out:
    free(supported_extensions);
    return supported;
}

static rt_result ChoosePhysicalDevice(rt_vk_device *dev) {

    dev->phys_device          = VK_NULL_HANDLE;
    uint32_t phys_device_count = 0;
    VkResult result = vkEnumeratePhysicalDevices(dev->instance, &phys_device_count, NULL);
    if (result != VK_SUCCESS) {
        rtReportError("VK", "Failed to enumerate the physical devices.");
        return 2;
    }
    VkPhysicalDevice *phys_devices = calloc(phys_device_count, sizeof(VkPhysicalDevice));
    if (!phys_devices) {
        rtReportError("VK", "Failed to enumerate the physical devices: Out of memory.");
        return 2;
    }
    vkEnumeratePhysicalDevices(dev->instance, &phys_device_count, phys_devices);

    uint32_t highscore  = 0;
    uint32_t best_index = phys_device_count;
    for (uint32_t i = 0; i < phys_device_count; ++i) {
        VkPhysicalDeviceTimelineSemaphoreFeatures timeline_semaphore_features = {
            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES,
        };
        VkPhysicalDeviceSynchronization2Features synchronization2_features = {
            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES,
            .pNext = &timeline_semaphore_features,
        };
        VkPhysicalDeviceDynamicRenderingFeatures dynamic_rendering_features = {
            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
            .pNext = &synchronization2_features,
        };
        VkPhysicalDeviceDescriptorIndexingFeatures descriptor_indexing_features = {
            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES,
            .pNext = &dynamic_rendering_features,
        };
        VkPhysicalDeviceFeatures2 features = {
            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
            .pNext = &descriptor_indexing_features,
        };
        vkGetPhysicalDeviceFeatures2(phys_devices[i], &features);

        VkPhysicalDeviceDescriptorIndexingProperties descriptor_indexing_props = {
            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES,
            .pNext = NULL,
        };
        VkPhysicalDeviceProperties2 props = {
            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
            .pNext = &descriptor_indexing_props,
        };
        vkGetPhysicalDeviceProperties2(phys_devices[i], &props);

        if (!CheckDeviceExtensionSupported(phys_devices[i]))
            continue;

        rt_queue_indices indices = RetrieveQueueIndices(phys_devices[i], dev->surface);
        if (indices.compute == UINT32_MAX || indices.present == UINT32_MAX ||
            indices.graphics == UINT32_MAX)
            continue;

        if (!synchronization2_features.synchronization2 ||
            !dynamic_rendering_features.dynamicRendering ||
            !timeline_semaphore_features.timelineSemaphore)
            continue;

        /* Check for bindless support */
        if (!descriptor_indexing_features.runtimeDescriptorArray ||
            !descriptor_indexing_features.descriptorBindingPartiallyBound)
            continue;

        uint32_t score = 0;

        if (props.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
            score += 100;

        score += (props.properties.limits.maxFramebufferWidth / 100) *
                 (props.properties.limits.maxFramebufferHeight / 100);

        score +=
            (descriptor_indexing_props.shaderStorageBufferArrayNonUniformIndexingNative) ? 100 : 0;
        score +=
            (descriptor_indexing_props.shaderSampledImageArrayNonUniformIndexingNative) ? 100 : 0;

        if (score > highscore) {
            highscore  = score;
            best_index = i;
        }

        if (strncmp(props.properties.deviceName,
                    r_VkPhysDeviceName.s,
                    VK_MAX_PHYSICAL_DEVICE_NAME_SIZE) == 0) {
            best_index = i;
            break;
        }
    }
    if (best_index < phys_device_count) {
        dev->phys_device = phys_devices[best_index];

        VkPhysicalDeviceDescriptorIndexingProperties descriptor_indexing_props = {
            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES,
            .pNext = NULL,
        };
        VkPhysicalDeviceProperties2 props = {
            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
            .pNext = &descriptor_indexing_props,
        };
        VkPhysicalDeviceDescriptorIndexingFeatures descriptor_indexing_features = {
            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES,
        };
        VkPhysicalDeviceFeatures2 features = {
            .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
            .pNext = &descriptor_indexing_features,
        };
        vkGetPhysicalDeviceFeatures2(phys_devices[best_index], &features);
        vkGetPhysicalDeviceProperties2(phys_devices[best_index], &props);

        dev->phys_device_props            = props.properties;
        dev->descriptor_indexing_props    = descriptor_indexing_props;
        dev->phys_device_features         = features.features;
        dev->descriptor_indexing_features = descriptor_indexing_features;
    }
    free(phys_devices);

    if (dev->phys_device == VK_NULL_HANDLE) {
        rtReportError("vk", "Failed to find a suitable physical device.");
        return 3;
    }
    return RT_SUCCESS;
}

static rt_result CreateDevice(rt_vk_device *dev) {
    const char *extensions[] = {
        VK_KHR_SWAPCHAIN_EXTENSION_NAME,
    };

    rt_queue_indices queue_indices = RetrieveQueueIndices(dev->phys_device, dev->surface);

    dev->compute_family  = queue_indices.compute;
    dev->graphics_family = queue_indices.graphics;
    dev->present_family  = queue_indices.present;
    dev->transfer_family = queue_indices.transfer;

    float priority = 1.f;

    uint32_t distinct_queue_count = 1;
    VkDeviceQueueCreateInfo queue_info[4];
    queue_info[0].sType            = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    queue_info[0].pNext            = NULL;
    queue_info[0].flags            = 0;
    queue_info[0].queueCount       = 1;
    queue_info[0].queueFamilyIndex = queue_indices.graphics;
    queue_info[0].pQueuePriorities = &priority;
    dev->unique_families[0] = queue_indices.graphics;
    if (queue_indices.compute != queue_indices.graphics) {
        queue_info[1].sType            = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
        queue_info[1].pNext            = NULL;
        queue_info[1].flags            = 0;
        queue_info[1].queueCount       = 1;
        queue_info[1].queueFamilyIndex = queue_indices.compute;
        queue_info[1].pQueuePriorities = &priority;
        dev->unique_families[distinct_queue_count] = queue_indices.compute;
        ++distinct_queue_count;
    }
    if (queue_indices.present != queue_indices.graphics &&
        queue_indices.present != queue_indices.compute) {
        queue_info[distinct_queue_count].sType      = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
        queue_info[distinct_queue_count].pNext      = NULL;
        queue_info[distinct_queue_count].flags      = 0;
        queue_info[distinct_queue_count].queueCount = 1;
        queue_info[distinct_queue_count].queueFamilyIndex = queue_indices.present;
        queue_info[distinct_queue_count].pQueuePriorities = &priority;
        dev->unique_families[distinct_queue_count] = queue_indices.present;
        ++distinct_queue_count;
    }
    if (queue_indices.transfer != queue_indices.graphics &&
        queue_indices.transfer != queue_indices.compute &&
        queue_indices.transfer != queue_indices.present) {
        queue_info[distinct_queue_count].sType      = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
        queue_info[distinct_queue_count].pNext      = NULL;
        queue_info[distinct_queue_count].flags      = 0;
        queue_info[distinct_queue_count].queueCount = 1;
        queue_info[distinct_queue_count].queueFamilyIndex = queue_indices.transfer;
        queue_info[distinct_queue_count].pQueuePriorities = &priority;
        dev->unique_families[distinct_queue_count] = queue_indices.transfer;
        ++distinct_queue_count;
    }
    dev->unique_family_count = distinct_queue_count;

    VkPhysicalDeviceTimelineSemaphoreFeatures timeline_semaphore_features = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES,
    };
    VkPhysicalDeviceSynchronization2Features synchronization2_features = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES,
        .pNext = &timeline_semaphore_features,
    };
    VkPhysicalDeviceDynamicRenderingFeatures dynamic_rendering_features = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
        .pNext = &synchronization2_features,
    };
    VkPhysicalDeviceDescriptorIndexingFeatures indexing_features = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES,
        .pNext = &dynamic_rendering_features,
    };
    VkPhysicalDeviceFeatures2 features = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
                                          .pNext = &indexing_features};
    vkGetPhysicalDeviceFeatures2(dev->phys_device, &features);

    RT_ASSERT(indexing_features.runtimeDescriptorArray &&
                  indexing_features.descriptorBindingPartiallyBound,
              "We require a device that supports bindless vulkan.");

    VkDeviceCreateInfo device_info = {
        .sType                   = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
        .pNext                   = &features,
        .enabledExtensionCount   = RT_ARRAY_COUNT(extensions),
        .ppEnabledExtensionNames = extensions,
        .pQueueCreateInfos       = queue_info,
        .queueCreateInfoCount    = distinct_queue_count,
    };
    if (vkCreateDevice(dev->phys_device, &device_info, dev->alloc_cb, &dev->device) !=
        VK_SUCCESS) {
        rtReportError("VK", "Device creation failed.");
        return 10;
    }

    vkGetDeviceQueue(dev->device, queue_indices.graphics, 0, &dev->graphics_queue);
    vkGetDeviceQueue(dev->device, queue_indices.compute, 0, &dev->compute_queue);
    vkGetDeviceQueue(dev->device, queue_indices.present, 0, &dev->present_queue);
    vkGetDeviceQueue(dev->device, queue_indices.transfer, 0, &dev->transfer_queue);

    return RT_SUCCESS;
}

static rt_result CreatePerFrameObjects(rt_vk_device *dev) {
    for (unsigned int i = 0; i < dev->max_frames_in_flight; ++i) {
        VkSemaphoreCreateInfo semaphore_info = {
            .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
        };
        if (vkCreateSemaphore(dev->device,
                              &semaphore_info,
                              dev->alloc_cb,
                              &dev->frames[i].render_finished) != VK_SUCCESS) {
            return RT_UNKNOWN_ERROR;
        }
        if (vkCreateSemaphore(dev->device,
                              &semaphore_info,
                              dev->alloc_cb,
                              &dev->frames[i].image_available) != VK_SUCCESS) {
            return RT_UNKNOWN_ERROR;
        }
        if (vkCreateSemaphore(dev->device,
                              &semaphore_info,
                              dev->alloc_cb,
                              &dev->frames[i].swapchain_transitioned) != VK_SUCCESS) {
            return RT_UNKNOWN_ERROR;
        }
#ifdef RT_DEBUG
        char name[128];
        rtSPrint(name, 128, "Render Finished Semaphore (%u)", i);
        VkDebugUtilsObjectNameInfoEXT name_info = {
            .sType        = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
            .objectHandle = (uint64_t)dev->frames[i].render_finished,
            .objectType   = VK_OBJECT_TYPE_SEMAPHORE,
            .pObjectName  = name,
        };
        vkSetDebugUtilsObjectNameEXT(dev->device, &name_info);

        rtSPrint(name, 128, "Image Available Semaphore (%u)", i);
        name_info.objectHandle = (uint64_t)dev->frames[i].image_available;
        vkSetDebugUtilsObjectNameEXT(dev->device, &name_info);

        rtSPrint(name, 128, "Swapchain Transitioned Semaphore (%u)", i);
        name_info.objectHandle = (uint64_t)dev->frames[i].swapchain_transitioned;
        vkSetDebugUtilsObjectNameEXT(dev->device, &name_info);
#endif
    }
    return RT_SUCCESS;
}

void DestroyPerFrameObjects(rt_vk_device *dev) {
    for (unsigned int i = 0; i < dev->max_frames_in_flight; ++i) {
        vkDestroySemaphore(dev->device, dev->frames[i].image_available, dev->alloc_cb);
        vkDestroySemaphore(dev->device, dev->frames[i].render_finished, dev->alloc_cb);
        vkDestroySemaphore(dev->device, dev->frames[i].swapchain_transitioned, dev->alloc_cb);
    }
}


extern rt_result CreateSwapchain(rt_vk_device *dev);
extern rt_result RecreateSwapchain(rt_vk_device *dev);
extern void DestroySwapchain(rt_vk_device *dev);

rt_create_vk_device_result rtCreateVkDevice(const rt_renderer_window_info *info) {
    rtLog("VK", "Init");

    rt_timestamp initBegin = rtTimeNow();
    rt_vk_device dev;

    _tracking_alloc_cbs.pUserData       = NULL;
    _tracking_alloc_cbs.pfnAllocation   = TrackAllocation;
    _tracking_alloc_cbs.pfnReallocation = TrackReallocation;
    _tracking_alloc_cbs.pfnFree         = TrackFree;

    if (r_VkEnableAPIAllocTracking.i) {
        dev.alloc_cb = &_tracking_alloc_cbs;
    } else {
        dev.alloc_cb = NULL;
    }

    dev.max_frames_in_flight = RT_RESTRICT_VALUE_TO_BOUNDS(r_VkMaxFramesInFlight.i, 2, 3);
    rtLog("VK", "Max frames in flight: %u", dev.max_frames_in_flight);
    if ((int)dev.max_frames_in_flight != r_VkMaxFramesInFlight.i) {
        r_VkMaxFramesInFlight.i = (int)dev.max_frames_in_flight;
        rtNotifyCVARChange(&r_VkMaxFramesInFlight);
    }

    rt_result res;
    if ((res = CreateInstance(&dev)) != RT_SUCCESS)
        goto out;
    if ((res = CreateSurface(info, &dev)) != RT_SUCCESS)
        goto out;
    if ((res = ChoosePhysicalDevice(&dev)) != RT_SUCCESS)
        goto out;
    if ((res = CreateDevice(&dev)) != RT_SUCCESS)
        goto out;
    if ((res = CreatePerFrameObjects(&dev)) != RT_SUCCESS)
        goto out;
    if ((res = CreateSwapchain(&dev)) != RT_SUCCESS)
        goto out;
    rt_create_vk_physical_resource_manager_result phys_res_mgr_res = rtCreateVkPhysicalResourceManager(&dev);
    if ((res = phys_res_mgr_res.result) != RT_SUCCESS)
        goto out;
    dev.phys_res_mgr = phys_res_mgr_res.phys_res_mgr;
    rt_create_vk_bindless_registry_result bindless_registry_result = rtCreateVkBindlessRegistry(&dev);
    if ((res = bindless_registry_result.result) != RT_SUCCESS)
        goto out;
    dev.bindless_registry = bindless_registry_result.bindless_registry;

    rt_time_delta initTime = rtTimeBetween(initBegin, rtTimeNow());
    rtLog("VK", "Init complete. Took %lf seconds.", initTime);

out:
    return (rt_create_vk_device_result) {.result = res, .device = dev};
}

void rtDestroyVkDevice(rt_vk_device *dev) {
    rtLog("VK", "Shutdown");
    vkDeviceWaitIdle(dev->device);
    rtDestroyVkBindlessRegistry(&dev->bindless_registry);
    rtDestroyVkPhysicalResourceManager(&dev->phys_res_mgr);
    DestroySwapchain(dev);
    DestroyPerFrameObjects(dev);
    vkDestroyDevice(dev->device, dev->alloc_cb);
    vkDestroySurfaceKHR(dev->instance, dev->surface, dev->alloc_cb);
#ifdef RT_DEBUG
    vkDestroyDebugUtilsMessengerEXT(dev->instance, dev->messenger, dev->alloc_cb);
#endif
    vkDestroyInstance(dev->instance, dev->alloc_cb);
}

/* rt_render_device_i functions */
rt_physical_resource_manager_i rtVkDevGetPhysicalResourceManager(void *o) {
    rt_vk_device *dev = o;
    rt_physical_resource_manager_i iface = {
        .o = &dev->phys_res_mgr,
        .IsPresent = rtVkPhysicalResourceManagerIsPresent,
        .Destroy = rtVkPhysicalResourceManagerDestroy,
        .CreateBuffer = rtVkPhysicalResourceManagerCreateBuffer,
        .CreateTexture2D = rtVkPhysicalResourceManagerCreateTexture2D,
    };
    return iface;
}

rt_result rtVkDevSubmitCommandList(void *o, rt_render_command_list *list) {
    /* Dummy implementation */
    RT_UNUSED(o);

    size_t data_off = 0;
    for (uint32_t i = 0; i < list->length; ++i) {
        if (list->headers[i].type != RT_RENDER_COMMAND_DRAW_INDIRECT)
            break;
        rtLog("VK", "DrawIndirect %u", i);
        rtLog("VK", "Target queue: %u", list->headers[i].target_queue);

        RT_VERIFY(data_off < list->data_end);
        rt_draw_indirect_data *data = (rt_draw_indirect_data *)((char *)list->data + data_off);
        rtLog("VK", "buffer %u draw_count %u offset %u stride %u",
            data->buffer.value,
            data->draw_count,
            data->offset,
            data->stride);
        data_off += sizeof(rt_draw_indirect_data);
    }
    return RT_SUCCESS;
}