#include "renderer/renderer.h"
#include "ui/ui_core.h"
#include "ui/ui_icons.h"

#include <math.h>
#include <stdlib.h>
#include <string.h>

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>

#define VK_USE_PLATFORM_WIN32_KHR
#include <vulkan/vulkan.h>

// FreeType headers — temporarily undefine `internal` macro (base_core.h: #define internal static)
// because FreeType uses `internal` as a struct field name.
#undef internal
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_BITMAP_H
#define internal static
#include "renderer/font_inter.gen.h"

// Embedded SPIR-V shaders (generated at build time by glslc)
#include "renderer/ui_vert.spv.h"
#include "renderer/ui_frag.spv.h"

// MSVC ICE workaround: prevent inlining of large Vulkan setup functions
#ifdef _MSC_VER
#define VK_NOINLINE __declspec(noinline)
#else
#define VK_NOINLINE
#endif

#define NUM_BACK_BUFFERS  2
#define MAX_VERTICES      (64 * 1024)
#define MAX_INDICES       (MAX_VERTICES * 3)

// Font atlas
#define FONT_ATLAS_W      1024
#define FONT_ATLAS_H      1024
#define GLYPH_FIRST       32
#define GLYPH_LAST        126
#define GLYPH_COUNT       (GLYPH_LAST - GLYPH_FIRST + 1)

////////////////////////////////
// Vertex format for 2D UI rendering
// mode: 0 = SDF rounded rect, 1 = textured glyph, 2 = RGBA icon

typedef struct UIVertex {
    float pos[2];
    float uv[2];
    float col[4];
    float rect_min[2];
    float rect_max[2];
    float corner_radii[4]; // TL, TR, BR, BL
    float border_thickness;
    float softness;
    float mode;
} UIVertex;

////////////////////////////////
// Glyph info

typedef struct GlyphInfo {
    F32 u0, v0, u1, v1;
    F32 w, h;
    F32 x_advance;
} GlyphInfo;

////////////////////////////////
// Frame context

typedef struct FrameContext {
    VkCommandBuffer  command_buffer;
    VkFence          fence;
    U64              fence_value;
} FrameContext;

////////////////////////////////
// Renderer struct

struct Renderer {
    Renderer        *parent;
    HWND             hwnd;
    S32              width;
    S32              height;
    S32              frame_count;
    U32              frame_index;

    // Vulkan core
    VkInstance               instance;
    VkPhysicalDevice         physical_device;
    VkDevice                 device;
    VkQueue                  graphics_queue;
    U32                      queue_family;
    // Surface & swap chain
    VkSurfaceKHR             surface;
    VkSwapchainKHR           swap_chain;
    VkFormat                 swap_chain_format;
    VkImage                  swap_chain_images[NUM_BACK_BUFFERS];
    VkImageView              swap_chain_views[NUM_BACK_BUFFERS];
    VkFramebuffer            framebuffers[NUM_BACK_BUFFERS];
    B32                      swap_chain_occluded;

    // Render pass
    VkRenderPass             render_pass;

    // Command pool & per-frame resources
    VkCommandPool            command_pool;
    FrameContext             frames[NUM_BACK_BUFFERS];

    // Synchronization
    VkSemaphore              image_available_sema[NUM_BACK_BUFFERS];
    VkSemaphore              render_finished_sema[NUM_BACK_BUFFERS];

    // Pipeline
    VkPipelineLayout         pipeline_layout;
    VkPipeline               pipeline;
    VkDescriptorSetLayout    descriptor_set_layout;

    // Descriptor pool & sets
    VkDescriptorPool         descriptor_pool;
    VkDescriptorSet          font_descriptor_set;
    VkDescriptorSet          icon_descriptor_set;
    VkSampler                sampler;

    // Per-frame vertex/index buffers (double-buffered, persistently mapped)
    VkBuffer                 vertex_buffers[NUM_BACK_BUFFERS];
    VkDeviceMemory           vertex_memory[NUM_BACK_BUFFERS];
    void                    *vb_mapped[NUM_BACK_BUFFERS];

    VkBuffer                 index_buffers[NUM_BACK_BUFFERS];
    VkDeviceMemory           index_memory[NUM_BACK_BUFFERS];
    void                    *ib_mapped[NUM_BACK_BUFFERS];

    // Font atlas
    VkImage                  font_image;
    VkDeviceMemory           font_memory;
    VkImageView              font_view;
    GlyphInfo                glyphs[GLYPH_COUNT];
    F32                      font_atlas_size;
    F32                      font_line_height;

    // Icon atlas
    VkImage                  icon_image;
    VkDeviceMemory           icon_memory;
    VkImageView              icon_view;

    // FreeType
    FT_Library               ft_lib;
    FT_Face                  ft_face;

    // Clear color
    F32                      clear_r;
    F32                      clear_g;
    F32                      clear_b;

#ifdef _DEBUG
    VkDebugUtilsMessengerEXT debug_messenger;
#endif
};

////////////////////////////////
// Debug callback

#ifdef _DEBUG
static VKAPI_ATTR VkBool32 VKAPI_CALL vk_debug_callback(
    VkDebugUtilsMessageSeverityFlagBitsEXT severity,
    VkDebugUtilsMessageTypeFlagsEXT type,
    const VkDebugUtilsMessengerCallbackDataEXT *data,
    void *user_data)
{
    (void)type; (void)user_data;
    if (severity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
        OutputDebugStringA("VK: ");
        OutputDebugStringA(data->pMessage);
        OutputDebugStringA("\n");
    }
    return VK_FALSE;
}
#endif

////////////////////////////////
// Helper: begin/end one-shot command buffer

static VkCommandBuffer begin_one_shot(Renderer *r) {
    VkCommandBufferAllocateInfo ai = {0};
    ai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    ai.commandPool = r->command_pool;
    ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    ai.commandBufferCount = 1;
    VkCommandBuffer cb;
    vkAllocateCommandBuffers(r->device, &ai, &cb);

    VkCommandBufferBeginInfo bi = {0};
    bi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
    vkBeginCommandBuffer(cb, &bi);
    return cb;
}

static void end_one_shot(Renderer *r, VkCommandBuffer cb) {
    vkEndCommandBuffer(cb);
    VkSubmitInfo si = {0};
    si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    si.commandBufferCount = 1;
    si.pCommandBuffers = &cb;
    vkQueueSubmit(r->graphics_queue, 1, &si, VK_NULL_HANDLE);
    vkQueueWaitIdle(r->graphics_queue);
    vkFreeCommandBuffers(r->device, r->command_pool, 1, &cb);
}

////////////////////////////////
// Helper: transition image layout

static void transition_image(VkCommandBuffer cb, VkImage image,
    VkImageLayout old_layout, VkImageLayout new_layout,
    VkAccessFlags src_access, VkAccessFlags dst_access,
    VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage)
{
    VkImageMemoryBarrier barrier = {0};
    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    barrier.oldLayout = old_layout;
    barrier.newLayout = new_layout;
    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.image = image;
    barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    barrier.subresourceRange.baseMipLevel = 0;
    barrier.subresourceRange.levelCount = 1;
    barrier.subresourceRange.baseArrayLayer = 0;
    barrier.subresourceRange.layerCount = 1;
    barrier.srcAccessMask = src_access;
    barrier.dstAccessMask = dst_access;
    vkCmdPipelineBarrier(cb, src_stage, dst_stage, 0, 0, NULL, 0, NULL, 1, &barrier);
}

////////////////////////////////
// Vulkan infrastructure

static VK_NOINLINE B32 create_instance(Renderer *r) {
    VkApplicationInfo app_info = {0};
    app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    app_info.pApplicationName = "autosample";
    app_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0);
    app_info.pEngineName = "autosample";
    app_info.engineVersion = VK_MAKE_VERSION(0, 1, 0);
    app_info.apiVersion = VK_API_VERSION_1_2;

    const char *extensions[] = {
        VK_KHR_SURFACE_EXTENSION_NAME,
        VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
#ifdef _DEBUG
        VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
#endif
    };

    const char *layers[] = {
#ifdef _DEBUG
        "VK_LAYER_KHRONOS_validation",
#endif
    };

    VkInstanceCreateInfo ci = {0};
    ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    ci.pApplicationInfo = &app_info;
    ci.enabledExtensionCount = ArrayCount(extensions);
    ci.ppEnabledExtensionNames = extensions;
    ci.enabledLayerCount = ArrayCount(layers);
    ci.ppEnabledLayerNames = layers;

    if (vkCreateInstance(&ci, NULL, &r->instance) != VK_SUCCESS)
        return 0;

#ifdef _DEBUG
    {
        VkDebugUtilsMessengerCreateInfoEXT dbg = {0};
        dbg.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
        dbg.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
                            | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
        dbg.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
                        | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
                        | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
        dbg.pfnUserCallback = vk_debug_callback;

        PFN_vkCreateDebugUtilsMessengerEXT func =
            (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(r->instance, "vkCreateDebugUtilsMessengerEXT");
        if (func) func(r->instance, &dbg, NULL, &r->debug_messenger);
    }
#endif

    return 1;
}

static VK_NOINLINE B32 create_surface(Renderer *r) {
    VkWin32SurfaceCreateInfoKHR ci = {0};
    ci.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
    ci.hinstance = GetModuleHandle(NULL);
    ci.hwnd = r->hwnd;
    return vkCreateWin32SurfaceKHR(r->instance, &ci, NULL, &r->surface) == VK_SUCCESS;
}

static VK_NOINLINE B32 pick_physical_device(Renderer *r) {
    U32 count = 0;
    vkEnumeratePhysicalDevices(r->instance, &count, NULL);
    if (count == 0) return 0;

    VkPhysicalDevice devices[16];
    if (count > 16) count = 16;
    vkEnumeratePhysicalDevices(r->instance, &count, devices);

    // Prefer discrete GPU
    for (U32 i = 0; i < count; i++) {
        VkPhysicalDeviceProperties props;
        vkGetPhysicalDeviceProperties(devices[i], &props);
        if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
            r->physical_device = devices[i];
            return 1;
        }
    }
    r->physical_device = devices[0];
    return 1;
}

static VK_NOINLINE B32 find_queue_family(Renderer *r) {
    U32 count = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(r->physical_device, &count, NULL);
    VkQueueFamilyProperties props[64];
    if (count > 64) count = 64;
    vkGetPhysicalDeviceQueueFamilyProperties(r->physical_device, &count, props);

    for (U32 i = 0; i < count; i++) {
        VkBool32 present = VK_FALSE;
        vkGetPhysicalDeviceSurfaceSupportKHR(r->physical_device, i, r->surface, &present);
        if ((props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && present) {
            r->queue_family = i;
            return 1;
        }
    }
    return 0;
}

static VK_NOINLINE B32 create_device(Renderer *r) {
    float priority = 1.0f;
    VkDeviceQueueCreateInfo queue_ci = {0};
    queue_ci.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    queue_ci.queueFamilyIndex = r->queue_family;
    queue_ci.queueCount = 1;
    queue_ci.pQueuePriorities = &priority;

    const char *extensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };

    VkDeviceCreateInfo ci = {0};
    ci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
    ci.queueCreateInfoCount = 1;
    ci.pQueueCreateInfos = &queue_ci;
    ci.enabledExtensionCount = 1;
    ci.ppEnabledExtensionNames = extensions;

    if (vkCreateDevice(r->physical_device, &ci, NULL, &r->device) != VK_SUCCESS)
        return 0;

    vkGetDeviceQueue(r->device, r->queue_family, 0, &r->graphics_queue);
    return 1;
}

static U32 find_memory_type(VkPhysicalDevice phys_dev, U32 type_filter, VkMemoryPropertyFlags props) {
    VkPhysicalDeviceMemoryProperties mem_props;
    vkGetPhysicalDeviceMemoryProperties(phys_dev, &mem_props);
    for (U32 i = 0; i < mem_props.memoryTypeCount; i++) {
        if ((type_filter & (1 << i)) && (mem_props.memoryTypes[i].propertyFlags & props) == props)
            return i;
    }
    return 0; // fallback
}

static VK_NOINLINE B32 create_swap_chain(Renderer *r) {
    VkSurfaceCapabilitiesKHR caps;
    vkGetPhysicalDeviceSurfaceCapabilitiesKHR(r->physical_device, r->surface, &caps);

    // Pick format
    U32 fmt_count = 0;
    vkGetPhysicalDeviceSurfaceFormatsKHR(r->physical_device, r->surface, &fmt_count, NULL);
    VkSurfaceFormatKHR formats[64];
    if (fmt_count > 64) fmt_count = 64;
    vkGetPhysicalDeviceSurfaceFormatsKHR(r->physical_device, r->surface, &fmt_count, formats);

    VkSurfaceFormatKHR chosen = formats[0];
    for (U32 i = 0; i < fmt_count; i++) {
        if (formats[i].format == VK_FORMAT_B8G8R8A8_UNORM &&
            formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
            chosen = formats[i];
            break;
        }
    }
    r->swap_chain_format = chosen.format;

    // Pick present mode (mailbox preferred, fallback fifo)
    U32 pm_count = 0;
    vkGetPhysicalDeviceSurfacePresentModesKHR(r->physical_device, r->surface, &pm_count, NULL);
    VkPresentModeKHR modes[16];
    if (pm_count > 16) pm_count = 16;
    vkGetPhysicalDeviceSurfacePresentModesKHR(r->physical_device, r->surface, &pm_count, modes);

    VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR;
    for (U32 i = 0; i < pm_count; i++) {
        if (modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
            present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
            break;
        }
    }

    VkExtent2D extent;
    if (caps.currentExtent.width != 0xFFFFFFFF) {
        extent = caps.currentExtent;
    } else {
        extent.width = (U32)r->width;
        extent.height = (U32)r->height;
    }

    U32 image_count = NUM_BACK_BUFFERS;
    if (image_count < caps.minImageCount) image_count = caps.minImageCount;
    if (caps.maxImageCount > 0 && image_count > caps.maxImageCount)
        image_count = caps.maxImageCount;

    VkSwapchainCreateInfoKHR ci = {0};
    ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
    ci.surface = r->surface;
    ci.minImageCount = image_count;
    ci.imageFormat = chosen.format;
    ci.imageColorSpace = chosen.colorSpace;
    ci.imageExtent = extent;
    ci.imageArrayLayers = 1;
    ci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    ci.preTransform = caps.currentTransform;
    ci.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
    ci.presentMode = present_mode;
    ci.clipped = VK_TRUE;
    ci.oldSwapchain = r->swap_chain; // for recreation

    if (vkCreateSwapchainKHR(r->device, &ci, NULL, &r->swap_chain) != VK_SUCCESS)
        return 0;

    // Get swap chain images
    U32 sc_count = NUM_BACK_BUFFERS;
    vkGetSwapchainImagesKHR(r->device, r->swap_chain, &sc_count, r->swap_chain_images);

    // Create image views
    for (U32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        VkImageViewCreateInfo vi = {0};
        vi.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
        vi.image = r->swap_chain_images[i];
        vi.viewType = VK_IMAGE_VIEW_TYPE_2D;
        vi.format = r->swap_chain_format;
        vi.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        vi.subresourceRange.levelCount = 1;
        vi.subresourceRange.layerCount = 1;
        if (vkCreateImageView(r->device, &vi, NULL, &r->swap_chain_views[i]) != VK_SUCCESS)
            return 0;
    }

    r->width = (S32)extent.width;
    r->height = (S32)extent.height;
    return 1;
}

static VK_NOINLINE B32 create_render_pass(Renderer *r) {
    VkAttachmentDescription color_attach = {0};
    color_attach.format = r->swap_chain_format;
    color_attach.samples = VK_SAMPLE_COUNT_1_BIT;
    color_attach.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    color_attach.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
    color_attach.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    color_attach.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    color_attach.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    color_attach.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

    VkAttachmentReference color_ref = {0};
    color_ref.attachment = 0;
    color_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

    VkSubpassDescription subpass = {0};
    subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    subpass.colorAttachmentCount = 1;
    subpass.pColorAttachments = &color_ref;

    VkSubpassDependency dep = {0};
    dep.srcSubpass = VK_SUBPASS_EXTERNAL;
    dep.dstSubpass = 0;
    dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dep.srcAccessMask = 0;
    dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;

    VkRenderPassCreateInfo ci = {0};
    ci.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
    ci.attachmentCount = 1;
    ci.pAttachments = &color_attach;
    ci.subpassCount = 1;
    ci.pSubpasses = &subpass;
    ci.dependencyCount = 1;
    ci.pDependencies = &dep;

    return vkCreateRenderPass(r->device, &ci, NULL, &r->render_pass) == VK_SUCCESS;
}

static VK_NOINLINE B32 create_framebuffers(Renderer *r) {
    for (U32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        VkFramebufferCreateInfo ci = {0};
        ci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
        ci.renderPass = r->render_pass;
        ci.attachmentCount = 1;
        ci.pAttachments = &r->swap_chain_views[i];
        ci.width = (U32)r->width;
        ci.height = (U32)r->height;
        ci.layers = 1;
        if (vkCreateFramebuffer(r->device, &ci, NULL, &r->framebuffers[i]) != VK_SUCCESS)
            return 0;
    }
    return 1;
}

static VK_NOINLINE B32 create_command_resources(Renderer *r) {
    VkCommandPoolCreateInfo pool_ci = {0};
    pool_ci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
    pool_ci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
    pool_ci.queueFamilyIndex = r->queue_family;
    if (vkCreateCommandPool(r->device, &pool_ci, NULL, &r->command_pool) != VK_SUCCESS)
        return 0;

    VkCommandBufferAllocateInfo alloc_info = {0};
    alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    alloc_info.commandPool = r->command_pool;
    alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    alloc_info.commandBufferCount = NUM_BACK_BUFFERS;

    VkCommandBuffer cbs[NUM_BACK_BUFFERS];
    if (vkAllocateCommandBuffers(r->device, &alloc_info, cbs) != VK_SUCCESS)
        return 0;

    for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        r->frames[i].command_buffer = cbs[i];

        VkFenceCreateInfo fence_ci = {0};
        fence_ci.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
        fence_ci.flags = VK_FENCE_CREATE_SIGNALED_BIT;
        if (vkCreateFence(r->device, &fence_ci, NULL, &r->frames[i].fence) != VK_SUCCESS)
            return 0;
    }

    for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        VkSemaphoreCreateInfo sem_ci = {0};
        sem_ci.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
        if (vkCreateSemaphore(r->device, &sem_ci, NULL, &r->image_available_sema[i]) != VK_SUCCESS)
            return 0;
        if (vkCreateSemaphore(r->device, &sem_ci, NULL, &r->render_finished_sema[i]) != VK_SUCCESS)
            return 0;
    }

    return 1;
}

static VK_NOINLINE B32 create_sampler(Renderer *r) {
    VkSamplerCreateInfo ci = {0};
    ci.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
    ci.magFilter = VK_FILTER_LINEAR;
    ci.minFilter = VK_FILTER_LINEAR;
    ci.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
    ci.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    ci.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    ci.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
    return vkCreateSampler(r->device, &ci, NULL, &r->sampler) == VK_SUCCESS;
}

static VK_NOINLINE B32 create_descriptor_resources(Renderer *r) {
    // Descriptor set layout: single combined image sampler at binding 0
    VkDescriptorSetLayoutBinding binding = {0};
    binding.binding = 0;
    binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    binding.descriptorCount = 1;
    binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;

    VkDescriptorSetLayoutCreateInfo layout_ci = {0};
    layout_ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
    layout_ci.bindingCount = 1;
    layout_ci.pBindings = &binding;
    if (vkCreateDescriptorSetLayout(r->device, &layout_ci, NULL, &r->descriptor_set_layout) != VK_SUCCESS)
        return 0;

    // Descriptor pool: 2 sets (font + icon)
    VkDescriptorPoolSize pool_size = {0};
    pool_size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    pool_size.descriptorCount = 2;

    VkDescriptorPoolCreateInfo pool_ci = {0};
    pool_ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
    pool_ci.maxSets = 2;
    pool_ci.poolSizeCount = 1;
    pool_ci.pPoolSizes = &pool_size;
    if (vkCreateDescriptorPool(r->device, &pool_ci, NULL, &r->descriptor_pool) != VK_SUCCESS)
        return 0;

    // Allocate font descriptor set
    VkDescriptorSetAllocateInfo alloc_info = {0};
    alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
    alloc_info.descriptorPool = r->descriptor_pool;
    alloc_info.descriptorSetCount = 1;
    alloc_info.pSetLayouts = &r->descriptor_set_layout;
    if (vkAllocateDescriptorSets(r->device, &alloc_info, &r->font_descriptor_set) != VK_SUCCESS)
        return 0;

    return 1;
}

static VkShaderModule create_shader_module(Renderer *r, const U32 *code, size_t size) {
    VkShaderModuleCreateInfo ci = {0};
    ci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
    ci.codeSize = size;
    ci.pCode = code;
    VkShaderModule module = VK_NULL_HANDLE;
    vkCreateShaderModule(r->device, &ci, NULL, &module);
    return module;
}

static VK_NOINLINE B32 create_pipeline(Renderer *r) {
    VkShaderModule vert_module = create_shader_module(r, ui_vert_spv, sizeof(ui_vert_spv));
    VkShaderModule frag_module = create_shader_module(r, ui_frag_spv, sizeof(ui_frag_spv));
    if (!vert_module || !frag_module) return 0;

    VkPipelineShaderStageCreateInfo stages[2] = {{0}, {0}};
    stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
    stages[0].module = vert_module;
    stages[0].pName = "main";
    stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
    stages[1].module = frag_module;
    stages[1].pName = "main";

    // Vertex input
    VkVertexInputBindingDescription binding = {0};
    binding.binding = 0;
    binding.stride = sizeof(UIVertex);
    binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

    VkVertexInputAttributeDescription attrs[9] = {0};
    attrs[0].location = 0; attrs[0].binding = 0; attrs[0].format = VK_FORMAT_R32G32_SFLOAT;       attrs[0].offset = offsetof(UIVertex, pos);
    attrs[1].location = 1; attrs[1].binding = 0; attrs[1].format = VK_FORMAT_R32G32_SFLOAT;       attrs[1].offset = offsetof(UIVertex, uv);
    attrs[2].location = 2; attrs[2].binding = 0; attrs[2].format = VK_FORMAT_R32G32B32A32_SFLOAT; attrs[2].offset = offsetof(UIVertex, col);
    attrs[3].location = 3; attrs[3].binding = 0; attrs[3].format = VK_FORMAT_R32G32_SFLOAT;       attrs[3].offset = offsetof(UIVertex, rect_min);
    attrs[4].location = 4; attrs[4].binding = 0; attrs[4].format = VK_FORMAT_R32G32_SFLOAT;       attrs[4].offset = offsetof(UIVertex, rect_max);
    attrs[5].location = 5; attrs[5].binding = 0; attrs[5].format = VK_FORMAT_R32G32B32A32_SFLOAT; attrs[5].offset = offsetof(UIVertex, corner_radii);
    attrs[6].location = 6; attrs[6].binding = 0; attrs[6].format = VK_FORMAT_R32_SFLOAT;          attrs[6].offset = offsetof(UIVertex, border_thickness);
    attrs[7].location = 7; attrs[7].binding = 0; attrs[7].format = VK_FORMAT_R32_SFLOAT;          attrs[7].offset = offsetof(UIVertex, softness);
    attrs[8].location = 8; attrs[8].binding = 0; attrs[8].format = VK_FORMAT_R32_SFLOAT;          attrs[8].offset = offsetof(UIVertex, mode);

    VkPipelineVertexInputStateCreateInfo vertex_input = {0};
    vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
    vertex_input.vertexBindingDescriptionCount = 1;
    vertex_input.pVertexBindingDescriptions = &binding;
    vertex_input.vertexAttributeDescriptionCount = 9;
    vertex_input.pVertexAttributeDescriptions = attrs;

    VkPipelineInputAssemblyStateCreateInfo input_assembly = {0};
    input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
    input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;

    // Dynamic viewport/scissor
    VkDynamicState dynamic_states[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
    VkPipelineDynamicStateCreateInfo dynamic_state = {0};
    dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
    dynamic_state.dynamicStateCount = 2;
    dynamic_state.pDynamicStates = dynamic_states;

    VkPipelineViewportStateCreateInfo viewport_state = {0};
    viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
    viewport_state.viewportCount = 1;
    viewport_state.scissorCount = 1;

    VkPipelineRasterizationStateCreateInfo rasterizer = {0};
    rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
    rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
    rasterizer.lineWidth = 1.0f;
    rasterizer.cullMode = VK_CULL_MODE_NONE;
    rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;

    VkPipelineMultisampleStateCreateInfo multisample = {0};
    multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
    multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;

    VkPipelineColorBlendAttachmentState blend_attach = {0};
    blend_attach.blendEnable = VK_TRUE;
    blend_attach.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
    blend_attach.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
    blend_attach.colorBlendOp = VK_BLEND_OP_ADD;
    blend_attach.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
    blend_attach.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
    blend_attach.alphaBlendOp = VK_BLEND_OP_ADD;
    blend_attach.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
                                | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;

    VkPipelineColorBlendStateCreateInfo blend = {0};
    blend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
    blend.attachmentCount = 1;
    blend.pAttachments = &blend_attach;

    VkPipelineDepthStencilStateCreateInfo depth_stencil = {0};
    depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;

    // Pipeline layout: push constants + 1 descriptor set
    VkPushConstantRange push_range = {0};
    push_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
    push_range.offset = 0;
    push_range.size = 4 * sizeof(float); // viewport_size + padding

    VkPipelineLayoutCreateInfo layout_ci = {0};
    layout_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
    layout_ci.setLayoutCount = 1;
    layout_ci.pSetLayouts = &r->descriptor_set_layout;
    layout_ci.pushConstantRangeCount = 1;
    layout_ci.pPushConstantRanges = &push_range;
    if (vkCreatePipelineLayout(r->device, &layout_ci, NULL, &r->pipeline_layout) != VK_SUCCESS) {
        vkDestroyShaderModule(r->device, vert_module, NULL);
        vkDestroyShaderModule(r->device, frag_module, NULL);
        return 0;
    }

    VkGraphicsPipelineCreateInfo pipeline_ci = {0};
    pipeline_ci.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
    pipeline_ci.stageCount = 2;
    pipeline_ci.pStages = stages;
    pipeline_ci.pVertexInputState = &vertex_input;
    pipeline_ci.pInputAssemblyState = &input_assembly;
    pipeline_ci.pViewportState = &viewport_state;
    pipeline_ci.pRasterizationState = &rasterizer;
    pipeline_ci.pMultisampleState = &multisample;
    pipeline_ci.pDepthStencilState = &depth_stencil;
    pipeline_ci.pColorBlendState = &blend;
    pipeline_ci.pDynamicState = &dynamic_state;
    pipeline_ci.layout = r->pipeline_layout;
    pipeline_ci.renderPass = r->render_pass;
    pipeline_ci.subpass = 0;

    VkResult result = vkCreateGraphicsPipelines(r->device, VK_NULL_HANDLE, 1, &pipeline_ci, NULL, &r->pipeline);

    vkDestroyShaderModule(r->device, vert_module, NULL);
    vkDestroyShaderModule(r->device, frag_module, NULL);

    return result == VK_SUCCESS;
}

static VK_NOINLINE B32 create_ui_buffers(Renderer *r) {
    for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        // Vertex buffer
        VkBufferCreateInfo buf_ci = {0};
        buf_ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
        buf_ci.size = MAX_VERTICES * sizeof(UIVertex);
        buf_ci.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;

        if (vkCreateBuffer(r->device, &buf_ci, NULL, &r->vertex_buffers[i]) != VK_SUCCESS)
            return 0;
        VkMemoryRequirements mem_req;
        vkGetBufferMemoryRequirements(r->device, r->vertex_buffers[i], &mem_req);
        VkMemoryAllocateInfo alloc_info = {0};
        alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
        alloc_info.allocationSize = mem_req.size;
        alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
        if (vkAllocateMemory(r->device, &alloc_info, NULL, &r->vertex_memory[i]) != VK_SUCCESS)
            return 0;
        vkBindBufferMemory(r->device, r->vertex_buffers[i], r->vertex_memory[i], 0);
        vkMapMemory(r->device, r->vertex_memory[i], 0, buf_ci.size, 0, &r->vb_mapped[i]);

        // Index buffer
        buf_ci.size = MAX_INDICES * sizeof(U32);
        buf_ci.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;

        if (vkCreateBuffer(r->device, &buf_ci, NULL, &r->index_buffers[i]) != VK_SUCCESS)
            return 0;
        vkGetBufferMemoryRequirements(r->device, r->index_buffers[i], &mem_req);
        alloc_info.allocationSize = mem_req.size;
        alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
        if (vkAllocateMemory(r->device, &alloc_info, NULL, &r->index_memory[i]) != VK_SUCCESS)
            return 0;
        vkBindBufferMemory(r->device, r->index_buffers[i], r->index_memory[i], 0);
        vkMapMemory(r->device, r->index_memory[i], 0, buf_ci.size, 0, &r->ib_mapped[i]);
    }
    return 1;
}

////////////////////////////////
// Helper: update a descriptor set to point to an image view

static void update_descriptor_set(Renderer *r, VkDescriptorSet set, VkImageView view) {
    VkDescriptorImageInfo img_info = {0};
    img_info.sampler = r->sampler;
    img_info.imageView = view;
    img_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;

    VkWriteDescriptorSet write = {0};
    write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    write.dstSet = set;
    write.dstBinding = 0;
    write.descriptorCount = 1;
    write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    write.pImageInfo = &img_info;
    vkUpdateDescriptorSets(r->device, 1, &write, 0, NULL);
}

////////////////////////////////
// Font atlas (FreeType)

static void init_freetype(Renderer *r) {
    FT_Init_FreeType(&r->ft_lib);
    FT_New_Memory_Face(r->ft_lib, font_inter_data, font_inter_size, 0, &r->ft_face);
}

static VK_NOINLINE B32 create_font_atlas(Renderer *r, F32 font_size) {
    S32 pixel_size = (S32)(font_size + 0.5f);
    r->font_atlas_size = font_size;

    FT_Set_Pixel_Sizes(r->ft_face, 0, pixel_size);
    r->font_line_height = (F32)(r->ft_face->size->metrics.height >> 6);

    U8 *atlas_data = (U8 *)calloc(1, FONT_ATLAS_W * FONT_ATLAS_H);

    S32 pen_x = 1, pen_y = 1;
    S32 row_height = 0;
    F32 ascender = (F32)(r->ft_face->size->metrics.ascender >> 6);

    for (S32 i = 0; i < GLYPH_COUNT; i++) {
        char ch = (char)(GLYPH_FIRST + i);
        if (FT_Load_Char(r->ft_face, ch, FT_LOAD_RENDER)) continue;

        FT_GlyphSlot g = r->ft_face->glyph;
        S32 bw = (S32)g->bitmap.width;
        S32 bh = (S32)g->bitmap.rows;
        S32 pad = 2;
        S32 cell_w = bw + pad;
        S32 cell_h = (S32)r->font_line_height + pad;

        if (pen_x + cell_w >= FONT_ATLAS_W) {
            pen_x = 1;
            pen_y += row_height + 1;
            row_height = 0;
        }
        if (pen_y + cell_h >= FONT_ATLAS_H) break;

        S32 y_off = (S32)ascender - g->bitmap_top;
        for (S32 y = 0; y < bh; y++) {
            S32 dst_y = pen_y + y_off + y;
            if (dst_y < 0 || dst_y >= FONT_ATLAS_H) continue;
            for (S32 x = 0; x < bw; x++) {
                S32 dst_x = pen_x + g->bitmap_left + x;
                if (dst_x < 0 || dst_x >= FONT_ATLAS_W) continue;
                atlas_data[dst_y * FONT_ATLAS_W + dst_x] = g->bitmap.buffer[y * g->bitmap.pitch + x];
            }
        }

        r->glyphs[i].u0 = (F32)pen_x / (F32)FONT_ATLAS_W;
        r->glyphs[i].v0 = (F32)pen_y / (F32)FONT_ATLAS_H;
        r->glyphs[i].u1 = (F32)(pen_x + cell_w) / (F32)FONT_ATLAS_W;
        r->glyphs[i].v1 = (F32)(pen_y + cell_h) / (F32)FONT_ATLAS_H;
        r->glyphs[i].w = (F32)cell_w;
        r->glyphs[i].h = (F32)cell_h;
        r->glyphs[i].x_advance = (F32)(g->advance.x >> 6);

        if (cell_h > row_height) row_height = cell_h;
        pen_x += cell_w + 1;
    }

    // Create Vulkan image
    VkImageCreateInfo img_ci = {0};
    img_ci.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    img_ci.imageType = VK_IMAGE_TYPE_2D;
    img_ci.format = VK_FORMAT_R8_UNORM;
    img_ci.extent.width = FONT_ATLAS_W;
    img_ci.extent.height = FONT_ATLAS_H;
    img_ci.extent.depth = 1;
    img_ci.mipLevels = 1;
    img_ci.arrayLayers = 1;
    img_ci.samples = VK_SAMPLE_COUNT_1_BIT;
    img_ci.tiling = VK_IMAGE_TILING_OPTIMAL;
    img_ci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;

    if (vkCreateImage(r->device, &img_ci, NULL, &r->font_image) != VK_SUCCESS) {
        free(atlas_data);
        return 0;
    }
    {
        VkMemoryRequirements mem_req;
        vkGetImageMemoryRequirements(r->device, r->font_image, &mem_req);
        VkMemoryAllocateInfo alloc_info = {0};
        alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
        alloc_info.allocationSize = mem_req.size;
        alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
            VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
        if (vkAllocateMemory(r->device, &alloc_info, NULL, &r->font_memory) != VK_SUCCESS) {
            free(atlas_data);
            return 0;
        }
        vkBindImageMemory(r->device, r->font_image, r->font_memory, 0);
    }

    // Staging buffer
    VkBufferCreateInfo staging_ci = {0};
    staging_ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    staging_ci.size = FONT_ATLAS_W * FONT_ATLAS_H;
    staging_ci.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;

    VkBuffer staging_buf;
    VkDeviceMemory staging_memory;
    void *staging_mapped;
    vkCreateBuffer(r->device, &staging_ci, NULL, &staging_buf);
    {
        VkMemoryRequirements mem_req;
        vkGetBufferMemoryRequirements(r->device, staging_buf, &mem_req);
        VkMemoryAllocateInfo alloc_info = {0};
        alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
        alloc_info.allocationSize = mem_req.size;
        alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
        vkAllocateMemory(r->device, &alloc_info, NULL, &staging_memory);
        vkBindBufferMemory(r->device, staging_buf, staging_memory, 0);
        vkMapMemory(r->device, staging_memory, 0, staging_ci.size, 0, &staging_mapped);
    }
    memcpy(staging_mapped, atlas_data, FONT_ATLAS_W * FONT_ATLAS_H);
    free(atlas_data);

    // Copy via command buffer
    VkCommandBuffer cb = begin_one_shot(r);

    transition_image(cb, r->font_image,
        VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        0, VK_ACCESS_TRANSFER_WRITE_BIT,
        VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);

    VkBufferImageCopy region = {0};
    region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    region.imageSubresource.layerCount = 1;
    region.imageExtent.width = FONT_ATLAS_W;
    region.imageExtent.height = FONT_ATLAS_H;
    region.imageExtent.depth = 1;
    vkCmdCopyBufferToImage(cb, staging_buf, r->font_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);

    transition_image(cb, r->font_image,
        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
        VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
        VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);

    end_one_shot(r, cb);

    vkDestroyBuffer(r->device, staging_buf, NULL);
    vkFreeMemory(r->device, staging_memory, NULL);

    // Image view
    VkImageViewCreateInfo view_ci = {0};
    view_ci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    view_ci.image = r->font_image;
    view_ci.viewType = VK_IMAGE_VIEW_TYPE_2D;
    view_ci.format = VK_FORMAT_R8_UNORM;
    view_ci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    view_ci.subresourceRange.levelCount = 1;
    view_ci.subresourceRange.layerCount = 1;
    if (vkCreateImageView(r->device, &view_ci, NULL, &r->font_view) != VK_SUCCESS)
        return 0;

    // Update descriptor
    update_descriptor_set(r, r->font_descriptor_set, r->font_view);

    return 1;
}

////////////////////////////////
// Text measurement callback for UI system

Vec2F32 renderer_measure_text(const char *text, S32 length, F32 font_size, void *user_data) {
    Renderer *r = (Renderer *)user_data;
    if (!r || length == 0) return v2f32(0, font_size);

    FT_Set_Pixel_Sizes(r->ft_face, 0, (FT_UInt)(font_size + 0.5f));

    F32 width = 0;
    for (S32 i = 0; i < length; i++) {
        if (FT_Load_Char(r->ft_face, (FT_ULong)(unsigned char)text[i], FT_LOAD_DEFAULT)) continue;
        width += (F32)(r->ft_face->glyph->advance.x >> 6);
    }
    F32 height = (F32)(r->ft_face->size->metrics.height >> 6);
    return v2f32(width, height);
}

////////////////////////////////
// Quad emission helpers

typedef struct DrawBatch {
    UIVertex *vertices;
    U32      *indices;
    U32       vertex_count;
    U32       index_count;
} DrawBatch;

static void emit_quad(DrawBatch *batch,
    F32 x0, F32 y0, F32 x1, F32 y1,
    F32 u0, F32 v0, F32 u1, F32 v1,
    F32 cr, F32 cg, F32 cb, F32 ca,
    F32 rmin_x, F32 rmin_y, F32 rmax_x, F32 rmax_y,
    F32 cr_tl, F32 cr_tr, F32 cr_br, F32 cr_bl,
    F32 border_thickness, F32 softness, F32 mode)
{
    if (batch->vertex_count + 4 > MAX_VERTICES || batch->index_count + 6 > MAX_INDICES)
        return;

    U32 base = batch->vertex_count;
    UIVertex *v = &batch->vertices[base];

    F32 px0 = x0, py0 = y0, px1 = x1, py1 = y1;
    if (mode < 0.5f) {
        F32 pad = softness + 1.0f;
        px0 -= pad; py0 -= pad; px1 += pad; py1 += pad;
    }

    v[0].pos[0] = px0; v[0].pos[1] = py0; v[0].uv[0] = u0; v[0].uv[1] = v0;
    v[1].pos[0] = px1; v[1].pos[1] = py0; v[1].uv[0] = u1; v[1].uv[1] = v0;
    v[2].pos[0] = px1; v[2].pos[1] = py1; v[2].uv[0] = u1; v[2].uv[1] = v1;
    v[3].pos[0] = px0; v[3].pos[1] = py1; v[3].uv[0] = u0; v[3].uv[1] = v1;

    for (S32 i = 0; i < 4; i++) {
        v[i].col[0] = cr; v[i].col[1] = cg; v[i].col[2] = cb; v[i].col[3] = ca;
        v[i].rect_min[0] = rmin_x; v[i].rect_min[1] = rmin_y;
        v[i].rect_max[0] = rmax_x; v[i].rect_max[1] = rmax_y;
        v[i].corner_radii[0] = cr_tl; v[i].corner_radii[1] = cr_tr;
        v[i].corner_radii[2] = cr_br; v[i].corner_radii[3] = cr_bl;
        v[i].border_thickness = border_thickness;
        v[i].softness = softness;
        v[i].mode = mode;
    }

    U32 *idx = &batch->indices[batch->index_count];
    idx[0] = base; idx[1] = base + 1; idx[2] = base + 2;
    idx[3] = base; idx[4] = base + 2; idx[5] = base + 3;

    batch->vertex_count += 4;
    batch->index_count += 6;
}

static void emit_quad_rotated(DrawBatch *batch,
    F32 x0, F32 y0, F32 x1, F32 y1,
    F32 u0, F32 v0, F32 u1, F32 v1,
    F32 cr, F32 cg, F32 cb, F32 ca,
    F32 angle_rad)
{
    if (batch->vertex_count + 4 > MAX_VERTICES || batch->index_count + 6 > MAX_INDICES)
        return;

    U32 base = batch->vertex_count;
    UIVertex *v = &batch->vertices[base];

    F32 cx = (x0 + x1) * 0.5f;
    F32 cy = (y0 + y1) * 0.5f;
    F32 cosA = cosf(angle_rad);
    F32 sinA = sinf(angle_rad);

    F32 dx0 = x0 - cx, dy0 = y0 - cy;
    F32 dx1 = x1 - cx, dy1 = y1 - cy;

    v[0].pos[0] = cx + dx0 * cosA - dy0 * sinA;
    v[0].pos[1] = cy + dx0 * sinA + dy0 * cosA;
    v[0].uv[0] = u0; v[0].uv[1] = v0;

    v[1].pos[0] = cx + dx1 * cosA - dy0 * sinA;
    v[1].pos[1] = cy + dx1 * sinA + dy0 * cosA;
    v[1].uv[0] = u1; v[1].uv[1] = v0;

    v[2].pos[0] = cx + dx1 * cosA - dy1 * sinA;
    v[2].pos[1] = cy + dx1 * sinA + dy1 * cosA;
    v[2].uv[0] = u1; v[2].uv[1] = v1;

    v[3].pos[0] = cx + dx0 * cosA - dy1 * sinA;
    v[3].pos[1] = cy + dx0 * sinA + dy1 * cosA;
    v[3].uv[0] = u0; v[3].uv[1] = v1;

    for (S32 i = 0; i < 4; i++) {
        v[i].col[0] = cr; v[i].col[1] = cg; v[i].col[2] = cb; v[i].col[3] = ca;
        v[i].rect_min[0] = 0; v[i].rect_min[1] = 0;
        v[i].rect_max[0] = 0; v[i].rect_max[1] = 0;
        v[i].corner_radii[0] = 0; v[i].corner_radii[1] = 0;
        v[i].corner_radii[2] = 0; v[i].corner_radii[3] = 0;
        v[i].border_thickness = 0;
        v[i].softness = 0;
        v[i].mode = 2.0f;
    }

    U32 *idx = &batch->indices[batch->index_count];
    idx[0] = base; idx[1] = base + 1; idx[2] = base + 2;
    idx[3] = base; idx[4] = base + 2; idx[5] = base + 3;

    batch->vertex_count += 4;
    batch->index_count += 6;
}

static void emit_rect(DrawBatch *batch,
    F32 x0, F32 y0, F32 x1, F32 y1,
    F32 cr, F32 cg, F32 cb, F32 ca,
    F32 cr_tl, F32 cr_tr, F32 cr_br, F32 cr_bl,
    F32 border_thickness, F32 softness)
{
    emit_quad(batch, x0, y0, x1, y1,
        0, 0, 0, 0,
        cr, cg, cb, ca,
        x0, y0, x1, y1,
        cr_tl, cr_tr, cr_br, cr_bl,
        border_thickness, softness, 0.0f);
}

static void emit_rect_vgradient(DrawBatch *batch,
    F32 x0, F32 y0, F32 x1, F32 y1,
    F32 tr, F32 tg, F32 tb, F32 ta,
    F32 br, F32 bg, F32 bb_, F32 ba,
    F32 cr_tl, F32 cr_tr, F32 cr_br, F32 cr_bl,
    F32 softness)
{
    if (batch->vertex_count + 4 > MAX_VERTICES || batch->index_count + 6 > MAX_INDICES)
        return;

    U32 base = batch->vertex_count;
    UIVertex *v = &batch->vertices[base];

    F32 pad = softness + 1.0f;
    F32 px0 = x0 - pad, py0 = y0 - pad, px1 = x1 + pad, py1 = y1 + pad;

    v[0].pos[0] = px0; v[0].pos[1] = py0; v[0].uv[0] = 0; v[0].uv[1] = 0;
    v[1].pos[0] = px1; v[1].pos[1] = py0; v[1].uv[0] = 0; v[1].uv[1] = 0;
    v[2].pos[0] = px1; v[2].pos[1] = py1; v[2].uv[0] = 0; v[2].uv[1] = 0;
    v[3].pos[0] = px0; v[3].pos[1] = py1; v[3].uv[0] = 0; v[3].uv[1] = 0;

    v[0].col[0] = tr; v[0].col[1] = tg; v[0].col[2] = tb; v[0].col[3] = ta;
    v[1].col[0] = tr; v[1].col[1] = tg; v[1].col[2] = tb; v[1].col[3] = ta;
    v[2].col[0] = br; v[2].col[1] = bg; v[2].col[2] = bb_; v[2].col[3] = ba;
    v[3].col[0] = br; v[3].col[1] = bg; v[3].col[2] = bb_; v[3].col[3] = ba;

    for (S32 i = 0; i < 4; i++) {
        v[i].rect_min[0] = x0; v[i].rect_min[1] = y0;
        v[i].rect_max[0] = x1; v[i].rect_max[1] = y1;
        v[i].corner_radii[0] = cr_tl; v[i].corner_radii[1] = cr_tr;
        v[i].corner_radii[2] = cr_br; v[i].corner_radii[3] = cr_bl;
        v[i].border_thickness = 0;
        v[i].softness = softness;
        v[i].mode = 0;
    }

    U32 *idx = &batch->indices[batch->index_count];
    idx[0] = base; idx[1] = base + 1; idx[2] = base + 2;
    idx[3] = base; idx[4] = base + 2; idx[5] = base + 3;

    batch->vertex_count += 4;
    batch->index_count += 6;
}

static void emit_text_glyphs(DrawBatch *batch, Renderer *r,
    Clay_BoundingBox bbox, Clay_Color color, const char *text, S32 text_len,
    U16 font_size)
{
    if (text_len == 0 || color.a < 0.1f) return;

    F32 cr = color.r / 255.f;
    F32 cg = color.g / 255.f;
    F32 cb = color.b / 255.f;
    F32 ca = color.a / 255.f;

    F32 scale = (F32)font_size / r->font_atlas_size;
    F32 text_h = r->font_line_height * scale;

    F32 x = floorf(bbox.x + 0.5f);
    F32 y = floorf(bbox.y + (bbox.height - text_h) * 0.5f + 0.5f);

    for (S32 i = 0; i < text_len; i++) {
        char ch = text[i];
        if (ch < GLYPH_FIRST || ch > GLYPH_LAST) {
            if (ch == ' ') {
                S32 gi = ' ' - GLYPH_FIRST;
                if (gi >= 0 && gi < GLYPH_COUNT)
                    x += r->glyphs[gi].x_advance * scale;
                continue;
            }
            ch = '?';
        }
        S32 gi = ch - GLYPH_FIRST;
        if (gi < 0 || gi >= GLYPH_COUNT) continue;

        GlyphInfo *g = &r->glyphs[gi];
        F32 gw = g->w * scale;
        F32 gh = g->h * scale;

        emit_quad(batch,
            x, y, x + gw, y + gh,
            g->u0, g->v0, g->u1, g->v1,
            cr, cg, cb, ca,
            0, 0, 0, 0,
            0, 0, 0, 0,
            0, 0, 1.0f);

        x += g->x_advance * scale;
    }
}

////////////////////////////////
// Flush helper

static void flush_batch(Renderer *r, DrawBatch *batch, U32 buf_idx,
    U32 *flush_index_start, VkDescriptorSet tex_set)
{
    U32 draw_index_count = batch->index_count - *flush_index_start;
    if (draw_index_count == 0) return;

    VkCommandBuffer cb = r->frames[r->frame_index % NUM_BACK_BUFFERS].command_buffer;

    vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, r->pipeline);

    F32 constants[4] = { (F32)r->width, (F32)r->height, 0, 0 };
    vkCmdPushConstants(cb, r->pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(constants), constants);

    vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, r->pipeline_layout,
        0, 1, &tex_set, 0, NULL);

    VkDeviceSize offset = 0;
    vkCmdBindVertexBuffers(cb, 0, 1, &r->vertex_buffers[buf_idx], &offset);
    vkCmdBindIndexBuffer(cb, r->index_buffers[buf_idx], 0, VK_INDEX_TYPE_UINT32);

    vkCmdDrawIndexed(cb, draw_index_count, 1, *flush_index_start, 0, 0);

    *flush_index_start = batch->index_count;
}

////////////////////////////////
// Public API

Renderer *renderer_create(RendererDesc *desc) {
    Renderer *r = (Renderer *)calloc(1, sizeof(Renderer));

    r->hwnd        = (HWND)desc->window_handle;
    r->width       = desc->width;
    r->height      = desc->height;
    r->frame_count = desc->frame_count > 0 ? desc->frame_count : NUM_BACK_BUFFERS;
    r->clear_r     = 0.12f;
    r->clear_g     = 0.12f;
    r->clear_b     = 0.13f;
    if (r->frame_count > NUM_BACK_BUFFERS) r->frame_count = NUM_BACK_BUFFERS;

    if (!create_instance(r))             goto fail;
    if (!create_surface(r))              goto fail;
    if (!pick_physical_device(r))        goto fail;
    if (!find_queue_family(r))           goto fail;
    if (!create_device(r))               goto fail;
    if (!create_command_resources(r))     goto fail;
    if (!create_sampler(r))              goto fail;
    if (!create_descriptor_resources(r)) goto fail;
    if (!create_swap_chain(r))           goto fail;
    if (!create_render_pass(r))          goto fail;
    if (!create_framebuffers(r))         goto fail;
    if (!create_pipeline(r))             goto fail;
    if (!create_ui_buffers(r))           goto fail;

    init_freetype(r);
    if (!create_font_atlas(r, 22.0f))    goto fail;

    return r;

fail:
    renderer_destroy(r);
    return NULL;
}

Renderer *renderer_create_shared(Renderer *parent, RendererDesc *desc) {
    if (!parent) return NULL;

    Renderer *r = (Renderer *)calloc(1, sizeof(Renderer));

    r->parent      = parent;
    r->hwnd        = (HWND)desc->window_handle;
    r->width       = desc->width;
    r->height      = desc->height;
    r->frame_count = desc->frame_count > 0 ? desc->frame_count : NUM_BACK_BUFFERS;
    if (r->frame_count > NUM_BACK_BUFFERS) r->frame_count = NUM_BACK_BUFFERS;

    // Share from parent
    r->instance            = parent->instance;
    r->physical_device     = parent->physical_device;
    r->device              = parent->device;
    r->graphics_queue      = parent->graphics_queue;
    r->queue_family        = parent->queue_family;
    r->render_pass         = parent->render_pass;
    r->pipeline_layout     = parent->pipeline_layout;
    r->pipeline            = parent->pipeline;
    r->descriptor_set_layout = parent->descriptor_set_layout;
    r->sampler             = parent->sampler;
    r->font_image          = parent->font_image;
    r->font_memory         = parent->font_memory;
    r->font_view           = parent->font_view;
    r->font_descriptor_set = parent->font_descriptor_set;
    r->icon_image          = parent->icon_image;
    r->icon_memory         = parent->icon_memory;
    r->icon_view           = parent->icon_view;
    r->icon_descriptor_set = parent->icon_descriptor_set;
    r->descriptor_pool     = parent->descriptor_pool;
    r->ft_lib              = parent->ft_lib;
    r->ft_face             = parent->ft_face;
    memcpy(r->glyphs, parent->glyphs, sizeof(r->glyphs));
    r->font_atlas_size     = parent->font_atlas_size;
    r->font_line_height    = parent->font_line_height;

    // Own surface, swap chain, command resources, buffers
    if (!create_surface(r))          goto fail;
    if (!create_command_resources(r)) goto fail;
    if (!create_swap_chain(r))       goto fail;
    // Shared renderers reuse parent render_pass but format must match
    if (!create_framebuffers(r))     goto fail;
    if (!create_ui_buffers(r))       goto fail;

    r->clear_r = parent->clear_r;
    r->clear_g = parent->clear_g;
    r->clear_b = parent->clear_b;

    return r;

fail:
    renderer_destroy(r);
    return NULL;
}

void renderer_destroy(Renderer *r) {
    if (!r) return;

    if (r->device) vkDeviceWaitIdle(r->device);

    // Per-window resources
    for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        if (r->vertex_buffers[i]) {
            vkDestroyBuffer(r->device, r->vertex_buffers[i], NULL);
            vkFreeMemory(r->device, r->vertex_memory[i], NULL);
        }
        if (r->index_buffers[i]) {
            vkDestroyBuffer(r->device, r->index_buffers[i], NULL);
            vkFreeMemory(r->device, r->index_memory[i], NULL);
        }
    }

    for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        if (r->framebuffers[i]) vkDestroyFramebuffer(r->device, r->framebuffers[i], NULL);
        if (r->swap_chain_views[i]) vkDestroyImageView(r->device, r->swap_chain_views[i], NULL);
    }

    if (r->swap_chain) vkDestroySwapchainKHR(r->device, r->swap_chain, NULL);

    for (S32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        if (r->frames[i].fence) vkDestroyFence(r->device, r->frames[i].fence, NULL);
        if (r->image_available_sema[i]) vkDestroySemaphore(r->device, r->image_available_sema[i], NULL);
        if (r->render_finished_sema[i]) vkDestroySemaphore(r->device, r->render_finished_sema[i], NULL);
    }
    if (r->command_pool) vkDestroyCommandPool(r->device, r->command_pool, NULL);

    if (r->surface) vkDestroySurfaceKHR(r->instance, r->surface, NULL);

    // Shared resources only freed by root renderer
    if (!r->parent) {
        if (r->font_view) vkDestroyImageView(r->device, r->font_view, NULL);
        if (r->font_image) { vkDestroyImage(r->device, r->font_image, NULL); vkFreeMemory(r->device, r->font_memory, NULL); }
        if (r->icon_view) vkDestroyImageView(r->device, r->icon_view, NULL);
        if (r->icon_image) { vkDestroyImage(r->device, r->icon_image, NULL); vkFreeMemory(r->device, r->icon_memory, NULL); }

        if (r->pipeline) vkDestroyPipeline(r->device, r->pipeline, NULL);
        if (r->pipeline_layout) vkDestroyPipelineLayout(r->device, r->pipeline_layout, NULL);
        if (r->descriptor_pool) vkDestroyDescriptorPool(r->device, r->descriptor_pool, NULL);
        if (r->descriptor_set_layout) vkDestroyDescriptorSetLayout(r->device, r->descriptor_set_layout, NULL);
        if (r->sampler) vkDestroySampler(r->device, r->sampler, NULL);
        if (r->render_pass) vkDestroyRenderPass(r->device, r->render_pass, NULL);

        if (r->ft_face) FT_Done_Face(r->ft_face);
        if (r->ft_lib)  FT_Done_FreeType(r->ft_lib);

        if (r->device) vkDestroyDevice(r->device, NULL);

#ifdef _DEBUG
        if (r->debug_messenger) {
            PFN_vkDestroyDebugUtilsMessengerEXT func =
                (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(r->instance, "vkDestroyDebugUtilsMessengerEXT");
            if (func) func(r->instance, r->debug_messenger, NULL);
        }
#endif
        if (r->instance) vkDestroyInstance(r->instance, NULL);
    }

    free(r);
}

B32 renderer_begin_frame(Renderer *r) {
    // Sync shared resources from parent
    if (r->parent) {
        r->font_image          = r->parent->font_image;
        r->font_view           = r->parent->font_view;
        r->font_descriptor_set = r->parent->font_descriptor_set;
        r->icon_image          = r->parent->icon_image;
        r->icon_view           = r->parent->icon_view;
        r->icon_descriptor_set = r->parent->icon_descriptor_set;
        memcpy(r->glyphs, r->parent->glyphs, sizeof(r->glyphs));
        r->font_atlas_size     = r->parent->font_atlas_size;
        r->font_line_height    = r->parent->font_line_height;
    }

    if (IsIconic(r->hwnd)) {
        Sleep(10);
        return 0;
    }

    return 1;
}

void renderer_end_frame(Renderer *r, Clay_RenderCommandArray *render_commands) {
    U32 frame_idx = r->frame_index % NUM_BACK_BUFFERS;
    FrameContext *fc = &r->frames[frame_idx];

    // Wait for this frame's fence
    vkWaitForFences(r->device, 1, &fc->fence, VK_TRUE, UINT64_MAX);
    vkResetFences(r->device, 1, &fc->fence);

    // Acquire next image
    U32 image_index;
    VkResult acquire_result = vkAcquireNextImageKHR(r->device, r->swap_chain, UINT64_MAX,
        r->image_available_sema[frame_idx], VK_NULL_HANDLE, &image_index);

    if (acquire_result == VK_ERROR_OUT_OF_DATE_KHR) {
        renderer_resize(r, r->width, r->height);
        return;
    }

    VkCommandBuffer cb = fc->command_buffer;
    vkResetCommandBuffer(cb, 0);

    VkCommandBufferBeginInfo begin_info = {0};
    begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
    vkBeginCommandBuffer(cb, &begin_info);

    VkClearValue clear_value = {0};
    clear_value.color.float32[0] = r->clear_r;
    clear_value.color.float32[1] = r->clear_g;
    clear_value.color.float32[2] = r->clear_b;
    clear_value.color.float32[3] = 1.0f;

    VkRenderPassBeginInfo rp_begin = {0};
    rp_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
    rp_begin.renderPass = r->render_pass;
    rp_begin.framebuffer = r->framebuffers[image_index];
    rp_begin.renderArea.extent.width = (U32)r->width;
    rp_begin.renderArea.extent.height = (U32)r->height;
    rp_begin.clearValueCount = 1;
    rp_begin.pClearValues = &clear_value;

    vkCmdBeginRenderPass(cb, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);

    VkViewport viewport = {0};
    viewport.width = (float)r->width;
    viewport.height = (float)r->height;
    viewport.maxDepth = 1.0f;
    vkCmdSetViewport(cb, 0, 1, &viewport);

    VkRect2D scissor = {0};
    scissor.extent.width = (U32)r->width;
    scissor.extent.height = (U32)r->height;
    vkCmdSetScissor(cb, 0, 1, &scissor);

    // Process Clay render commands
    if (render_commands->length > 0) {
        DrawBatch batch = {0};
        batch.vertices = (UIVertex *)r->vb_mapped[frame_idx];
        batch.indices = (U32 *)r->ib_mapped[frame_idx];

        S32 bound_texture = 0; // 0 = font, 1 = icon
        U32 flush_index_start = 0;

        for (S32 i = 0; i < render_commands->length; i++) {
            Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(render_commands, i);
            Clay_BoundingBox bb = cmd->boundingBox;

            switch (cmd->commandType) {
            case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
                Clay_RectangleRenderData *rect = &cmd->renderData.rectangle;
                Clay_Color c = rect->backgroundColor;
                emit_rect(&batch,
                    bb.x, bb.y, bb.x + bb.width, bb.y + bb.height,
                    c.r / 255.f, c.g / 255.f, c.b / 255.f, c.a / 255.f,
                    rect->cornerRadius.topLeft, rect->cornerRadius.topRight,
                    rect->cornerRadius.bottomRight, rect->cornerRadius.bottomLeft,
                    0, 1.0f);
            } break;

            case CLAY_RENDER_COMMAND_TYPE_BORDER: {
                Clay_BorderRenderData *border = &cmd->renderData.border;
                Clay_Color c = border->color;
                F32 cr_norm = c.r / 255.f;
                F32 cg_norm = c.g / 255.f;
                F32 cb_norm = c.b / 255.f;
                F32 ca_norm = c.a / 255.f;

                Clay_CornerRadius cr = border->cornerRadius;
                B32 has_radius = cr.topLeft > 0 || cr.topRight > 0 || cr.bottomLeft > 0 || cr.bottomRight > 0;
                B32 uniform = border->width.top == border->width.bottom &&
                               border->width.top == border->width.left &&
                               border->width.top == border->width.right &&
                               border->width.top > 0;

                if (has_radius && uniform) {
                    emit_rect(&batch,
                        bb.x, bb.y, bb.x + bb.width, bb.y + bb.height,
                        cr_norm, cg_norm, cb_norm, ca_norm,
                        cr.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft,
                        (F32)border->width.top, 1.0f);
                } else {
                    if (border->width.top > 0) {
                        emit_rect(&batch, bb.x, bb.y, bb.x + bb.width, bb.y + border->width.top,
                            cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f);
                    }
                    if (border->width.bottom > 0) {
                        emit_rect(&batch, bb.x, bb.y + bb.height - border->width.bottom, bb.x + bb.width, bb.y + bb.height,
                            cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f);
                    }
                    if (border->width.left > 0) {
                        emit_rect(&batch, bb.x, bb.y, bb.x + border->width.left, bb.y + bb.height,
                            cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f);
                    }
                    if (border->width.right > 0) {
                        emit_rect(&batch, bb.x + bb.width - border->width.right, bb.y, bb.x + bb.width, bb.y + bb.height,
                            cr_norm, cg_norm, cb_norm, ca_norm, 0, 0, 0, 0, 0, 1.0f);
                    }
                }
            } break;

            case CLAY_RENDER_COMMAND_TYPE_TEXT: {
                // Bind font texture
                if (bound_texture != 0) {
                    VkDescriptorSet heap = bound_texture == 1 ? r->icon_descriptor_set : r->font_descriptor_set;
                    flush_batch(r, &batch, frame_idx, &flush_index_start, heap);
                    bound_texture = 0;
                }
                Clay_TextRenderData *text = &cmd->renderData.text;
                emit_text_glyphs(&batch, r, bb, text->textColor,
                    text->stringContents.chars, text->stringContents.length,
                    text->fontSize);
            } break;

            case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
                VkDescriptorSet heap = bound_texture == 1 ? r->icon_descriptor_set : r->font_descriptor_set;
                flush_batch(r, &batch, frame_idx, &flush_index_start, heap);
                VkRect2D clip = {0};
                clip.offset.x = (S32)Max(bb.x, 0.f);
                clip.offset.y = (S32)Max(bb.y, 0.f);
                clip.extent.width = (U32)Min(bb.width, (F32)r->width - clip.offset.x);
                clip.extent.height = (U32)Min(bb.height, (F32)r->height - clip.offset.y);
                if (clip.extent.width == 0) clip.extent.width = 1;
                if (clip.extent.height == 0) clip.extent.height = 1;
                vkCmdSetScissor(cb, 0, 1, &clip);
            } break;

            case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
                VkDescriptorSet heap = bound_texture == 1 ? r->icon_descriptor_set : r->font_descriptor_set;
                flush_batch(r, &batch, frame_idx, &flush_index_start, heap);
                VkRect2D full_scissor = {0};
                full_scissor.extent.width = (U32)r->width;
                full_scissor.extent.height = (U32)r->height;
                vkCmdSetScissor(cb, 0, 1, &full_scissor);
            } break;

            case CLAY_RENDER_COMMAND_TYPE_CUSTOM: {
                Clay_CustomRenderData *custom = &cmd->renderData.custom;
                if (custom->customData) {
                    CustomRenderType type = *(CustomRenderType *)custom->customData;
                    if (type == CUSTOM_RENDER_VGRADIENT) {
                        if (bound_texture != 0) {
                            VkDescriptorSet heap = bound_texture == 1 ? r->icon_descriptor_set : r->font_descriptor_set;
                            flush_batch(r, &batch, frame_idx, &flush_index_start, heap);
                            bound_texture = 0;
                        }
                        CustomGradientData *grad = (CustomGradientData *)custom->customData;
                        Clay_Color tc = grad->top_color;
                        Clay_Color bc = grad->bottom_color;
                        emit_rect_vgradient(&batch,
                            bb.x, bb.y, bb.x + bb.width, bb.y + bb.height,
                            tc.r / 255.f, tc.g / 255.f, tc.b / 255.f, tc.a / 255.f,
                            bc.r / 255.f, bc.g / 255.f, bc.b / 255.f, bc.a / 255.f,
                            custom->cornerRadius.topLeft, custom->cornerRadius.topRight,
                            custom->cornerRadius.bottomRight, custom->cornerRadius.bottomLeft,
                            1.0f);
                    } else if (type == CUSTOM_RENDER_ICON) {
                        if (bound_texture != 1 && r->icon_descriptor_set) {
                            flush_batch(r, &batch, frame_idx, &flush_index_start, r->font_descriptor_set);
                            bound_texture = 1;
                        }
                        CustomIconData *icon = (CustomIconData *)custom->customData;
                        Clay_Color c = icon->color;
                        F32 icon_cr = c.r / 255.f, icon_cg = c.g / 255.f;
                        F32 icon_cb = c.b / 255.f, icon_ca = c.a / 255.f;
                        UI_IconInfo *info = &g_icons[icon->icon_id];
                        emit_quad(&batch,
                            bb.x, bb.y, bb.x + bb.width, bb.y + bb.height,
                            info->u0, info->v0, info->u1, info->v1,
                            icon_cr, icon_cg, icon_cb, icon_ca,
                            0, 0, 0, 0,
                            0, 0, 0, 0,
                            0, 0, 2.0f);
                    } else if (type == CUSTOM_RENDER_ROTATED_ICON) {
                        if (bound_texture != 1 && r->icon_descriptor_set) {
                            flush_batch(r, &batch, frame_idx, &flush_index_start, r->font_descriptor_set);
                            bound_texture = 1;
                        }
                        CustomRotatedIconData *ri = (CustomRotatedIconData *)custom->customData;
                        Clay_Color c = ri->color;
                        F32 ri_cr = c.r / 255.f, ri_cg = c.g / 255.f;
                        F32 ri_cb = c.b / 255.f, ri_ca = c.a / 255.f;
                        UI_IconInfo *info = &g_icons[ri->icon_id];
                        emit_quad_rotated(&batch,
                            bb.x, bb.y, bb.x + bb.width, bb.y + bb.height,
                            info->u0, info->v0, info->u1, info->v1,
                            ri_cr, ri_cg, ri_cb, ri_ca,
                            ri->angle_rad);
                    }
                }
            } break;

            case CLAY_RENDER_COMMAND_TYPE_IMAGE:
            default:
                break;
            }
        }

        // Flush remaining
        VkDescriptorSet heap = bound_texture == 1 ? r->icon_descriptor_set : r->font_descriptor_set;
        flush_batch(r, &batch, frame_idx, &flush_index_start, heap);
    }

    vkCmdEndRenderPass(cb);
    vkEndCommandBuffer(cb);

    // Submit
    VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    VkSubmitInfo submit = {0};
    submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submit.waitSemaphoreCount = 1;
    submit.pWaitSemaphores = &r->image_available_sema[frame_idx];
    submit.pWaitDstStageMask = &wait_stage;
    submit.commandBufferCount = 1;
    submit.pCommandBuffers = &cb;
    submit.signalSemaphoreCount = 1;
    submit.pSignalSemaphores = &r->render_finished_sema[frame_idx];
    vkQueueSubmit(r->graphics_queue, 1, &submit, fc->fence);

    // Present
    VkPresentInfoKHR present = {0};
    present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
    present.waitSemaphoreCount = 1;
    present.pWaitSemaphores = &r->render_finished_sema[frame_idx];
    present.swapchainCount = 1;
    present.pSwapchains = &r->swap_chain;
    present.pImageIndices = &image_index;
    VkResult present_result = vkQueuePresentKHR(r->graphics_queue, &present);

    if (present_result == VK_ERROR_OUT_OF_DATE_KHR || present_result == VK_SUBOPTIMAL_KHR) {
        renderer_resize(r, r->width, r->height);
    }

    r->frame_index++;
}

void renderer_create_icon_atlas(Renderer *r, const U8 *data, S32 w, S32 h) {
    // Create image
    VkImageCreateInfo img_ci = {0};
    img_ci.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    img_ci.imageType = VK_IMAGE_TYPE_2D;
    img_ci.format = VK_FORMAT_R8G8B8A8_UNORM;
    img_ci.extent.width = (U32)w;
    img_ci.extent.height = (U32)h;
    img_ci.extent.depth = 1;
    img_ci.mipLevels = 1;
    img_ci.arrayLayers = 1;
    img_ci.samples = VK_SAMPLE_COUNT_1_BIT;
    img_ci.tiling = VK_IMAGE_TILING_OPTIMAL;
    img_ci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;

    vkCreateImage(r->device, &img_ci, NULL, &r->icon_image);
    {
        VkMemoryRequirements mem_req;
        vkGetImageMemoryRequirements(r->device, r->icon_image, &mem_req);
        VkMemoryAllocateInfo alloc_info = {0};
        alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
        alloc_info.allocationSize = mem_req.size;
        alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
            VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
        vkAllocateMemory(r->device, &alloc_info, NULL, &r->icon_memory);
        vkBindImageMemory(r->device, r->icon_image, r->icon_memory, 0);
    }

    // Staging buffer
    VkBufferCreateInfo staging_ci = {0};
    staging_ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    staging_ci.size = (VkDeviceSize)w * h * 4;
    staging_ci.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;

    VkBuffer staging_buf;
    VkDeviceMemory staging_memory;
    void *staging_mapped;
    vkCreateBuffer(r->device, &staging_ci, NULL, &staging_buf);
    {
        VkMemoryRequirements mem_req;
        vkGetBufferMemoryRequirements(r->device, staging_buf, &mem_req);
        VkMemoryAllocateInfo alloc_info = {0};
        alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
        alloc_info.allocationSize = mem_req.size;
        alloc_info.memoryTypeIndex = find_memory_type(r->physical_device, mem_req.memoryTypeBits,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
        vkAllocateMemory(r->device, &alloc_info, NULL, &staging_memory);
        vkBindBufferMemory(r->device, staging_buf, staging_memory, 0);
        vkMapMemory(r->device, staging_memory, 0, staging_ci.size, 0, &staging_mapped);
    }
    memcpy(staging_mapped, data, (size_t)w * h * 4);

    VkCommandBuffer cb = begin_one_shot(r);

    transition_image(cb, r->icon_image,
        VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        0, VK_ACCESS_TRANSFER_WRITE_BIT,
        VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);

    VkBufferImageCopy region = {0};
    region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    region.imageSubresource.layerCount = 1;
    region.imageExtent.width = (U32)w;
    region.imageExtent.height = (U32)h;
    region.imageExtent.depth = 1;
    vkCmdCopyBufferToImage(cb, staging_buf, r->icon_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);

    transition_image(cb, r->icon_image,
        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
        VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
        VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);

    end_one_shot(r, cb);
    vkDestroyBuffer(r->device, staging_buf, NULL);
    vkFreeMemory(r->device, staging_memory, NULL);

    // Image view
    VkImageViewCreateInfo view_ci = {0};
    view_ci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    view_ci.image = r->icon_image;
    view_ci.viewType = VK_IMAGE_VIEW_TYPE_2D;
    view_ci.format = VK_FORMAT_R8G8B8A8_UNORM;
    view_ci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    view_ci.subresourceRange.levelCount = 1;
    view_ci.subresourceRange.layerCount = 1;
    vkCreateImageView(r->device, &view_ci, NULL, &r->icon_view);

    // Allocate icon descriptor set
    VkDescriptorSetAllocateInfo ds_alloc = {0};
    ds_alloc.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
    ds_alloc.descriptorPool = r->descriptor_pool;
    ds_alloc.descriptorSetCount = 1;
    ds_alloc.pSetLayouts = &r->descriptor_set_layout;
    vkAllocateDescriptorSets(r->device, &ds_alloc, &r->icon_descriptor_set);

    update_descriptor_set(r, r->icon_descriptor_set, r->icon_view);
}

void renderer_resize(Renderer *r, S32 width, S32 height) {
    if (width <= 0 || height <= 0) return;

    vkDeviceWaitIdle(r->device);

    // Clean up old swap chain resources
    for (U32 i = 0; i < NUM_BACK_BUFFERS; i++) {
        if (r->framebuffers[i]) { vkDestroyFramebuffer(r->device, r->framebuffers[i], NULL); r->framebuffers[i] = VK_NULL_HANDLE; }
        if (r->swap_chain_views[i]) { vkDestroyImageView(r->device, r->swap_chain_views[i], NULL); r->swap_chain_views[i] = VK_NULL_HANDLE; }
    }

    VkSwapchainKHR old_swap = r->swap_chain;
    r->width = width;
    r->height = height;

    create_swap_chain(r);

    if (old_swap && old_swap != r->swap_chain) {
        vkDestroySwapchainKHR(r->device, old_swap, NULL);
    }

    create_framebuffers(r);
}

void renderer_set_clear_color(Renderer *r, F32 cr, F32 cg, F32 cb) {
    r->clear_r = cr;
    r->clear_g = cg;
    r->clear_b = cb;
}

void renderer_set_font_scale(Renderer *r, F32 scale) {
    F32 target_size = 22.0f * scale;
    if (fabsf(target_size - r->font_atlas_size) < 0.1f) return;
    vkDeviceWaitIdle(r->device);
    if (r->font_view) { vkDestroyImageView(r->device, r->font_view, NULL); r->font_view = VK_NULL_HANDLE; }
    if (r->font_image) { vkDestroyImage(r->device, r->font_image, NULL); vkFreeMemory(r->device, r->font_memory, NULL); r->font_image = VK_NULL_HANDLE; }
    create_font_atlas(r, target_size);
}

void renderer_sync_from_parent(Renderer *r) {
    if (!r || !r->parent) return;
    Renderer *p = r->parent;
    r->font_image          = p->font_image;
    r->font_view           = p->font_view;
    r->font_descriptor_set = p->font_descriptor_set;
    r->font_atlas_size     = p->font_atlas_size;
    r->font_line_height    = p->font_line_height;
    memcpy(r->glyphs, p->glyphs, sizeof(r->glyphs));
}
