vulkan: initial loader and null driver

Change-Id: Id5ebb5f01e61e9b114990f49c64c88fbbb7b730e
(cherry picked from commit 4df205cdfc61e66de774ba50be9ef59a08cf88bb)
diff --git a/vulkan/libvulkan/Android.mk b/vulkan/libvulkan/Android.mk
new file mode 100644
index 0000000..c26b01f
--- /dev/null
+++ b/vulkan/libvulkan/Android.mk
@@ -0,0 +1,25 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_CLANG := true
+LOCAL_CFLAGS := -std=c99 -fvisibility=hidden -fstrict-aliasing
+LOCAL_CFLAGS += -DLOG_TAG=\"vulkan\"
+LOCAL_CFLAGS += -Weverything -Werror -Wno-padded -Wno-undef
+LOCAL_CPPFLAGS := -std=c++1y \
+	-Wno-c++98-compat-pedantic \
+	-Wno-exit-time-destructors \
+	-Wno-c99-extensions
+
+LOCAL_C_INCLUDES := \
+	frameworks/native/vulkan/include
+
+LOCAL_SRC_FILES := \
+	entry.cpp \
+	get_proc_addr.cpp \
+	loader.cpp
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+LOCAL_SHARED_LIBRARIES := libhardware liblog
+
+LOCAL_MODULE := libvulkan
+include $(BUILD_SHARED_LIBRARY)
diff --git a/vulkan/libvulkan/entry.cpp b/vulkan/libvulkan/entry.cpp
new file mode 100644
index 0000000..7bfaaf2
--- /dev/null
+++ b/vulkan/libvulkan/entry.cpp
@@ -0,0 +1,802 @@
+/*
+* Copyright 2015 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+// This file is generated. Do not edit manually!
+// To regenerate: $ apic template ../api/vulkan.api entry.cpp.tmpl
+// Requires apic from https://android.googlesource.com/platform/tools/gpu/.
+
+#include "loader.h"
+using namespace vulkan;
+
+// clang-format off
+
+namespace {
+    inline const InstanceVtbl& GetVtbl(VkInstance instance) {
+        return **reinterpret_cast<InstanceVtbl**>(instance);
+    }
+    inline const InstanceVtbl& GetVtbl(VkPhysicalDevice physicalDevice) {
+        return **reinterpret_cast<InstanceVtbl**>(physicalDevice);
+    }
+    inline const DeviceVtbl& GetVtbl(VkDevice device) {
+        return **reinterpret_cast<DeviceVtbl**>(device);
+    }
+    inline const DeviceVtbl& GetVtbl(VkQueue queue) {
+        return **reinterpret_cast<DeviceVtbl**>(queue);
+    }
+    inline const DeviceVtbl& GetVtbl(VkCmdBuffer cmdBuffer) {
+        return **reinterpret_cast<DeviceVtbl**>(cmdBuffer);
+    }
+} // namespace
+
+__attribute__((visibility("default")))
+VkResult vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo, VkInstance* pInstance) {
+    return vulkan::CreateInstance(pCreateInfo, pInstance);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyInstance(VkInstance instance) {
+    return GetVtbl(instance).DestroyInstance(instance);
+}
+
+__attribute__((visibility("default")))
+VkResult vkEnumeratePhysicalDevices(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices) {
+    return GetVtbl(instance).EnumeratePhysicalDevices(instance, pPhysicalDeviceCount, pPhysicalDevices);
+}
+
+__attribute__((visibility("default")))
+PFN_vkVoidFunction vkGetDeviceProcAddr(VkDevice device, const char* pName) {
+    return vulkan::GetDeviceProcAddr(device, pName);
+}
+
+__attribute__((visibility("default")))
+PFN_vkVoidFunction vkGetInstanceProcAddr(VkInstance instance, const char* pName) {
+    return vulkan::GetInstanceProcAddr(instance, pName);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties) {
+    return GetVtbl(physicalDevice).GetPhysicalDeviceProperties(physicalDevice, pProperties);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetPhysicalDeviceQueueCount(VkPhysicalDevice physicalDevice, uint32_t* pCount) {
+    return GetVtbl(physicalDevice).GetPhysicalDeviceQueueCount(physicalDevice, pCount);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetPhysicalDeviceQueueProperties(VkPhysicalDevice physicalDevice, uint32_t count, VkPhysicalDeviceQueueProperties* pQueueProperties) {
+    return GetVtbl(physicalDevice).GetPhysicalDeviceQueueProperties(physicalDevice, count, pQueueProperties);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties) {
+    return GetVtbl(physicalDevice).GetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetPhysicalDeviceFeatures(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures* pFeatures) {
+    return GetVtbl(physicalDevice).GetPhysicalDeviceFeatures(physicalDevice, pFeatures);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetPhysicalDeviceFormatProperties(VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties* pFormatProperties) {
+    return GetVtbl(physicalDevice).GetPhysicalDeviceFormatProperties(physicalDevice, format, pFormatProperties);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetPhysicalDeviceImageFormatProperties(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkImageTiling tiling, VkImageUsageFlags usage, VkImageFormatProperties* pImageFormatProperties) {
+    return GetVtbl(physicalDevice).GetPhysicalDeviceImageFormatProperties(physicalDevice, format, type, tiling, usage, pImageFormatProperties);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetPhysicalDeviceLimits(VkPhysicalDevice physicalDevice, VkPhysicalDeviceLimits* pLimits) {
+    return GetVtbl(physicalDevice).GetPhysicalDeviceLimits(physicalDevice, pLimits);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, VkDevice* pDevice) {
+    return GetVtbl(physicalDevice).CreateDevice(physicalDevice, pCreateInfo, pDevice);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyDevice(VkDevice device) {
+    return vulkan::DestroyDevice(device);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetGlobalLayerProperties(uint32_t* pCount, VkLayerProperties* pProperties) {
+    return vulkan::GetGlobalLayerProperties(pCount, pProperties);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetGlobalExtensionProperties(const char* pLayerName, uint32_t* pCount, VkExtensionProperties* pProperties) {
+    return vulkan::GetGlobalExtensionProperties(pLayerName, pCount, pProperties);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetPhysicalDeviceLayerProperties(VkPhysicalDevice physicalDevice, uint32_t* pCount, VkLayerProperties* pProperties) {
+    return GetVtbl(physicalDevice).GetPhysicalDeviceLayerProperties(physicalDevice, pCount, pProperties);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetPhysicalDeviceExtensionProperties(VkPhysicalDevice physicalDevice, const char* pLayerName, uint32_t* pCount, VkExtensionProperties* pProperties) {
+    return GetVtbl(physicalDevice).GetPhysicalDeviceExtensionProperties(physicalDevice, pLayerName, pCount, pProperties);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetDeviceQueue(VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue* pQueue) {
+    return vulkan::GetDeviceQueue(device, queueFamilyIndex, queueIndex, pQueue);
+}
+
+__attribute__((visibility("default")))
+VkResult vkQueueSubmit(VkQueue queue, uint32_t cmdBufferCount, const VkCmdBuffer* pCmdBuffers, VkFence fence) {
+    return GetVtbl(queue).QueueSubmit(queue, cmdBufferCount, pCmdBuffers, fence);
+}
+
+__attribute__((visibility("default")))
+VkResult vkQueueWaitIdle(VkQueue queue) {
+    return GetVtbl(queue).QueueWaitIdle(queue);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDeviceWaitIdle(VkDevice device) {
+    return GetVtbl(device).DeviceWaitIdle(device);
+}
+
+__attribute__((visibility("default")))
+VkResult vkAllocMemory(VkDevice device, const VkMemoryAllocInfo* pAllocInfo, VkDeviceMemory* pMem) {
+    return GetVtbl(device).AllocMemory(device, pAllocInfo, pMem);
+}
+
+__attribute__((visibility("default")))
+VkResult vkFreeMemory(VkDevice device, VkDeviceMemory mem) {
+    return GetVtbl(device).FreeMemory(device, mem);
+}
+
+__attribute__((visibility("default")))
+VkResult vkMapMemory(VkDevice device, VkDeviceMemory mem, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData) {
+    return GetVtbl(device).MapMemory(device, mem, offset, size, flags, ppData);
+}
+
+__attribute__((visibility("default")))
+VkResult vkUnmapMemory(VkDevice device, VkDeviceMemory mem) {
+    return GetVtbl(device).UnmapMemory(device, mem);
+}
+
+__attribute__((visibility("default")))
+VkResult vkFlushMappedMemoryRanges(VkDevice device, uint32_t memRangeCount, const VkMappedMemoryRange* pMemRanges) {
+    return GetVtbl(device).FlushMappedMemoryRanges(device, memRangeCount, pMemRanges);
+}
+
+__attribute__((visibility("default")))
+VkResult vkInvalidateMappedMemoryRanges(VkDevice device, uint32_t memRangeCount, const VkMappedMemoryRange* pMemRanges) {
+    return GetVtbl(device).InvalidateMappedMemoryRanges(device, memRangeCount, pMemRanges);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetDeviceMemoryCommitment(VkDevice device, VkDeviceMemory memory, VkDeviceSize* pCommittedMemoryInBytes) {
+    return GetVtbl(device).GetDeviceMemoryCommitment(device, memory, pCommittedMemoryInBytes);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements) {
+    return GetVtbl(device).GetBufferMemoryRequirements(device, buffer, pMemoryRequirements);
+}
+
+__attribute__((visibility("default")))
+VkResult vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory mem, VkDeviceSize memOffset) {
+    return GetVtbl(device).BindBufferMemory(device, buffer, mem, memOffset);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetImageMemoryRequirements(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements) {
+    return GetVtbl(device).GetImageMemoryRequirements(device, image, pMemoryRequirements);
+}
+
+__attribute__((visibility("default")))
+VkResult vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory mem, VkDeviceSize memOffset) {
+    return GetVtbl(device).BindImageMemory(device, image, mem, memOffset);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetImageSparseMemoryRequirements(VkDevice device, VkImage image, uint32_t* pNumRequirements, VkSparseImageMemoryRequirements* pSparseMemoryRequirements) {
+    return GetVtbl(device).GetImageSparseMemoryRequirements(device, image, pNumRequirements, pSparseMemoryRequirements);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetPhysicalDeviceSparseImageFormatProperties(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, uint32_t samples, VkImageUsageFlags usage, VkImageTiling tiling, uint32_t* pNumProperties, VkSparseImageFormatProperties* pProperties) {
+    return GetVtbl(physicalDevice).GetPhysicalDeviceSparseImageFormatProperties(physicalDevice, format, type, samples, usage, tiling, pNumProperties, pProperties);
+}
+
+__attribute__((visibility("default")))
+VkResult vkQueueBindSparseBufferMemory(VkQueue queue, VkBuffer buffer, uint32_t numBindings, const VkSparseMemoryBindInfo* pBindInfo) {
+    return GetVtbl(queue).QueueBindSparseBufferMemory(queue, buffer, numBindings, pBindInfo);
+}
+
+__attribute__((visibility("default")))
+VkResult vkQueueBindSparseImageOpaqueMemory(VkQueue queue, VkImage image, uint32_t numBindings, const VkSparseMemoryBindInfo* pBindInfo) {
+    return GetVtbl(queue).QueueBindSparseImageOpaqueMemory(queue, image, numBindings, pBindInfo);
+}
+
+__attribute__((visibility("default")))
+VkResult vkQueueBindSparseImageMemory(VkQueue queue, VkImage image, uint32_t numBindings, const VkSparseImageMemoryBindInfo* pBindInfo) {
+    return GetVtbl(queue).QueueBindSparseImageMemory(queue, image, numBindings, pBindInfo);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateFence(VkDevice device, const VkFenceCreateInfo* pCreateInfo, VkFence* pFence) {
+    return GetVtbl(device).CreateFence(device, pCreateInfo, pFence);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyFence(VkDevice device, VkFence fence) {
+    return GetVtbl(device).DestroyFence(device, fence);
+}
+
+__attribute__((visibility("default")))
+VkResult vkResetFences(VkDevice device, uint32_t fenceCount, const VkFence* pFences) {
+    return GetVtbl(device).ResetFences(device, fenceCount, pFences);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetFenceStatus(VkDevice device, VkFence fence) {
+    return GetVtbl(device).GetFenceStatus(device, fence);
+}
+
+__attribute__((visibility("default")))
+VkResult vkWaitForFences(VkDevice device, uint32_t fenceCount, const VkFence* pFences, VkBool32 waitAll, uint64_t timeout) {
+    return GetVtbl(device).WaitForFences(device, fenceCount, pFences, waitAll, timeout);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateSemaphore(VkDevice device, const VkSemaphoreCreateInfo* pCreateInfo, VkSemaphore* pSemaphore) {
+    return GetVtbl(device).CreateSemaphore(device, pCreateInfo, pSemaphore);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroySemaphore(VkDevice device, VkSemaphore semaphore) {
+    return GetVtbl(device).DestroySemaphore(device, semaphore);
+}
+
+__attribute__((visibility("default")))
+VkResult vkQueueSignalSemaphore(VkQueue queue, VkSemaphore semaphore) {
+    return GetVtbl(queue).QueueSignalSemaphore(queue, semaphore);
+}
+
+__attribute__((visibility("default")))
+VkResult vkQueueWaitSemaphore(VkQueue queue, VkSemaphore semaphore) {
+    return GetVtbl(queue).QueueWaitSemaphore(queue, semaphore);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateEvent(VkDevice device, const VkEventCreateInfo* pCreateInfo, VkEvent* pEvent) {
+    return GetVtbl(device).CreateEvent(device, pCreateInfo, pEvent);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyEvent(VkDevice device, VkEvent event) {
+    return GetVtbl(device).DestroyEvent(device, event);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetEventStatus(VkDevice device, VkEvent event) {
+    return GetVtbl(device).GetEventStatus(device, event);
+}
+
+__attribute__((visibility("default")))
+VkResult vkSetEvent(VkDevice device, VkEvent event) {
+    return GetVtbl(device).SetEvent(device, event);
+}
+
+__attribute__((visibility("default")))
+VkResult vkResetEvent(VkDevice device, VkEvent event) {
+    return GetVtbl(device).ResetEvent(device, event);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateQueryPool(VkDevice device, const VkQueryPoolCreateInfo* pCreateInfo, VkQueryPool* pQueryPool) {
+    return GetVtbl(device).CreateQueryPool(device, pCreateInfo, pQueryPool);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyQueryPool(VkDevice device, VkQueryPool queryPool) {
+    return GetVtbl(device).DestroyQueryPool(device, queryPool);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetQueryPoolResults(VkDevice device, VkQueryPool queryPool, uint32_t startQuery, uint32_t queryCount, size_t* pDataSize, void* pData, VkQueryResultFlags flags) {
+    return GetVtbl(device).GetQueryPoolResults(device, queryPool, startQuery, queryCount, pDataSize, pData, flags);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo, VkBuffer* pBuffer) {
+    return GetVtbl(device).CreateBuffer(device, pCreateInfo, pBuffer);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyBuffer(VkDevice device, VkBuffer buffer) {
+    return GetVtbl(device).DestroyBuffer(device, buffer);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateBufferView(VkDevice device, const VkBufferViewCreateInfo* pCreateInfo, VkBufferView* pView) {
+    return GetVtbl(device).CreateBufferView(device, pCreateInfo, pView);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyBufferView(VkDevice device, VkBufferView bufferView) {
+    return GetVtbl(device).DestroyBufferView(device, bufferView);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo, VkImage* pImage) {
+    return GetVtbl(device).CreateImage(device, pCreateInfo, pImage);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyImage(VkDevice device, VkImage image) {
+    return GetVtbl(device).DestroyImage(device, image);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetImageSubresourceLayout(VkDevice device, VkImage image, const VkImageSubresource* pSubresource, VkSubresourceLayout* pLayout) {
+    return GetVtbl(device).GetImageSubresourceLayout(device, image, pSubresource, pLayout);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateImageView(VkDevice device, const VkImageViewCreateInfo* pCreateInfo, VkImageView* pView) {
+    return GetVtbl(device).CreateImageView(device, pCreateInfo, pView);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyImageView(VkDevice device, VkImageView imageView) {
+    return GetVtbl(device).DestroyImageView(device, imageView);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateAttachmentView(VkDevice device, const VkAttachmentViewCreateInfo* pCreateInfo, VkAttachmentView* pView) {
+    return GetVtbl(device).CreateAttachmentView(device, pCreateInfo, pView);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyAttachmentView(VkDevice device, VkAttachmentView attachmentView) {
+    return GetVtbl(device).DestroyAttachmentView(device, attachmentView);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateShaderModule(VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo, VkShaderModule* pShaderModule) {
+    return GetVtbl(device).CreateShaderModule(device, pCreateInfo, pShaderModule);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyShaderModule(VkDevice device, VkShaderModule shaderModule) {
+    return GetVtbl(device).DestroyShaderModule(device, shaderModule);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateShader(VkDevice device, const VkShaderCreateInfo* pCreateInfo, VkShader* pShader) {
+    return GetVtbl(device).CreateShader(device, pCreateInfo, pShader);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyShader(VkDevice device, VkShader shader) {
+    return GetVtbl(device).DestroyShader(device, shader);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreatePipelineCache(VkDevice device, const VkPipelineCacheCreateInfo* pCreateInfo, VkPipelineCache* pPipelineCache) {
+    return GetVtbl(device).CreatePipelineCache(device, pCreateInfo, pPipelineCache);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyPipelineCache(VkDevice device, VkPipelineCache pipelineCache) {
+    return GetVtbl(device).DestroyPipelineCache(device, pipelineCache);
+}
+
+__attribute__((visibility("default")))
+size_t vkGetPipelineCacheSize(VkDevice device, VkPipelineCache pipelineCache) {
+    return GetVtbl(device).GetPipelineCacheSize(device, pipelineCache);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetPipelineCacheData(VkDevice device, VkPipelineCache pipelineCache, void* pData) {
+    return GetVtbl(device).GetPipelineCacheData(device, pipelineCache, pData);
+}
+
+__attribute__((visibility("default")))
+VkResult vkMergePipelineCaches(VkDevice device, VkPipelineCache destCache, uint32_t srcCacheCount, const VkPipelineCache* pSrcCaches) {
+    return GetVtbl(device).MergePipelineCaches(device, destCache, srcCacheCount, pSrcCaches);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count, const VkGraphicsPipelineCreateInfo* pCreateInfos, VkPipeline* pPipelines) {
+    return GetVtbl(device).CreateGraphicsPipelines(device, pipelineCache, count, pCreateInfos, pPipelines);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateComputePipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count, const VkComputePipelineCreateInfo* pCreateInfos, VkPipeline* pPipelines) {
+    return GetVtbl(device).CreateComputePipelines(device, pipelineCache, count, pCreateInfos, pPipelines);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyPipeline(VkDevice device, VkPipeline pipeline) {
+    return GetVtbl(device).DestroyPipeline(device, pipeline);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreatePipelineLayout(VkDevice device, const VkPipelineLayoutCreateInfo* pCreateInfo, VkPipelineLayout* pPipelineLayout) {
+    return GetVtbl(device).CreatePipelineLayout(device, pCreateInfo, pPipelineLayout);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyPipelineLayout(VkDevice device, VkPipelineLayout pipelineLayout) {
+    return GetVtbl(device).DestroyPipelineLayout(device, pipelineLayout);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateSampler(VkDevice device, const VkSamplerCreateInfo* pCreateInfo, VkSampler* pSampler) {
+    return GetVtbl(device).CreateSampler(device, pCreateInfo, pSampler);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroySampler(VkDevice device, VkSampler sampler) {
+    return GetVtbl(device).DestroySampler(device, sampler);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateDescriptorSetLayout(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, VkDescriptorSetLayout* pSetLayout) {
+    return GetVtbl(device).CreateDescriptorSetLayout(device, pCreateInfo, pSetLayout);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyDescriptorSetLayout(VkDevice device, VkDescriptorSetLayout descriptorSetLayout) {
+    return GetVtbl(device).DestroyDescriptorSetLayout(device, descriptorSetLayout);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateDescriptorPool(VkDevice device, VkDescriptorPoolUsage poolUsage, uint32_t maxSets, const VkDescriptorPoolCreateInfo* pCreateInfo, VkDescriptorPool* pDescriptorPool) {
+    return GetVtbl(device).CreateDescriptorPool(device, poolUsage, maxSets, pCreateInfo, pDescriptorPool);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool) {
+    return GetVtbl(device).DestroyDescriptorPool(device, descriptorPool);
+}
+
+__attribute__((visibility("default")))
+VkResult vkResetDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool) {
+    return GetVtbl(device).ResetDescriptorPool(device, descriptorPool);
+}
+
+__attribute__((visibility("default")))
+VkResult vkAllocDescriptorSets(VkDevice device, VkDescriptorPool descriptorPool, VkDescriptorSetUsage setUsage, uint32_t count, const VkDescriptorSetLayout* pSetLayouts, VkDescriptorSet* pDescriptorSets, uint32_t* pCount) {
+    return GetVtbl(device).AllocDescriptorSets(device, descriptorPool, setUsage, count, pSetLayouts, pDescriptorSets, pCount);
+}
+
+__attribute__((visibility("default")))
+VkResult vkFreeDescriptorSets(VkDevice device, VkDescriptorPool descriptorPool, uint32_t count, const VkDescriptorSet* pDescriptorSets) {
+    return GetVtbl(device).FreeDescriptorSets(device, descriptorPool, count, pDescriptorSets);
+}
+
+__attribute__((visibility("default")))
+VkResult vkUpdateDescriptorSets(VkDevice device, uint32_t writeCount, const VkWriteDescriptorSet* pDescriptorWrites, uint32_t copyCount, const VkCopyDescriptorSet* pDescriptorCopies) {
+    return GetVtbl(device).UpdateDescriptorSets(device, writeCount, pDescriptorWrites, copyCount, pDescriptorCopies);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateDynamicViewportState(VkDevice device, const VkDynamicViewportStateCreateInfo* pCreateInfo, VkDynamicViewportState* pState) {
+    return GetVtbl(device).CreateDynamicViewportState(device, pCreateInfo, pState);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyDynamicViewportState(VkDevice device, VkDynamicViewportState dynamicViewportState) {
+    return GetVtbl(device).DestroyDynamicViewportState(device, dynamicViewportState);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateDynamicRasterState(VkDevice device, const VkDynamicRasterStateCreateInfo* pCreateInfo, VkDynamicRasterState* pState) {
+    return GetVtbl(device).CreateDynamicRasterState(device, pCreateInfo, pState);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyDynamicRasterState(VkDevice device, VkDynamicRasterState dynamicRasterState) {
+    return GetVtbl(device).DestroyDynamicRasterState(device, dynamicRasterState);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateDynamicColorBlendState(VkDevice device, const VkDynamicColorBlendStateCreateInfo* pCreateInfo, VkDynamicColorBlendState* pState) {
+    return GetVtbl(device).CreateDynamicColorBlendState(device, pCreateInfo, pState);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyDynamicColorBlendState(VkDevice device, VkDynamicColorBlendState dynamicColorBlendState) {
+    return GetVtbl(device).DestroyDynamicColorBlendState(device, dynamicColorBlendState);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateDynamicDepthStencilState(VkDevice device, const VkDynamicDepthStencilStateCreateInfo* pCreateInfo, VkDynamicDepthStencilState* pState) {
+    return GetVtbl(device).CreateDynamicDepthStencilState(device, pCreateInfo, pState);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyDynamicDepthStencilState(VkDevice device, VkDynamicDepthStencilState dynamicDepthStencilState) {
+    return GetVtbl(device).DestroyDynamicDepthStencilState(device, dynamicDepthStencilState);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateFramebuffer(VkDevice device, const VkFramebufferCreateInfo* pCreateInfo, VkFramebuffer* pFramebuffer) {
+    return GetVtbl(device).CreateFramebuffer(device, pCreateInfo, pFramebuffer);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyFramebuffer(VkDevice device, VkFramebuffer framebuffer) {
+    return GetVtbl(device).DestroyFramebuffer(device, framebuffer);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateRenderPass(VkDevice device, const VkRenderPassCreateInfo* pCreateInfo, VkRenderPass* pRenderPass) {
+    return GetVtbl(device).CreateRenderPass(device, pCreateInfo, pRenderPass);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyRenderPass(VkDevice device, VkRenderPass renderPass) {
+    return GetVtbl(device).DestroyRenderPass(device, renderPass);
+}
+
+__attribute__((visibility("default")))
+VkResult vkGetRenderAreaGranularity(VkDevice device, VkRenderPass renderPass, VkExtent2D* pGranularity) {
+    return GetVtbl(device).GetRenderAreaGranularity(device, renderPass, pGranularity);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateCommandPool(VkDevice device, const VkCmdPoolCreateInfo* pCreateInfo, VkCmdPool* pCmdPool) {
+    return GetVtbl(device).CreateCommandPool(device, pCreateInfo, pCmdPool);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyCommandPool(VkDevice device, VkCmdPool cmdPool) {
+    return GetVtbl(device).DestroyCommandPool(device, cmdPool);
+}
+
+__attribute__((visibility("default")))
+VkResult vkResetCommandPool(VkDevice device, VkCmdPool cmdPool, VkCmdPoolResetFlags flags) {
+    return GetVtbl(device).ResetCommandPool(device, cmdPool, flags);
+}
+
+__attribute__((visibility("default")))
+VkResult vkCreateCommandBuffer(VkDevice device, const VkCmdBufferCreateInfo* pCreateInfo, VkCmdBuffer* pCmdBuffer) {
+    return GetVtbl(device).CreateCommandBuffer(device, pCreateInfo, pCmdBuffer);
+}
+
+__attribute__((visibility("default")))
+VkResult vkDestroyCommandBuffer(VkDevice device, VkCmdBuffer commandBuffer) {
+    return GetVtbl(device).DestroyCommandBuffer(device, commandBuffer);
+}
+
+__attribute__((visibility("default")))
+VkResult vkBeginCommandBuffer(VkCmdBuffer cmdBuffer, const VkCmdBufferBeginInfo* pBeginInfo) {
+    return GetVtbl(cmdBuffer).BeginCommandBuffer(cmdBuffer, pBeginInfo);
+}
+
+__attribute__((visibility("default")))
+VkResult vkEndCommandBuffer(VkCmdBuffer cmdBuffer) {
+    return GetVtbl(cmdBuffer).EndCommandBuffer(cmdBuffer);
+}
+
+__attribute__((visibility("default")))
+VkResult vkResetCommandBuffer(VkCmdBuffer cmdBuffer, VkCmdBufferResetFlags flags) {
+    return GetVtbl(cmdBuffer).ResetCommandBuffer(cmdBuffer, flags);
+}
+
+__attribute__((visibility("default")))
+void vkCmdBindPipeline(VkCmdBuffer cmdBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipeline pipeline) {
+    GetVtbl(cmdBuffer).CmdBindPipeline(cmdBuffer, pipelineBindPoint, pipeline);
+}
+
+__attribute__((visibility("default")))
+void vkCmdBindDynamicViewportState(VkCmdBuffer cmdBuffer, VkDynamicViewportState dynamicViewportState) {
+    GetVtbl(cmdBuffer).CmdBindDynamicViewportState(cmdBuffer, dynamicViewportState);
+}
+
+__attribute__((visibility("default")))
+void vkCmdBindDynamicRasterState(VkCmdBuffer cmdBuffer, VkDynamicRasterState dynamicRasterState) {
+    GetVtbl(cmdBuffer).CmdBindDynamicRasterState(cmdBuffer, dynamicRasterState);
+}
+
+__attribute__((visibility("default")))
+void vkCmdBindDynamicColorBlendState(VkCmdBuffer cmdBuffer, VkDynamicColorBlendState dynamicColorBlendState) {
+    GetVtbl(cmdBuffer).CmdBindDynamicColorBlendState(cmdBuffer, dynamicColorBlendState);
+}
+
+__attribute__((visibility("default")))
+void vkCmdBindDynamicDepthStencilState(VkCmdBuffer cmdBuffer, VkDynamicDepthStencilState dynamicDepthStencilState) {
+    GetVtbl(cmdBuffer).CmdBindDynamicDepthStencilState(cmdBuffer, dynamicDepthStencilState);
+}
+
+__attribute__((visibility("default")))
+void vkCmdBindDescriptorSets(VkCmdBuffer cmdBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t firstSet, uint32_t setCount, const VkDescriptorSet* pDescriptorSets, uint32_t dynamicOffsetCount, const uint32_t* pDynamicOffsets) {
+    GetVtbl(cmdBuffer).CmdBindDescriptorSets(cmdBuffer, pipelineBindPoint, layout, firstSet, setCount, pDescriptorSets, dynamicOffsetCount, pDynamicOffsets);
+}
+
+__attribute__((visibility("default")))
+void vkCmdBindIndexBuffer(VkCmdBuffer cmdBuffer, VkBuffer buffer, VkDeviceSize offset, VkIndexType indexType) {
+    GetVtbl(cmdBuffer).CmdBindIndexBuffer(cmdBuffer, buffer, offset, indexType);
+}
+
+__attribute__((visibility("default")))
+void vkCmdBindVertexBuffers(VkCmdBuffer cmdBuffer, uint32_t startBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets) {
+    GetVtbl(cmdBuffer).CmdBindVertexBuffers(cmdBuffer, startBinding, bindingCount, pBuffers, pOffsets);
+}
+
+__attribute__((visibility("default")))
+void vkCmdDraw(VkCmdBuffer cmdBuffer, uint32_t firstVertex, uint32_t vertexCount, uint32_t firstInstance, uint32_t instanceCount) {
+    GetVtbl(cmdBuffer).CmdDraw(cmdBuffer, firstVertex, vertexCount, firstInstance, instanceCount);
+}
+
+__attribute__((visibility("default")))
+void vkCmdDrawIndexed(VkCmdBuffer cmdBuffer, uint32_t firstIndex, uint32_t indexCount, int32_t vertexOffset, uint32_t firstInstance, uint32_t instanceCount) {
+    GetVtbl(cmdBuffer).CmdDrawIndexed(cmdBuffer, firstIndex, indexCount, vertexOffset, firstInstance, instanceCount);
+}
+
+__attribute__((visibility("default")))
+void vkCmdDrawIndirect(VkCmdBuffer cmdBuffer, VkBuffer buffer, VkDeviceSize offset, uint32_t count, uint32_t stride) {
+    GetVtbl(cmdBuffer).CmdDrawIndirect(cmdBuffer, buffer, offset, count, stride);
+}
+
+__attribute__((visibility("default")))
+void vkCmdDrawIndexedIndirect(VkCmdBuffer cmdBuffer, VkBuffer buffer, VkDeviceSize offset, uint32_t count, uint32_t stride) {
+    GetVtbl(cmdBuffer).CmdDrawIndexedIndirect(cmdBuffer, buffer, offset, count, stride);
+}
+
+__attribute__((visibility("default")))
+void vkCmdDispatch(VkCmdBuffer cmdBuffer, uint32_t x, uint32_t y, uint32_t z) {
+    GetVtbl(cmdBuffer).CmdDispatch(cmdBuffer, x, y, z);
+}
+
+__attribute__((visibility("default")))
+void vkCmdDispatchIndirect(VkCmdBuffer cmdBuffer, VkBuffer buffer, VkDeviceSize offset) {
+    GetVtbl(cmdBuffer).CmdDispatchIndirect(cmdBuffer, buffer, offset);
+}
+
+__attribute__((visibility("default")))
+void vkCmdCopyBuffer(VkCmdBuffer cmdBuffer, VkBuffer srcBuffer, VkBuffer destBuffer, uint32_t regionCount, const VkBufferCopy* pRegions) {
+    GetVtbl(cmdBuffer).CmdCopyBuffer(cmdBuffer, srcBuffer, destBuffer, regionCount, pRegions);
+}
+
+__attribute__((visibility("default")))
+void vkCmdCopyImage(VkCmdBuffer cmdBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage destImage, VkImageLayout destImageLayout, uint32_t regionCount, const VkImageCopy* pRegions) {
+    GetVtbl(cmdBuffer).CmdCopyImage(cmdBuffer, srcImage, srcImageLayout, destImage, destImageLayout, regionCount, pRegions);
+}
+
+__attribute__((visibility("default")))
+void vkCmdBlitImage(VkCmdBuffer cmdBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage destImage, VkImageLayout destImageLayout, uint32_t regionCount, const VkImageBlit* pRegions, VkTexFilter filter) {
+    GetVtbl(cmdBuffer).CmdBlitImage(cmdBuffer, srcImage, srcImageLayout, destImage, destImageLayout, regionCount, pRegions, filter);
+}
+
+__attribute__((visibility("default")))
+void vkCmdCopyBufferToImage(VkCmdBuffer cmdBuffer, VkBuffer srcBuffer, VkImage destImage, VkImageLayout destImageLayout, uint32_t regionCount, const VkBufferImageCopy* pRegions) {
+    GetVtbl(cmdBuffer).CmdCopyBufferToImage(cmdBuffer, srcBuffer, destImage, destImageLayout, regionCount, pRegions);
+}
+
+__attribute__((visibility("default")))
+void vkCmdCopyImageToBuffer(VkCmdBuffer cmdBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkBuffer destBuffer, uint32_t regionCount, const VkBufferImageCopy* pRegions) {
+    GetVtbl(cmdBuffer).CmdCopyImageToBuffer(cmdBuffer, srcImage, srcImageLayout, destBuffer, regionCount, pRegions);
+}
+
+__attribute__((visibility("default")))
+void vkCmdUpdateBuffer(VkCmdBuffer cmdBuffer, VkBuffer destBuffer, VkDeviceSize destOffset, VkDeviceSize dataSize, const uint32_t* pData) {
+    GetVtbl(cmdBuffer).CmdUpdateBuffer(cmdBuffer, destBuffer, destOffset, dataSize, pData);
+}
+
+__attribute__((visibility("default")))
+void vkCmdFillBuffer(VkCmdBuffer cmdBuffer, VkBuffer destBuffer, VkDeviceSize destOffset, VkDeviceSize fillSize, uint32_t data) {
+    GetVtbl(cmdBuffer).CmdFillBuffer(cmdBuffer, destBuffer, destOffset, fillSize, data);
+}
+
+__attribute__((visibility("default")))
+void vkCmdClearColorImage(VkCmdBuffer cmdBuffer, VkImage image, VkImageLayout imageLayout, const VkClearColorValue* pColor, uint32_t rangeCount, const VkImageSubresourceRange* pRanges) {
+    GetVtbl(cmdBuffer).CmdClearColorImage(cmdBuffer, image, imageLayout, pColor, rangeCount, pRanges);
+}
+
+__attribute__((visibility("default")))
+void vkCmdClearDepthStencilImage(VkCmdBuffer cmdBuffer, VkImage image, VkImageLayout imageLayout, float depth, uint32_t stencil, uint32_t rangeCount, const VkImageSubresourceRange* pRanges) {
+    GetVtbl(cmdBuffer).CmdClearDepthStencilImage(cmdBuffer, image, imageLayout, depth, stencil, rangeCount, pRanges);
+}
+
+__attribute__((visibility("default")))
+void vkCmdClearColorAttachment(VkCmdBuffer cmdBuffer, uint32_t colorAttachment, VkImageLayout imageLayout, const VkClearColorValue* pColor, uint32_t rectCount, const VkRect3D* pRects) {
+    GetVtbl(cmdBuffer).CmdClearColorAttachment(cmdBuffer, colorAttachment, imageLayout, pColor, rectCount, pRects);
+}
+
+__attribute__((visibility("default")))
+void vkCmdClearDepthStencilAttachment(VkCmdBuffer cmdBuffer, VkImageAspectFlags imageAspectMask, VkImageLayout imageLayout, float depth, uint32_t stencil, uint32_t rectCount, const VkRect3D* pRects) {
+    GetVtbl(cmdBuffer).CmdClearDepthStencilAttachment(cmdBuffer, imageAspectMask, imageLayout, depth, stencil, rectCount, pRects);
+}
+
+__attribute__((visibility("default")))
+void vkCmdResolveImage(VkCmdBuffer cmdBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage destImage, VkImageLayout destImageLayout, uint32_t regionCount, const VkImageResolve* pRegions) {
+    GetVtbl(cmdBuffer).CmdResolveImage(cmdBuffer, srcImage, srcImageLayout, destImage, destImageLayout, regionCount, pRegions);
+}
+
+__attribute__((visibility("default")))
+void vkCmdSetEvent(VkCmdBuffer cmdBuffer, VkEvent event, VkPipelineStageFlags stageMask) {
+    GetVtbl(cmdBuffer).CmdSetEvent(cmdBuffer, event, stageMask);
+}
+
+__attribute__((visibility("default")))
+void vkCmdResetEvent(VkCmdBuffer cmdBuffer, VkEvent event, VkPipelineStageFlags stageMask) {
+    GetVtbl(cmdBuffer).CmdResetEvent(cmdBuffer, event, stageMask);
+}
+
+__attribute__((visibility("default")))
+void vkCmdWaitEvents(VkCmdBuffer cmdBuffer, uint32_t eventCount, const VkEvent* pEvents, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags destStageMask, uint32_t memBarrierCount, const void* const* ppMemBarriers) {
+    GetVtbl(cmdBuffer).CmdWaitEvents(cmdBuffer, eventCount, pEvents, srcStageMask, destStageMask, memBarrierCount, ppMemBarriers);
+}
+
+__attribute__((visibility("default")))
+void vkCmdPipelineBarrier(VkCmdBuffer cmdBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags destStageMask, VkBool32 byRegion, uint32_t memBarrierCount, const void* const* ppMemBarriers) {
+    GetVtbl(cmdBuffer).CmdPipelineBarrier(cmdBuffer, srcStageMask, destStageMask, byRegion, memBarrierCount, ppMemBarriers);
+}
+
+__attribute__((visibility("default")))
+void vkCmdBeginQuery(VkCmdBuffer cmdBuffer, VkQueryPool queryPool, uint32_t slot, VkQueryControlFlags flags) {
+    GetVtbl(cmdBuffer).CmdBeginQuery(cmdBuffer, queryPool, slot, flags);
+}
+
+__attribute__((visibility("default")))
+void vkCmdEndQuery(VkCmdBuffer cmdBuffer, VkQueryPool queryPool, uint32_t slot) {
+    GetVtbl(cmdBuffer).CmdEndQuery(cmdBuffer, queryPool, slot);
+}
+
+__attribute__((visibility("default")))
+void vkCmdResetQueryPool(VkCmdBuffer cmdBuffer, VkQueryPool queryPool, uint32_t startQuery, uint32_t queryCount) {
+    GetVtbl(cmdBuffer).CmdResetQueryPool(cmdBuffer, queryPool, startQuery, queryCount);
+}
+
+__attribute__((visibility("default")))
+void vkCmdWriteTimestamp(VkCmdBuffer cmdBuffer, VkTimestampType timestampType, VkBuffer destBuffer, VkDeviceSize destOffset) {
+    GetVtbl(cmdBuffer).CmdWriteTimestamp(cmdBuffer, timestampType, destBuffer, destOffset);
+}
+
+__attribute__((visibility("default")))
+void vkCmdCopyQueryPoolResults(VkCmdBuffer cmdBuffer, VkQueryPool queryPool, uint32_t startQuery, uint32_t queryCount, VkBuffer destBuffer, VkDeviceSize destOffset, VkDeviceSize destStride, VkQueryResultFlags flags) {
+    GetVtbl(cmdBuffer).CmdCopyQueryPoolResults(cmdBuffer, queryPool, startQuery, queryCount, destBuffer, destOffset, destStride, flags);
+}
+
+__attribute__((visibility("default")))
+void vkCmdPushConstants(VkCmdBuffer cmdBuffer, VkPipelineLayout layout, VkShaderStageFlags stageFlags, uint32_t start, uint32_t length, const void* values) {
+    GetVtbl(cmdBuffer).CmdPushConstants(cmdBuffer, layout, stageFlags, start, length, values);
+}
+
+__attribute__((visibility("default")))
+void vkCmdBeginRenderPass(VkCmdBuffer cmdBuffer, const VkRenderPassBeginInfo* pRenderPassBegin, VkRenderPassContents contents) {
+    GetVtbl(cmdBuffer).CmdBeginRenderPass(cmdBuffer, pRenderPassBegin, contents);
+}
+
+__attribute__((visibility("default")))
+void vkCmdNextSubpass(VkCmdBuffer cmdBuffer, VkRenderPassContents contents) {
+    GetVtbl(cmdBuffer).CmdNextSubpass(cmdBuffer, contents);
+}
+
+__attribute__((visibility("default")))
+void vkCmdEndRenderPass(VkCmdBuffer cmdBuffer) {
+    GetVtbl(cmdBuffer).CmdEndRenderPass(cmdBuffer);
+}
+
+__attribute__((visibility("default")))
+void vkCmdExecuteCommands(VkCmdBuffer cmdBuffer, uint32_t cmdBuffersCount, const VkCmdBuffer* pCmdBuffers) {
+    GetVtbl(cmdBuffer).CmdExecuteCommands(cmdBuffer, cmdBuffersCount, pCmdBuffers);
+}
diff --git a/vulkan/libvulkan/entry.cpp.tmpl b/vulkan/libvulkan/entry.cpp.tmpl
new file mode 100644
index 0000000..249f8fd
--- /dev/null
+++ b/vulkan/libvulkan/entry.cpp.tmpl
@@ -0,0 +1,145 @@
+{{Include "../api/templates/vulkan_common.tmpl"}}
+{{Macro "DefineGlobals" $}}
+{{$ | Macro "entry.cpp" | Reflow 4 | Write "entry.cpp"}}
+
+
+{{/*
+-------------------------------------------------------------------------------
+  Entry point
+-------------------------------------------------------------------------------
+*/}}
+{{define "entry.cpp"}}
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */

+// This file is generated. Do not edit manually!
+// To regenerate: $ apic template ../api/vulkan.api entry.cpp.tmpl
+// Requires apic from https://android.googlesource.com/platform/tools/gpu/.

+#include "loader.h"
+using namespace vulkan;

+// clang-format off

+namespace {
+    inline const InstanceVtbl& GetVtbl(VkInstance instance) {
+        return **reinterpret_cast<InstanceVtbl**>(instance);
+    }
+    inline const InstanceVtbl& GetVtbl(VkPhysicalDevice physicalDevice) {
+        return **reinterpret_cast<InstanceVtbl**>(physicalDevice);
+    }
+    inline const DeviceVtbl& GetVtbl(VkDevice device) {
+        return **reinterpret_cast<DeviceVtbl**>(device);
+    }
+    inline const DeviceVtbl& GetVtbl(VkQueue queue) {
+        return **reinterpret_cast<DeviceVtbl**>(queue);
+    }
+    inline const DeviceVtbl& GetVtbl(VkCmdBuffer cmdBuffer) {
+        return **reinterpret_cast<DeviceVtbl**>(cmdBuffer);
+    }
+} // namespace

+  {{range $f := AllCommands $}}
+    {{if not (GetAnnotation $f "pfn")}}
+      __attribute__((visibility("default")))
+      {{if eq (Macro "IsSpecialEntry" $f.Name) "true"}}
+        {{Macro "EmitSpecialEntry" $f}}
+      {{else}}
+        {{Macro "EmitEntry" $f}}
+      {{end}}
+    {{end}}
+  {{end}}
+{{end}}
+
+
+{{/*
+-------------------------------------------------------------------------------
+  Decides whether an entrypoint needs special-case handling. Emits the string
+  "true" if so, nothing otherwise.
+-------------------------------------------------------------------------------
+*/}}
+{{define "IsSpecialEntry"}}
+  {{/* TODO: figure out how to do this in a cleaner or at least multi-line way */}}
+  {{if or (eq $ "vkGetInstanceProcAddr") (or (eq $ "vkGetDeviceProcAddr") (or (eq $ "vkGetDeviceQueue") (eq $ "vkDestroyDevice")))}}
+    true
+  {{end}}
+{{end}}
+
+{{/*
+-------------------------------------------------------------------------------
+  Emits the entrypoint definition for the specified command, which always
+  dispatches statically because the function requires special-case handling.
+-------------------------------------------------------------------------------
+*/}}
+{{define "EmitSpecialEntry"}}
+  {{AssertType $ "Function"}}
+
+  {{Node "Type" $.Return}} {{Macro "FunctionName" $}}({{Macro "Parameters" $}}) {
+    {{if not (IsVoid $.Return.Type)}}return §{{end}}
+    vulkan::{{TrimPrefix "vk" $.Name}}({{Macro "Arguments" $}});
+  }
+  ¶
+{{end}}
+
+
+{{/*
+-------------------------------------------------------------------------------
+  Emits the entrypoint definition for the specified command. If the first
+  parameter is a handle to dispatchable object, it dispatches the call through
+  the object's function table. Otherwise, it dispatches statically.
+-------------------------------------------------------------------------------
+*/}}
+{{define "EmitEntry"}}
+  {{AssertType $ "Function"}}
+
+  {{Node "Type" $.Return}} {{Macro "FunctionName" $}}({{Macro "Parameters" $}}) {
+    {{if not (IsVoid $.Return.Type)}}return §{{end}}
+    {{Macro "Dispatch" $}}{{TrimPrefix "vk" $.Name}}({{Macro "Arguments" $}});
+  }
+  ¶
+{{end}}
+
+
+{{/*
+-------------------------------------------------------------------------------
+  Emit the dispatch lookup for a function based on its first parameter.
+-------------------------------------------------------------------------------
+*/}}
+{{define "Dispatch#VkInstance"      }}GetVtbl({{$.Node.Name}}).{{end}}
+{{define "Dispatch#VkPhysicalDevice"}}GetVtbl({{$.Node.Name}}).{{end}}
+{{define "Dispatch#VkDevice"        }}GetVtbl({{$.Node.Name}}).{{end}}
+{{define "Dispatch#VkQueue"         }}GetVtbl({{$.Node.Name}}).{{end}}
+{{define "Dispatch#VkCmdBuffer"     }}GetVtbl({{$.Node.Name}}).{{end}}
+{{define "Dispatch_Default"         }}vulkan::{{end}}
+{{define "Dispatch"}}
+  {{AssertType $ "Function"}}
+
+  {{range $i, $p := $.CallParameters}}
+    {{if not $i}}{{Node "Dispatch" $p}}{{end}}
+  {{end}}
+{{end}}
+
+
+{{/*
+-------------------------------------------------------------------------------
+  Emits a comma-separated list of C parameter names for the given command.
+-------------------------------------------------------------------------------
+*/}}
+{{define "Arguments"}}
+  {{AssertType $ "Function"}}
+
+  {{ForEach $.CallParameters "ParameterName" | JoinWith ", "}}
+{{end}}
diff --git a/vulkan/libvulkan/get_proc_addr.cpp b/vulkan/libvulkan/get_proc_addr.cpp
new file mode 100644
index 0000000..5f05f0e
--- /dev/null
+++ b/vulkan/libvulkan/get_proc_addr.cpp
@@ -0,0 +1,1167 @@
+/*
+* Copyright 2015 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+// This file is generated. Do not edit manually!
+// To regenerate: $ apic template ../api/vulkan.api get_proc_addr.cpp.tmpl
+// Requires apic from https://android.googlesource.com/platform/tools/gpu/.
+
+#include <algorithm>
+#include <log/log.h>
+#include "loader.h"
+using namespace vulkan;
+
+#define UNLIKELY(expr) __builtin_expect((expr), 0)
+
+namespace {
+
+struct NameProcEntry {
+    const char* name;
+    PFN_vkVoidFunction proc;
+};
+
+struct NameOffsetEntry {
+    const char* name;
+    size_t offset;
+};
+
+template <typename TEntry, size_t N>
+const TEntry* FindProcEntry(const TEntry(&table)[N], const char* name) {
+    auto entry = std::lower_bound(
+        table, table + N, name,
+        [](const TEntry& e, const char* n) { return strcmp(e.name, n) < 0; });
+    if (entry != (table + N) && strcmp(entry->name, name) == 0)
+        return entry;
+    return nullptr;
+}
+
+const NameProcEntry kInstanceProcTbl[] = {
+    // clang-format off
+    {"vkCreateDevice", reinterpret_cast<PFN_vkVoidFunction>(vkCreateDevice)},
+    {"vkDestroyInstance", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyInstance)},
+    {"vkEnumeratePhysicalDevices", reinterpret_cast<PFN_vkVoidFunction>(vkEnumeratePhysicalDevices)},
+    {"vkGetInstanceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(vkGetInstanceProcAddr)},
+    {"vkGetPhysicalDeviceExtensionProperties", reinterpret_cast<PFN_vkVoidFunction>(vkGetPhysicalDeviceExtensionProperties)},
+    {"vkGetPhysicalDeviceFeatures", reinterpret_cast<PFN_vkVoidFunction>(vkGetPhysicalDeviceFeatures)},
+    {"vkGetPhysicalDeviceFormatProperties", reinterpret_cast<PFN_vkVoidFunction>(vkGetPhysicalDeviceFormatProperties)},
+    {"vkGetPhysicalDeviceImageFormatProperties", reinterpret_cast<PFN_vkVoidFunction>(vkGetPhysicalDeviceImageFormatProperties)},
+    {"vkGetPhysicalDeviceLayerProperties", reinterpret_cast<PFN_vkVoidFunction>(vkGetPhysicalDeviceLayerProperties)},
+    {"vkGetPhysicalDeviceLimits", reinterpret_cast<PFN_vkVoidFunction>(vkGetPhysicalDeviceLimits)},
+    {"vkGetPhysicalDeviceMemoryProperties", reinterpret_cast<PFN_vkVoidFunction>(vkGetPhysicalDeviceMemoryProperties)},
+    {"vkGetPhysicalDeviceProperties", reinterpret_cast<PFN_vkVoidFunction>(vkGetPhysicalDeviceProperties)},
+    {"vkGetPhysicalDeviceQueueCount", reinterpret_cast<PFN_vkVoidFunction>(vkGetPhysicalDeviceQueueCount)},
+    {"vkGetPhysicalDeviceQueueProperties", reinterpret_cast<PFN_vkVoidFunction>(vkGetPhysicalDeviceQueueProperties)},
+    {"vkGetPhysicalDeviceSparseImageFormatProperties", reinterpret_cast<PFN_vkVoidFunction>(vkGetPhysicalDeviceSparseImageFormatProperties)},
+    // clang-format on
+};
+
+const NameProcEntry kDeviceProcTbl[] = {
+    // clang-format off
+    {"vkAllocDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(vkAllocDescriptorSets)},
+    {"vkAllocMemory", reinterpret_cast<PFN_vkVoidFunction>(vkAllocMemory)},
+    {"vkBeginCommandBuffer", reinterpret_cast<PFN_vkVoidFunction>(vkBeginCommandBuffer)},
+    {"vkBindBufferMemory", reinterpret_cast<PFN_vkVoidFunction>(vkBindBufferMemory)},
+    {"vkBindImageMemory", reinterpret_cast<PFN_vkVoidFunction>(vkBindImageMemory)},
+    {"vkCmdBeginQuery", reinterpret_cast<PFN_vkVoidFunction>(vkCmdBeginQuery)},
+    {"vkCmdBeginRenderPass", reinterpret_cast<PFN_vkVoidFunction>(vkCmdBeginRenderPass)},
+    {"vkCmdBindDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(vkCmdBindDescriptorSets)},
+    {"vkCmdBindDynamicColorBlendState", reinterpret_cast<PFN_vkVoidFunction>(vkCmdBindDynamicColorBlendState)},
+    {"vkCmdBindDynamicDepthStencilState", reinterpret_cast<PFN_vkVoidFunction>(vkCmdBindDynamicDepthStencilState)},
+    {"vkCmdBindDynamicRasterState", reinterpret_cast<PFN_vkVoidFunction>(vkCmdBindDynamicRasterState)},
+    {"vkCmdBindDynamicViewportState", reinterpret_cast<PFN_vkVoidFunction>(vkCmdBindDynamicViewportState)},
+    {"vkCmdBindIndexBuffer", reinterpret_cast<PFN_vkVoidFunction>(vkCmdBindIndexBuffer)},
+    {"vkCmdBindPipeline", reinterpret_cast<PFN_vkVoidFunction>(vkCmdBindPipeline)},
+    {"vkCmdBindVertexBuffers", reinterpret_cast<PFN_vkVoidFunction>(vkCmdBindVertexBuffers)},
+    {"vkCmdBlitImage", reinterpret_cast<PFN_vkVoidFunction>(vkCmdBlitImage)},
+    {"vkCmdClearColorAttachment", reinterpret_cast<PFN_vkVoidFunction>(vkCmdClearColorAttachment)},
+    {"vkCmdClearColorImage", reinterpret_cast<PFN_vkVoidFunction>(vkCmdClearColorImage)},
+    {"vkCmdClearDepthStencilAttachment", reinterpret_cast<PFN_vkVoidFunction>(vkCmdClearDepthStencilAttachment)},
+    {"vkCmdClearDepthStencilImage", reinterpret_cast<PFN_vkVoidFunction>(vkCmdClearDepthStencilImage)},
+    {"vkCmdCopyBuffer", reinterpret_cast<PFN_vkVoidFunction>(vkCmdCopyBuffer)},
+    {"vkCmdCopyBufferToImage", reinterpret_cast<PFN_vkVoidFunction>(vkCmdCopyBufferToImage)},
+    {"vkCmdCopyImage", reinterpret_cast<PFN_vkVoidFunction>(vkCmdCopyImage)},
+    {"vkCmdCopyImageToBuffer", reinterpret_cast<PFN_vkVoidFunction>(vkCmdCopyImageToBuffer)},
+    {"vkCmdCopyQueryPoolResults", reinterpret_cast<PFN_vkVoidFunction>(vkCmdCopyQueryPoolResults)},
+    {"vkCmdDispatch", reinterpret_cast<PFN_vkVoidFunction>(vkCmdDispatch)},
+    {"vkCmdDispatchIndirect", reinterpret_cast<PFN_vkVoidFunction>(vkCmdDispatchIndirect)},
+    {"vkCmdDraw", reinterpret_cast<PFN_vkVoidFunction>(vkCmdDraw)},
+    {"vkCmdDrawIndexed", reinterpret_cast<PFN_vkVoidFunction>(vkCmdDrawIndexed)},
+    {"vkCmdDrawIndexedIndirect", reinterpret_cast<PFN_vkVoidFunction>(vkCmdDrawIndexedIndirect)},
+    {"vkCmdDrawIndirect", reinterpret_cast<PFN_vkVoidFunction>(vkCmdDrawIndirect)},
+    {"vkCmdEndQuery", reinterpret_cast<PFN_vkVoidFunction>(vkCmdEndQuery)},
+    {"vkCmdEndRenderPass", reinterpret_cast<PFN_vkVoidFunction>(vkCmdEndRenderPass)},
+    {"vkCmdExecuteCommands", reinterpret_cast<PFN_vkVoidFunction>(vkCmdExecuteCommands)},
+    {"vkCmdFillBuffer", reinterpret_cast<PFN_vkVoidFunction>(vkCmdFillBuffer)},
+    {"vkCmdNextSubpass", reinterpret_cast<PFN_vkVoidFunction>(vkCmdNextSubpass)},
+    {"vkCmdPipelineBarrier", reinterpret_cast<PFN_vkVoidFunction>(vkCmdPipelineBarrier)},
+    {"vkCmdPushConstants", reinterpret_cast<PFN_vkVoidFunction>(vkCmdPushConstants)},
+    {"vkCmdResetEvent", reinterpret_cast<PFN_vkVoidFunction>(vkCmdResetEvent)},
+    {"vkCmdResetQueryPool", reinterpret_cast<PFN_vkVoidFunction>(vkCmdResetQueryPool)},
+    {"vkCmdResolveImage", reinterpret_cast<PFN_vkVoidFunction>(vkCmdResolveImage)},
+    {"vkCmdSetEvent", reinterpret_cast<PFN_vkVoidFunction>(vkCmdSetEvent)},
+    {"vkCmdUpdateBuffer", reinterpret_cast<PFN_vkVoidFunction>(vkCmdUpdateBuffer)},
+    {"vkCmdWaitEvents", reinterpret_cast<PFN_vkVoidFunction>(vkCmdWaitEvents)},
+    {"vkCmdWriteTimestamp", reinterpret_cast<PFN_vkVoidFunction>(vkCmdWriteTimestamp)},
+    {"vkCreateAttachmentView", reinterpret_cast<PFN_vkVoidFunction>(vkCreateAttachmentView)},
+    {"vkCreateBuffer", reinterpret_cast<PFN_vkVoidFunction>(vkCreateBuffer)},
+    {"vkCreateBufferView", reinterpret_cast<PFN_vkVoidFunction>(vkCreateBufferView)},
+    {"vkCreateCommandBuffer", reinterpret_cast<PFN_vkVoidFunction>(vkCreateCommandBuffer)},
+    {"vkCreateCommandPool", reinterpret_cast<PFN_vkVoidFunction>(vkCreateCommandPool)},
+    {"vkCreateComputePipelines", reinterpret_cast<PFN_vkVoidFunction>(vkCreateComputePipelines)},
+    {"vkCreateDescriptorPool", reinterpret_cast<PFN_vkVoidFunction>(vkCreateDescriptorPool)},
+    {"vkCreateDescriptorSetLayout", reinterpret_cast<PFN_vkVoidFunction>(vkCreateDescriptorSetLayout)},
+    {"vkCreateDynamicColorBlendState", reinterpret_cast<PFN_vkVoidFunction>(vkCreateDynamicColorBlendState)},
+    {"vkCreateDynamicDepthStencilState", reinterpret_cast<PFN_vkVoidFunction>(vkCreateDynamicDepthStencilState)},
+    {"vkCreateDynamicRasterState", reinterpret_cast<PFN_vkVoidFunction>(vkCreateDynamicRasterState)},
+    {"vkCreateDynamicViewportState", reinterpret_cast<PFN_vkVoidFunction>(vkCreateDynamicViewportState)},
+    {"vkCreateEvent", reinterpret_cast<PFN_vkVoidFunction>(vkCreateEvent)},
+    {"vkCreateFence", reinterpret_cast<PFN_vkVoidFunction>(vkCreateFence)},
+    {"vkCreateFramebuffer", reinterpret_cast<PFN_vkVoidFunction>(vkCreateFramebuffer)},
+    {"vkCreateGraphicsPipelines", reinterpret_cast<PFN_vkVoidFunction>(vkCreateGraphicsPipelines)},
+    {"vkCreateImage", reinterpret_cast<PFN_vkVoidFunction>(vkCreateImage)},
+    {"vkCreateImageView", reinterpret_cast<PFN_vkVoidFunction>(vkCreateImageView)},
+    {"vkCreatePipelineCache", reinterpret_cast<PFN_vkVoidFunction>(vkCreatePipelineCache)},
+    {"vkCreatePipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(vkCreatePipelineLayout)},
+    {"vkCreateQueryPool", reinterpret_cast<PFN_vkVoidFunction>(vkCreateQueryPool)},
+    {"vkCreateRenderPass", reinterpret_cast<PFN_vkVoidFunction>(vkCreateRenderPass)},
+    {"vkCreateSampler", reinterpret_cast<PFN_vkVoidFunction>(vkCreateSampler)},
+    {"vkCreateSemaphore", reinterpret_cast<PFN_vkVoidFunction>(vkCreateSemaphore)},
+    {"vkCreateShader", reinterpret_cast<PFN_vkVoidFunction>(vkCreateShader)},
+    {"vkCreateShaderModule", reinterpret_cast<PFN_vkVoidFunction>(vkCreateShaderModule)},
+    {"vkDestroyAttachmentView", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyAttachmentView)},
+    {"vkDestroyBuffer", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyBuffer)},
+    {"vkDestroyBufferView", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyBufferView)},
+    {"vkDestroyCommandBuffer", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyCommandBuffer)},
+    {"vkDestroyCommandPool", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyCommandPool)},
+    {"vkDestroyDescriptorPool", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyDescriptorPool)},
+    {"vkDestroyDescriptorSetLayout", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyDescriptorSetLayout)},
+    {"vkDestroyDevice", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyDevice)},
+    {"vkDestroyDynamicColorBlendState", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyDynamicColorBlendState)},
+    {"vkDestroyDynamicDepthStencilState", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyDynamicDepthStencilState)},
+    {"vkDestroyDynamicRasterState", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyDynamicRasterState)},
+    {"vkDestroyDynamicViewportState", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyDynamicViewportState)},
+    {"vkDestroyEvent", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyEvent)},
+    {"vkDestroyFence", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyFence)},
+    {"vkDestroyFramebuffer", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyFramebuffer)},
+    {"vkDestroyImage", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyImage)},
+    {"vkDestroyImageView", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyImageView)},
+    {"vkDestroyPipeline", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyPipeline)},
+    {"vkDestroyPipelineCache", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyPipelineCache)},
+    {"vkDestroyPipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyPipelineLayout)},
+    {"vkDestroyQueryPool", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyQueryPool)},
+    {"vkDestroyRenderPass", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyRenderPass)},
+    {"vkDestroySampler", reinterpret_cast<PFN_vkVoidFunction>(vkDestroySampler)},
+    {"vkDestroySemaphore", reinterpret_cast<PFN_vkVoidFunction>(vkDestroySemaphore)},
+    {"vkDestroyShader", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyShader)},
+    {"vkDestroyShaderModule", reinterpret_cast<PFN_vkVoidFunction>(vkDestroyShaderModule)},
+    {"vkDeviceWaitIdle", reinterpret_cast<PFN_vkVoidFunction>(vkDeviceWaitIdle)},
+    {"vkEndCommandBuffer", reinterpret_cast<PFN_vkVoidFunction>(vkEndCommandBuffer)},
+    {"vkFlushMappedMemoryRanges", reinterpret_cast<PFN_vkVoidFunction>(vkFlushMappedMemoryRanges)},
+    {"vkFreeDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(vkFreeDescriptorSets)},
+    {"vkFreeMemory", reinterpret_cast<PFN_vkVoidFunction>(vkFreeMemory)},
+    {"vkGetBufferMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(vkGetBufferMemoryRequirements)},
+    {"vkGetDeviceMemoryCommitment", reinterpret_cast<PFN_vkVoidFunction>(vkGetDeviceMemoryCommitment)},
+    {"vkGetDeviceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(vkGetDeviceProcAddr)},
+    {"vkGetDeviceQueue", reinterpret_cast<PFN_vkVoidFunction>(vkGetDeviceQueue)},
+    {"vkGetEventStatus", reinterpret_cast<PFN_vkVoidFunction>(vkGetEventStatus)},
+    {"vkGetFenceStatus", reinterpret_cast<PFN_vkVoidFunction>(vkGetFenceStatus)},
+    {"vkGetImageMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(vkGetImageMemoryRequirements)},
+    {"vkGetImageSparseMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(vkGetImageSparseMemoryRequirements)},
+    {"vkGetImageSubresourceLayout", reinterpret_cast<PFN_vkVoidFunction>(vkGetImageSubresourceLayout)},
+    {"vkGetPipelineCacheData", reinterpret_cast<PFN_vkVoidFunction>(vkGetPipelineCacheData)},
+    {"vkGetPipelineCacheSize", reinterpret_cast<PFN_vkVoidFunction>(vkGetPipelineCacheSize)},
+    {"vkGetQueryPoolResults", reinterpret_cast<PFN_vkVoidFunction>(vkGetQueryPoolResults)},
+    {"vkGetRenderAreaGranularity", reinterpret_cast<PFN_vkVoidFunction>(vkGetRenderAreaGranularity)},
+    {"vkInvalidateMappedMemoryRanges", reinterpret_cast<PFN_vkVoidFunction>(vkInvalidateMappedMemoryRanges)},
+    {"vkMapMemory", reinterpret_cast<PFN_vkVoidFunction>(vkMapMemory)},
+    {"vkMergePipelineCaches", reinterpret_cast<PFN_vkVoidFunction>(vkMergePipelineCaches)},
+    {"vkQueueBindSparseBufferMemory", reinterpret_cast<PFN_vkVoidFunction>(vkQueueBindSparseBufferMemory)},
+    {"vkQueueBindSparseImageMemory", reinterpret_cast<PFN_vkVoidFunction>(vkQueueBindSparseImageMemory)},
+    {"vkQueueBindSparseImageOpaqueMemory", reinterpret_cast<PFN_vkVoidFunction>(vkQueueBindSparseImageOpaqueMemory)},
+    {"vkQueueSignalSemaphore", reinterpret_cast<PFN_vkVoidFunction>(vkQueueSignalSemaphore)},
+    {"vkQueueSubmit", reinterpret_cast<PFN_vkVoidFunction>(vkQueueSubmit)},
+    {"vkQueueWaitIdle", reinterpret_cast<PFN_vkVoidFunction>(vkQueueWaitIdle)},
+    {"vkQueueWaitSemaphore", reinterpret_cast<PFN_vkVoidFunction>(vkQueueWaitSemaphore)},
+    {"vkResetCommandBuffer", reinterpret_cast<PFN_vkVoidFunction>(vkResetCommandBuffer)},
+    {"vkResetCommandPool", reinterpret_cast<PFN_vkVoidFunction>(vkResetCommandPool)},
+    {"vkResetDescriptorPool", reinterpret_cast<PFN_vkVoidFunction>(vkResetDescriptorPool)},
+    {"vkResetEvent", reinterpret_cast<PFN_vkVoidFunction>(vkResetEvent)},
+    {"vkResetFences", reinterpret_cast<PFN_vkVoidFunction>(vkResetFences)},
+    {"vkSetEvent", reinterpret_cast<PFN_vkVoidFunction>(vkSetEvent)},
+    {"vkUnmapMemory", reinterpret_cast<PFN_vkVoidFunction>(vkUnmapMemory)},
+    {"vkUpdateDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(vkUpdateDescriptorSets)},
+    {"vkWaitForFences", reinterpret_cast<PFN_vkVoidFunction>(vkWaitForFences)},
+    // clang-format on
+};
+
+const NameOffsetEntry kInstanceOffsetTbl[] = {
+    // clang-format off
+    {"vkCreateDevice", offsetof(InstanceVtbl, CreateDevice)},
+    {"vkDestroyInstance", offsetof(InstanceVtbl, DestroyInstance)},
+    {"vkEnumeratePhysicalDevices", offsetof(InstanceVtbl, EnumeratePhysicalDevices)},
+    {"vkGetInstanceProcAddr", offsetof(InstanceVtbl, GetInstanceProcAddr)},
+    {"vkGetPhysicalDeviceExtensionProperties", offsetof(InstanceVtbl, GetPhysicalDeviceExtensionProperties)},
+    {"vkGetPhysicalDeviceFeatures", offsetof(InstanceVtbl, GetPhysicalDeviceFeatures)},
+    {"vkGetPhysicalDeviceFormatProperties", offsetof(InstanceVtbl, GetPhysicalDeviceFormatProperties)},
+    {"vkGetPhysicalDeviceImageFormatProperties", offsetof(InstanceVtbl, GetPhysicalDeviceImageFormatProperties)},
+    {"vkGetPhysicalDeviceLayerProperties", offsetof(InstanceVtbl, GetPhysicalDeviceLayerProperties)},
+    {"vkGetPhysicalDeviceLimits", offsetof(InstanceVtbl, GetPhysicalDeviceLimits)},
+    {"vkGetPhysicalDeviceMemoryProperties", offsetof(InstanceVtbl, GetPhysicalDeviceMemoryProperties)},
+    {"vkGetPhysicalDeviceProperties", offsetof(InstanceVtbl, GetPhysicalDeviceProperties)},
+    {"vkGetPhysicalDeviceQueueCount", offsetof(InstanceVtbl, GetPhysicalDeviceQueueCount)},
+    {"vkGetPhysicalDeviceQueueProperties", offsetof(InstanceVtbl, GetPhysicalDeviceQueueProperties)},
+    {"vkGetPhysicalDeviceSparseImageFormatProperties", offsetof(InstanceVtbl, GetPhysicalDeviceSparseImageFormatProperties)},
+    // clang-format on
+};
+
+const NameOffsetEntry kDeviceOffsetTbl[] = {
+    // clang-format off
+    {"vkAllocDescriptorSets", offsetof(DeviceVtbl, AllocDescriptorSets)},
+    {"vkAllocMemory", offsetof(DeviceVtbl, AllocMemory)},
+    {"vkBeginCommandBuffer", offsetof(DeviceVtbl, BeginCommandBuffer)},
+    {"vkBindBufferMemory", offsetof(DeviceVtbl, BindBufferMemory)},
+    {"vkBindImageMemory", offsetof(DeviceVtbl, BindImageMemory)},
+    {"vkCmdBeginQuery", offsetof(DeviceVtbl, CmdBeginQuery)},
+    {"vkCmdBeginRenderPass", offsetof(DeviceVtbl, CmdBeginRenderPass)},
+    {"vkCmdBindDescriptorSets", offsetof(DeviceVtbl, CmdBindDescriptorSets)},
+    {"vkCmdBindDynamicColorBlendState", offsetof(DeviceVtbl, CmdBindDynamicColorBlendState)},
+    {"vkCmdBindDynamicDepthStencilState", offsetof(DeviceVtbl, CmdBindDynamicDepthStencilState)},
+    {"vkCmdBindDynamicRasterState", offsetof(DeviceVtbl, CmdBindDynamicRasterState)},
+    {"vkCmdBindDynamicViewportState", offsetof(DeviceVtbl, CmdBindDynamicViewportState)},
+    {"vkCmdBindIndexBuffer", offsetof(DeviceVtbl, CmdBindIndexBuffer)},
+    {"vkCmdBindPipeline", offsetof(DeviceVtbl, CmdBindPipeline)},
+    {"vkCmdBindVertexBuffers", offsetof(DeviceVtbl, CmdBindVertexBuffers)},
+    {"vkCmdBlitImage", offsetof(DeviceVtbl, CmdBlitImage)},
+    {"vkCmdClearColorAttachment", offsetof(DeviceVtbl, CmdClearColorAttachment)},
+    {"vkCmdClearColorImage", offsetof(DeviceVtbl, CmdClearColorImage)},
+    {"vkCmdClearDepthStencilAttachment", offsetof(DeviceVtbl, CmdClearDepthStencilAttachment)},
+    {"vkCmdClearDepthStencilImage", offsetof(DeviceVtbl, CmdClearDepthStencilImage)},
+    {"vkCmdCopyBuffer", offsetof(DeviceVtbl, CmdCopyBuffer)},
+    {"vkCmdCopyBufferToImage", offsetof(DeviceVtbl, CmdCopyBufferToImage)},
+    {"vkCmdCopyImage", offsetof(DeviceVtbl, CmdCopyImage)},
+    {"vkCmdCopyImageToBuffer", offsetof(DeviceVtbl, CmdCopyImageToBuffer)},
+    {"vkCmdCopyQueryPoolResults", offsetof(DeviceVtbl, CmdCopyQueryPoolResults)},
+    {"vkCmdDispatch", offsetof(DeviceVtbl, CmdDispatch)},
+    {"vkCmdDispatchIndirect", offsetof(DeviceVtbl, CmdDispatchIndirect)},
+    {"vkCmdDraw", offsetof(DeviceVtbl, CmdDraw)},
+    {"vkCmdDrawIndexed", offsetof(DeviceVtbl, CmdDrawIndexed)},
+    {"vkCmdDrawIndexedIndirect", offsetof(DeviceVtbl, CmdDrawIndexedIndirect)},
+    {"vkCmdDrawIndirect", offsetof(DeviceVtbl, CmdDrawIndirect)},
+    {"vkCmdEndQuery", offsetof(DeviceVtbl, CmdEndQuery)},
+    {"vkCmdEndRenderPass", offsetof(DeviceVtbl, CmdEndRenderPass)},
+    {"vkCmdExecuteCommands", offsetof(DeviceVtbl, CmdExecuteCommands)},
+    {"vkCmdFillBuffer", offsetof(DeviceVtbl, CmdFillBuffer)},
+    {"vkCmdNextSubpass", offsetof(DeviceVtbl, CmdNextSubpass)},
+    {"vkCmdPipelineBarrier", offsetof(DeviceVtbl, CmdPipelineBarrier)},
+    {"vkCmdPushConstants", offsetof(DeviceVtbl, CmdPushConstants)},
+    {"vkCmdResetEvent", offsetof(DeviceVtbl, CmdResetEvent)},
+    {"vkCmdResetQueryPool", offsetof(DeviceVtbl, CmdResetQueryPool)},
+    {"vkCmdResolveImage", offsetof(DeviceVtbl, CmdResolveImage)},
+    {"vkCmdSetEvent", offsetof(DeviceVtbl, CmdSetEvent)},
+    {"vkCmdUpdateBuffer", offsetof(DeviceVtbl, CmdUpdateBuffer)},
+    {"vkCmdWaitEvents", offsetof(DeviceVtbl, CmdWaitEvents)},
+    {"vkCmdWriteTimestamp", offsetof(DeviceVtbl, CmdWriteTimestamp)},
+    {"vkCreateAttachmentView", offsetof(DeviceVtbl, CreateAttachmentView)},
+    {"vkCreateBuffer", offsetof(DeviceVtbl, CreateBuffer)},
+    {"vkCreateBufferView", offsetof(DeviceVtbl, CreateBufferView)},
+    {"vkCreateCommandBuffer", offsetof(DeviceVtbl, CreateCommandBuffer)},
+    {"vkCreateCommandPool", offsetof(DeviceVtbl, CreateCommandPool)},
+    {"vkCreateComputePipelines", offsetof(DeviceVtbl, CreateComputePipelines)},
+    {"vkCreateDescriptorPool", offsetof(DeviceVtbl, CreateDescriptorPool)},
+    {"vkCreateDescriptorSetLayout", offsetof(DeviceVtbl, CreateDescriptorSetLayout)},
+    {"vkCreateDynamicColorBlendState", offsetof(DeviceVtbl, CreateDynamicColorBlendState)},
+    {"vkCreateDynamicDepthStencilState", offsetof(DeviceVtbl, CreateDynamicDepthStencilState)},
+    {"vkCreateDynamicRasterState", offsetof(DeviceVtbl, CreateDynamicRasterState)},
+    {"vkCreateDynamicViewportState", offsetof(DeviceVtbl, CreateDynamicViewportState)},
+    {"vkCreateEvent", offsetof(DeviceVtbl, CreateEvent)},
+    {"vkCreateFence", offsetof(DeviceVtbl, CreateFence)},
+    {"vkCreateFramebuffer", offsetof(DeviceVtbl, CreateFramebuffer)},
+    {"vkCreateGraphicsPipelines", offsetof(DeviceVtbl, CreateGraphicsPipelines)},
+    {"vkCreateImage", offsetof(DeviceVtbl, CreateImage)},
+    {"vkCreateImageView", offsetof(DeviceVtbl, CreateImageView)},
+    {"vkCreatePipelineCache", offsetof(DeviceVtbl, CreatePipelineCache)},
+    {"vkCreatePipelineLayout", offsetof(DeviceVtbl, CreatePipelineLayout)},
+    {"vkCreateQueryPool", offsetof(DeviceVtbl, CreateQueryPool)},
+    {"vkCreateRenderPass", offsetof(DeviceVtbl, CreateRenderPass)},
+    {"vkCreateSampler", offsetof(DeviceVtbl, CreateSampler)},
+    {"vkCreateSemaphore", offsetof(DeviceVtbl, CreateSemaphore)},
+    {"vkCreateShader", offsetof(DeviceVtbl, CreateShader)},
+    {"vkCreateShaderModule", offsetof(DeviceVtbl, CreateShaderModule)},
+    {"vkDestroyAttachmentView", offsetof(DeviceVtbl, DestroyAttachmentView)},
+    {"vkDestroyBuffer", offsetof(DeviceVtbl, DestroyBuffer)},
+    {"vkDestroyBufferView", offsetof(DeviceVtbl, DestroyBufferView)},
+    {"vkDestroyCommandBuffer", offsetof(DeviceVtbl, DestroyCommandBuffer)},
+    {"vkDestroyCommandPool", offsetof(DeviceVtbl, DestroyCommandPool)},
+    {"vkDestroyDescriptorPool", offsetof(DeviceVtbl, DestroyDescriptorPool)},
+    {"vkDestroyDescriptorSetLayout", offsetof(DeviceVtbl, DestroyDescriptorSetLayout)},
+    {"vkDestroyDevice", offsetof(DeviceVtbl, DestroyDevice)},
+    {"vkDestroyDynamicColorBlendState", offsetof(DeviceVtbl, DestroyDynamicColorBlendState)},
+    {"vkDestroyDynamicDepthStencilState", offsetof(DeviceVtbl, DestroyDynamicDepthStencilState)},
+    {"vkDestroyDynamicRasterState", offsetof(DeviceVtbl, DestroyDynamicRasterState)},
+    {"vkDestroyDynamicViewportState", offsetof(DeviceVtbl, DestroyDynamicViewportState)},
+    {"vkDestroyEvent", offsetof(DeviceVtbl, DestroyEvent)},
+    {"vkDestroyFence", offsetof(DeviceVtbl, DestroyFence)},
+    {"vkDestroyFramebuffer", offsetof(DeviceVtbl, DestroyFramebuffer)},
+    {"vkDestroyImage", offsetof(DeviceVtbl, DestroyImage)},
+    {"vkDestroyImageView", offsetof(DeviceVtbl, DestroyImageView)},
+    {"vkDestroyPipeline", offsetof(DeviceVtbl, DestroyPipeline)},
+    {"vkDestroyPipelineCache", offsetof(DeviceVtbl, DestroyPipelineCache)},
+    {"vkDestroyPipelineLayout", offsetof(DeviceVtbl, DestroyPipelineLayout)},
+    {"vkDestroyQueryPool", offsetof(DeviceVtbl, DestroyQueryPool)},
+    {"vkDestroyRenderPass", offsetof(DeviceVtbl, DestroyRenderPass)},
+    {"vkDestroySampler", offsetof(DeviceVtbl, DestroySampler)},
+    {"vkDestroySemaphore", offsetof(DeviceVtbl, DestroySemaphore)},
+    {"vkDestroyShader", offsetof(DeviceVtbl, DestroyShader)},
+    {"vkDestroyShaderModule", offsetof(DeviceVtbl, DestroyShaderModule)},
+    {"vkDeviceWaitIdle", offsetof(DeviceVtbl, DeviceWaitIdle)},
+    {"vkEndCommandBuffer", offsetof(DeviceVtbl, EndCommandBuffer)},
+    {"vkFlushMappedMemoryRanges", offsetof(DeviceVtbl, FlushMappedMemoryRanges)},
+    {"vkFreeDescriptorSets", offsetof(DeviceVtbl, FreeDescriptorSets)},
+    {"vkFreeMemory", offsetof(DeviceVtbl, FreeMemory)},
+    {"vkGetBufferMemoryRequirements", offsetof(DeviceVtbl, GetBufferMemoryRequirements)},
+    {"vkGetDeviceMemoryCommitment", offsetof(DeviceVtbl, GetDeviceMemoryCommitment)},
+    {"vkGetDeviceProcAddr", offsetof(DeviceVtbl, GetDeviceProcAddr)},
+    {"vkGetDeviceQueue", offsetof(DeviceVtbl, GetDeviceQueue)},
+    {"vkGetEventStatus", offsetof(DeviceVtbl, GetEventStatus)},
+    {"vkGetFenceStatus", offsetof(DeviceVtbl, GetFenceStatus)},
+    {"vkGetImageMemoryRequirements", offsetof(DeviceVtbl, GetImageMemoryRequirements)},
+    {"vkGetImageSparseMemoryRequirements", offsetof(DeviceVtbl, GetImageSparseMemoryRequirements)},
+    {"vkGetImageSubresourceLayout", offsetof(DeviceVtbl, GetImageSubresourceLayout)},
+    {"vkGetPipelineCacheData", offsetof(DeviceVtbl, GetPipelineCacheData)},
+    {"vkGetPipelineCacheSize", offsetof(DeviceVtbl, GetPipelineCacheSize)},
+    {"vkGetQueryPoolResults", offsetof(DeviceVtbl, GetQueryPoolResults)},
+    {"vkGetRenderAreaGranularity", offsetof(DeviceVtbl, GetRenderAreaGranularity)},
+    {"vkInvalidateMappedMemoryRanges", offsetof(DeviceVtbl, InvalidateMappedMemoryRanges)},
+    {"vkMapMemory", offsetof(DeviceVtbl, MapMemory)},
+    {"vkMergePipelineCaches", offsetof(DeviceVtbl, MergePipelineCaches)},
+    {"vkQueueBindSparseBufferMemory", offsetof(DeviceVtbl, QueueBindSparseBufferMemory)},
+    {"vkQueueBindSparseImageMemory", offsetof(DeviceVtbl, QueueBindSparseImageMemory)},
+    {"vkQueueBindSparseImageOpaqueMemory", offsetof(DeviceVtbl, QueueBindSparseImageOpaqueMemory)},
+    {"vkQueueSignalSemaphore", offsetof(DeviceVtbl, QueueSignalSemaphore)},
+    {"vkQueueSubmit", offsetof(DeviceVtbl, QueueSubmit)},
+    {"vkQueueWaitIdle", offsetof(DeviceVtbl, QueueWaitIdle)},
+    {"vkQueueWaitSemaphore", offsetof(DeviceVtbl, QueueWaitSemaphore)},
+    {"vkResetCommandBuffer", offsetof(DeviceVtbl, ResetCommandBuffer)},
+    {"vkResetCommandPool", offsetof(DeviceVtbl, ResetCommandPool)},
+    {"vkResetDescriptorPool", offsetof(DeviceVtbl, ResetDescriptorPool)},
+    {"vkResetEvent", offsetof(DeviceVtbl, ResetEvent)},
+    {"vkResetFences", offsetof(DeviceVtbl, ResetFences)},
+    {"vkSetEvent", offsetof(DeviceVtbl, SetEvent)},
+    {"vkUnmapMemory", offsetof(DeviceVtbl, UnmapMemory)},
+    {"vkUpdateDescriptorSets", offsetof(DeviceVtbl, UpdateDescriptorSets)},
+    {"vkWaitForFences", offsetof(DeviceVtbl, WaitForFences)},
+    // clang-format on
+};
+
+}  // namespace
+
+namespace vulkan {
+
+PFN_vkVoidFunction GetGlobalInstanceProcAddr(const char* name) {
+    if (strcmp(name, "vkGetDeviceProcAddr") == 0)
+        return reinterpret_cast<PFN_vkVoidFunction>(vkGetDeviceProcAddr);
+    const NameProcEntry* entry = FindProcEntry(kInstanceProcTbl, name);
+    return entry ? entry->proc : nullptr;
+}
+
+PFN_vkVoidFunction GetGlobalDeviceProcAddr(const char* name) {
+    const NameProcEntry* entry = FindProcEntry(kDeviceProcTbl, name);
+    return entry ? entry->proc : nullptr;
+}
+
+PFN_vkVoidFunction GetSpecificInstanceProcAddr(const InstanceVtbl* vtbl,
+                                               const char* name) {
+    const NameOffsetEntry* entry = FindProcEntry(kInstanceOffsetTbl, name);
+    if (!entry)
+        return nullptr;
+    const unsigned char* base = reinterpret_cast<const unsigned char*>(vtbl);
+    return reinterpret_cast<PFN_vkVoidFunction>(
+        const_cast<unsigned char*>(base) + entry->offset);
+}
+
+PFN_vkVoidFunction GetSpecificDeviceProcAddr(const DeviceVtbl* vtbl,
+                                             const char* name) {
+    const NameOffsetEntry* entry = FindProcEntry(kDeviceOffsetTbl, name);
+    if (!entry)
+        return nullptr;
+    const unsigned char* base = reinterpret_cast<const unsigned char*>(vtbl);
+    return reinterpret_cast<PFN_vkVoidFunction>(
+        const_cast<unsigned char*>(base) + entry->offset);
+}
+
+bool LoadInstanceVtbl(VkInstance instance,
+                      PFN_vkGetInstanceProcAddr get_proc_addr,
+                      InstanceVtbl& vtbl) {
+    bool success = true;
+    // clang-format off
+    vtbl.DestroyInstance = reinterpret_cast<PFN_vkDestroyInstance>(get_proc_addr(instance, "vkDestroyInstance"));
+    if (UNLIKELY(!vtbl.DestroyInstance)) {
+        ALOGE("missing instance proc: %s", "vkDestroyInstance");
+        success = false;
+    }
+    vtbl.EnumeratePhysicalDevices = reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(get_proc_addr(instance, "vkEnumeratePhysicalDevices"));
+    if (UNLIKELY(!vtbl.EnumeratePhysicalDevices)) {
+        ALOGE("missing instance proc: %s", "vkEnumeratePhysicalDevices");
+        success = false;
+    }
+    vtbl.GetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(get_proc_addr(instance, "vkGetInstanceProcAddr"));
+    if (UNLIKELY(!vtbl.GetInstanceProcAddr)) {
+        ALOGE("missing instance proc: %s", "vkGetInstanceProcAddr");
+        success = false;
+    }
+    vtbl.GetPhysicalDeviceProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(get_proc_addr(instance, "vkGetPhysicalDeviceProperties"));
+    if (UNLIKELY(!vtbl.GetPhysicalDeviceProperties)) {
+        ALOGE("missing instance proc: %s", "vkGetPhysicalDeviceProperties");
+        success = false;
+    }
+    vtbl.GetPhysicalDeviceQueueCount = reinterpret_cast<PFN_vkGetPhysicalDeviceQueueCount>(get_proc_addr(instance, "vkGetPhysicalDeviceQueueCount"));
+    if (UNLIKELY(!vtbl.GetPhysicalDeviceQueueCount)) {
+        ALOGE("missing instance proc: %s", "vkGetPhysicalDeviceQueueCount");
+        success = false;
+    }
+    vtbl.GetPhysicalDeviceQueueProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceQueueProperties>(get_proc_addr(instance, "vkGetPhysicalDeviceQueueProperties"));
+    if (UNLIKELY(!vtbl.GetPhysicalDeviceQueueProperties)) {
+        ALOGE("missing instance proc: %s", "vkGetPhysicalDeviceQueueProperties");
+        success = false;
+    }
+    vtbl.GetPhysicalDeviceMemoryProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceMemoryProperties>(get_proc_addr(instance, "vkGetPhysicalDeviceMemoryProperties"));
+    if (UNLIKELY(!vtbl.GetPhysicalDeviceMemoryProperties)) {
+        ALOGE("missing instance proc: %s", "vkGetPhysicalDeviceMemoryProperties");
+        success = false;
+    }
+    vtbl.GetPhysicalDeviceFeatures = reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures>(get_proc_addr(instance, "vkGetPhysicalDeviceFeatures"));
+    if (UNLIKELY(!vtbl.GetPhysicalDeviceFeatures)) {
+        ALOGE("missing instance proc: %s", "vkGetPhysicalDeviceFeatures");
+        success = false;
+    }
+    vtbl.GetPhysicalDeviceFormatProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceFormatProperties>(get_proc_addr(instance, "vkGetPhysicalDeviceFormatProperties"));
+    if (UNLIKELY(!vtbl.GetPhysicalDeviceFormatProperties)) {
+        ALOGE("missing instance proc: %s", "vkGetPhysicalDeviceFormatProperties");
+        success = false;
+    }
+    vtbl.GetPhysicalDeviceImageFormatProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceImageFormatProperties>(get_proc_addr(instance, "vkGetPhysicalDeviceImageFormatProperties"));
+    if (UNLIKELY(!vtbl.GetPhysicalDeviceImageFormatProperties)) {
+        ALOGE("missing instance proc: %s", "vkGetPhysicalDeviceImageFormatProperties");
+        success = false;
+    }
+    vtbl.GetPhysicalDeviceLimits = reinterpret_cast<PFN_vkGetPhysicalDeviceLimits>(get_proc_addr(instance, "vkGetPhysicalDeviceLimits"));
+    if (UNLIKELY(!vtbl.GetPhysicalDeviceLimits)) {
+        ALOGE("missing instance proc: %s", "vkGetPhysicalDeviceLimits");
+        success = false;
+    }
+    vtbl.CreateDevice = reinterpret_cast<PFN_vkCreateDevice>(get_proc_addr(instance, "vkCreateDevice"));
+    if (UNLIKELY(!vtbl.CreateDevice)) {
+        ALOGE("missing instance proc: %s", "vkCreateDevice");
+        success = false;
+    }
+    vtbl.GetPhysicalDeviceLayerProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceLayerProperties>(get_proc_addr(instance, "vkGetPhysicalDeviceLayerProperties"));
+    if (UNLIKELY(!vtbl.GetPhysicalDeviceLayerProperties)) {
+        ALOGE("missing instance proc: %s", "vkGetPhysicalDeviceLayerProperties");
+        success = false;
+    }
+    vtbl.GetPhysicalDeviceExtensionProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceExtensionProperties>(get_proc_addr(instance, "vkGetPhysicalDeviceExtensionProperties"));
+    if (UNLIKELY(!vtbl.GetPhysicalDeviceExtensionProperties)) {
+        ALOGE("missing instance proc: %s", "vkGetPhysicalDeviceExtensionProperties");
+        success = false;
+    }
+    vtbl.GetPhysicalDeviceSparseImageFormatProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceSparseImageFormatProperties>(get_proc_addr(instance, "vkGetPhysicalDeviceSparseImageFormatProperties"));
+    if (UNLIKELY(!vtbl.GetPhysicalDeviceSparseImageFormatProperties)) {
+        ALOGE("missing instance proc: %s", "vkGetPhysicalDeviceSparseImageFormatProperties");
+        success = false;
+    }
+    // clang-format on
+    return success;
+}
+
+bool LoadDeviceVtbl(VkDevice device,
+                    PFN_vkGetDeviceProcAddr get_proc_addr,
+                    DeviceVtbl& vtbl) {
+    bool success = true;
+    // clang-format off
+    vtbl.GetDeviceProcAddr = reinterpret_cast<PFN_vkGetDeviceProcAddr>(get_proc_addr(device, "vkGetDeviceProcAddr"));
+    if (UNLIKELY(!vtbl.GetDeviceProcAddr)) {
+        ALOGE("missing device proc: %s", "vkGetDeviceProcAddr");
+        success = false;
+    }
+    vtbl.DestroyDevice = reinterpret_cast<PFN_vkDestroyDevice>(get_proc_addr(device, "vkDestroyDevice"));
+    if (UNLIKELY(!vtbl.DestroyDevice)) {
+        ALOGE("missing device proc: %s", "vkDestroyDevice");
+        success = false;
+    }
+    vtbl.GetDeviceQueue = reinterpret_cast<PFN_vkGetDeviceQueue>(get_proc_addr(device, "vkGetDeviceQueue"));
+    if (UNLIKELY(!vtbl.GetDeviceQueue)) {
+        ALOGE("missing device proc: %s", "vkGetDeviceQueue");
+        success = false;
+    }
+    vtbl.QueueSubmit = reinterpret_cast<PFN_vkQueueSubmit>(get_proc_addr(device, "vkQueueSubmit"));
+    if (UNLIKELY(!vtbl.QueueSubmit)) {
+        ALOGE("missing device proc: %s", "vkQueueSubmit");
+        success = false;
+    }
+    vtbl.QueueWaitIdle = reinterpret_cast<PFN_vkQueueWaitIdle>(get_proc_addr(device, "vkQueueWaitIdle"));
+    if (UNLIKELY(!vtbl.QueueWaitIdle)) {
+        ALOGE("missing device proc: %s", "vkQueueWaitIdle");
+        success = false;
+    }
+    vtbl.DeviceWaitIdle = reinterpret_cast<PFN_vkDeviceWaitIdle>(get_proc_addr(device, "vkDeviceWaitIdle"));
+    if (UNLIKELY(!vtbl.DeviceWaitIdle)) {
+        ALOGE("missing device proc: %s", "vkDeviceWaitIdle");
+        success = false;
+    }
+    vtbl.AllocMemory = reinterpret_cast<PFN_vkAllocMemory>(get_proc_addr(device, "vkAllocMemory"));
+    if (UNLIKELY(!vtbl.AllocMemory)) {
+        ALOGE("missing device proc: %s", "vkAllocMemory");
+        success = false;
+    }
+    vtbl.FreeMemory = reinterpret_cast<PFN_vkFreeMemory>(get_proc_addr(device, "vkFreeMemory"));
+    if (UNLIKELY(!vtbl.FreeMemory)) {
+        ALOGE("missing device proc: %s", "vkFreeMemory");
+        success = false;
+    }
+    vtbl.MapMemory = reinterpret_cast<PFN_vkMapMemory>(get_proc_addr(device, "vkMapMemory"));
+    if (UNLIKELY(!vtbl.MapMemory)) {
+        ALOGE("missing device proc: %s", "vkMapMemory");
+        success = false;
+    }
+    vtbl.UnmapMemory = reinterpret_cast<PFN_vkUnmapMemory>(get_proc_addr(device, "vkUnmapMemory"));
+    if (UNLIKELY(!vtbl.UnmapMemory)) {
+        ALOGE("missing device proc: %s", "vkUnmapMemory");
+        success = false;
+    }
+    vtbl.FlushMappedMemoryRanges = reinterpret_cast<PFN_vkFlushMappedMemoryRanges>(get_proc_addr(device, "vkFlushMappedMemoryRanges"));
+    if (UNLIKELY(!vtbl.FlushMappedMemoryRanges)) {
+        ALOGE("missing device proc: %s", "vkFlushMappedMemoryRanges");
+        success = false;
+    }
+    vtbl.InvalidateMappedMemoryRanges = reinterpret_cast<PFN_vkInvalidateMappedMemoryRanges>(get_proc_addr(device, "vkInvalidateMappedMemoryRanges"));
+    if (UNLIKELY(!vtbl.InvalidateMappedMemoryRanges)) {
+        ALOGE("missing device proc: %s", "vkInvalidateMappedMemoryRanges");
+        success = false;
+    }
+    vtbl.GetDeviceMemoryCommitment = reinterpret_cast<PFN_vkGetDeviceMemoryCommitment>(get_proc_addr(device, "vkGetDeviceMemoryCommitment"));
+    if (UNLIKELY(!vtbl.GetDeviceMemoryCommitment)) {
+        ALOGE("missing device proc: %s", "vkGetDeviceMemoryCommitment");
+        success = false;
+    }
+    vtbl.GetBufferMemoryRequirements = reinterpret_cast<PFN_vkGetBufferMemoryRequirements>(get_proc_addr(device, "vkGetBufferMemoryRequirements"));
+    if (UNLIKELY(!vtbl.GetBufferMemoryRequirements)) {
+        ALOGE("missing device proc: %s", "vkGetBufferMemoryRequirements");
+        success = false;
+    }
+    vtbl.BindBufferMemory = reinterpret_cast<PFN_vkBindBufferMemory>(get_proc_addr(device, "vkBindBufferMemory"));
+    if (UNLIKELY(!vtbl.BindBufferMemory)) {
+        ALOGE("missing device proc: %s", "vkBindBufferMemory");
+        success = false;
+    }
+    vtbl.GetImageMemoryRequirements = reinterpret_cast<PFN_vkGetImageMemoryRequirements>(get_proc_addr(device, "vkGetImageMemoryRequirements"));
+    if (UNLIKELY(!vtbl.GetImageMemoryRequirements)) {
+        ALOGE("missing device proc: %s", "vkGetImageMemoryRequirements");
+        success = false;
+    }
+    vtbl.BindImageMemory = reinterpret_cast<PFN_vkBindImageMemory>(get_proc_addr(device, "vkBindImageMemory"));
+    if (UNLIKELY(!vtbl.BindImageMemory)) {
+        ALOGE("missing device proc: %s", "vkBindImageMemory");
+        success = false;
+    }
+    vtbl.GetImageSparseMemoryRequirements = reinterpret_cast<PFN_vkGetImageSparseMemoryRequirements>(get_proc_addr(device, "vkGetImageSparseMemoryRequirements"));
+    if (UNLIKELY(!vtbl.GetImageSparseMemoryRequirements)) {
+        ALOGE("missing device proc: %s", "vkGetImageSparseMemoryRequirements");
+        success = false;
+    }
+    vtbl.QueueBindSparseBufferMemory = reinterpret_cast<PFN_vkQueueBindSparseBufferMemory>(get_proc_addr(device, "vkQueueBindSparseBufferMemory"));
+    if (UNLIKELY(!vtbl.QueueBindSparseBufferMemory)) {
+        ALOGE("missing device proc: %s", "vkQueueBindSparseBufferMemory");
+        success = false;
+    }
+    vtbl.QueueBindSparseImageOpaqueMemory = reinterpret_cast<PFN_vkQueueBindSparseImageOpaqueMemory>(get_proc_addr(device, "vkQueueBindSparseImageOpaqueMemory"));
+    if (UNLIKELY(!vtbl.QueueBindSparseImageOpaqueMemory)) {
+        ALOGE("missing device proc: %s", "vkQueueBindSparseImageOpaqueMemory");
+        success = false;
+    }
+    vtbl.QueueBindSparseImageMemory = reinterpret_cast<PFN_vkQueueBindSparseImageMemory>(get_proc_addr(device, "vkQueueBindSparseImageMemory"));
+    if (UNLIKELY(!vtbl.QueueBindSparseImageMemory)) {
+        ALOGE("missing device proc: %s", "vkQueueBindSparseImageMemory");
+        success = false;
+    }
+    vtbl.CreateFence = reinterpret_cast<PFN_vkCreateFence>(get_proc_addr(device, "vkCreateFence"));
+    if (UNLIKELY(!vtbl.CreateFence)) {
+        ALOGE("missing device proc: %s", "vkCreateFence");
+        success = false;
+    }
+    vtbl.DestroyFence = reinterpret_cast<PFN_vkDestroyFence>(get_proc_addr(device, "vkDestroyFence"));
+    if (UNLIKELY(!vtbl.DestroyFence)) {
+        ALOGE("missing device proc: %s", "vkDestroyFence");
+        success = false;
+    }
+    vtbl.ResetFences = reinterpret_cast<PFN_vkResetFences>(get_proc_addr(device, "vkResetFences"));
+    if (UNLIKELY(!vtbl.ResetFences)) {
+        ALOGE("missing device proc: %s", "vkResetFences");
+        success = false;
+    }
+    vtbl.GetFenceStatus = reinterpret_cast<PFN_vkGetFenceStatus>(get_proc_addr(device, "vkGetFenceStatus"));
+    if (UNLIKELY(!vtbl.GetFenceStatus)) {
+        ALOGE("missing device proc: %s", "vkGetFenceStatus");
+        success = false;
+    }
+    vtbl.WaitForFences = reinterpret_cast<PFN_vkWaitForFences>(get_proc_addr(device, "vkWaitForFences"));
+    if (UNLIKELY(!vtbl.WaitForFences)) {
+        ALOGE("missing device proc: %s", "vkWaitForFences");
+        success = false;
+    }
+    vtbl.CreateSemaphore = reinterpret_cast<PFN_vkCreateSemaphore>(get_proc_addr(device, "vkCreateSemaphore"));
+    if (UNLIKELY(!vtbl.CreateSemaphore)) {
+        ALOGE("missing device proc: %s", "vkCreateSemaphore");
+        success = false;
+    }
+    vtbl.DestroySemaphore = reinterpret_cast<PFN_vkDestroySemaphore>(get_proc_addr(device, "vkDestroySemaphore"));
+    if (UNLIKELY(!vtbl.DestroySemaphore)) {
+        ALOGE("missing device proc: %s", "vkDestroySemaphore");
+        success = false;
+    }
+    vtbl.QueueSignalSemaphore = reinterpret_cast<PFN_vkQueueSignalSemaphore>(get_proc_addr(device, "vkQueueSignalSemaphore"));
+    if (UNLIKELY(!vtbl.QueueSignalSemaphore)) {
+        ALOGE("missing device proc: %s", "vkQueueSignalSemaphore");
+        success = false;
+    }
+    vtbl.QueueWaitSemaphore = reinterpret_cast<PFN_vkQueueWaitSemaphore>(get_proc_addr(device, "vkQueueWaitSemaphore"));
+    if (UNLIKELY(!vtbl.QueueWaitSemaphore)) {
+        ALOGE("missing device proc: %s", "vkQueueWaitSemaphore");
+        success = false;
+    }
+    vtbl.CreateEvent = reinterpret_cast<PFN_vkCreateEvent>(get_proc_addr(device, "vkCreateEvent"));
+    if (UNLIKELY(!vtbl.CreateEvent)) {
+        ALOGE("missing device proc: %s", "vkCreateEvent");
+        success = false;
+    }
+    vtbl.DestroyEvent = reinterpret_cast<PFN_vkDestroyEvent>(get_proc_addr(device, "vkDestroyEvent"));
+    if (UNLIKELY(!vtbl.DestroyEvent)) {
+        ALOGE("missing device proc: %s", "vkDestroyEvent");
+        success = false;
+    }
+    vtbl.GetEventStatus = reinterpret_cast<PFN_vkGetEventStatus>(get_proc_addr(device, "vkGetEventStatus"));
+    if (UNLIKELY(!vtbl.GetEventStatus)) {
+        ALOGE("missing device proc: %s", "vkGetEventStatus");
+        success = false;
+    }
+    vtbl.SetEvent = reinterpret_cast<PFN_vkSetEvent>(get_proc_addr(device, "vkSetEvent"));
+    if (UNLIKELY(!vtbl.SetEvent)) {
+        ALOGE("missing device proc: %s", "vkSetEvent");
+        success = false;
+    }
+    vtbl.ResetEvent = reinterpret_cast<PFN_vkResetEvent>(get_proc_addr(device, "vkResetEvent"));
+    if (UNLIKELY(!vtbl.ResetEvent)) {
+        ALOGE("missing device proc: %s", "vkResetEvent");
+        success = false;
+    }
+    vtbl.CreateQueryPool = reinterpret_cast<PFN_vkCreateQueryPool>(get_proc_addr(device, "vkCreateQueryPool"));
+    if (UNLIKELY(!vtbl.CreateQueryPool)) {
+        ALOGE("missing device proc: %s", "vkCreateQueryPool");
+        success = false;
+    }
+    vtbl.DestroyQueryPool = reinterpret_cast<PFN_vkDestroyQueryPool>(get_proc_addr(device, "vkDestroyQueryPool"));
+    if (UNLIKELY(!vtbl.DestroyQueryPool)) {
+        ALOGE("missing device proc: %s", "vkDestroyQueryPool");
+        success = false;
+    }
+    vtbl.GetQueryPoolResults = reinterpret_cast<PFN_vkGetQueryPoolResults>(get_proc_addr(device, "vkGetQueryPoolResults"));
+    if (UNLIKELY(!vtbl.GetQueryPoolResults)) {
+        ALOGE("missing device proc: %s", "vkGetQueryPoolResults");
+        success = false;
+    }
+    vtbl.CreateBuffer = reinterpret_cast<PFN_vkCreateBuffer>(get_proc_addr(device, "vkCreateBuffer"));
+    if (UNLIKELY(!vtbl.CreateBuffer)) {
+        ALOGE("missing device proc: %s", "vkCreateBuffer");
+        success = false;
+    }
+    vtbl.DestroyBuffer = reinterpret_cast<PFN_vkDestroyBuffer>(get_proc_addr(device, "vkDestroyBuffer"));
+    if (UNLIKELY(!vtbl.DestroyBuffer)) {
+        ALOGE("missing device proc: %s", "vkDestroyBuffer");
+        success = false;
+    }
+    vtbl.CreateBufferView = reinterpret_cast<PFN_vkCreateBufferView>(get_proc_addr(device, "vkCreateBufferView"));
+    if (UNLIKELY(!vtbl.CreateBufferView)) {
+        ALOGE("missing device proc: %s", "vkCreateBufferView");
+        success = false;
+    }
+    vtbl.DestroyBufferView = reinterpret_cast<PFN_vkDestroyBufferView>(get_proc_addr(device, "vkDestroyBufferView"));
+    if (UNLIKELY(!vtbl.DestroyBufferView)) {
+        ALOGE("missing device proc: %s", "vkDestroyBufferView");
+        success = false;
+    }
+    vtbl.CreateImage = reinterpret_cast<PFN_vkCreateImage>(get_proc_addr(device, "vkCreateImage"));
+    if (UNLIKELY(!vtbl.CreateImage)) {
+        ALOGE("missing device proc: %s", "vkCreateImage");
+        success = false;
+    }
+    vtbl.DestroyImage = reinterpret_cast<PFN_vkDestroyImage>(get_proc_addr(device, "vkDestroyImage"));
+    if (UNLIKELY(!vtbl.DestroyImage)) {
+        ALOGE("missing device proc: %s", "vkDestroyImage");
+        success = false;
+    }
+    vtbl.GetImageSubresourceLayout = reinterpret_cast<PFN_vkGetImageSubresourceLayout>(get_proc_addr(device, "vkGetImageSubresourceLayout"));
+    if (UNLIKELY(!vtbl.GetImageSubresourceLayout)) {
+        ALOGE("missing device proc: %s", "vkGetImageSubresourceLayout");
+        success = false;
+    }
+    vtbl.CreateImageView = reinterpret_cast<PFN_vkCreateImageView>(get_proc_addr(device, "vkCreateImageView"));
+    if (UNLIKELY(!vtbl.CreateImageView)) {
+        ALOGE("missing device proc: %s", "vkCreateImageView");
+        success = false;
+    }
+    vtbl.DestroyImageView = reinterpret_cast<PFN_vkDestroyImageView>(get_proc_addr(device, "vkDestroyImageView"));
+    if (UNLIKELY(!vtbl.DestroyImageView)) {
+        ALOGE("missing device proc: %s", "vkDestroyImageView");
+        success = false;
+    }
+    vtbl.CreateAttachmentView = reinterpret_cast<PFN_vkCreateAttachmentView>(get_proc_addr(device, "vkCreateAttachmentView"));
+    if (UNLIKELY(!vtbl.CreateAttachmentView)) {
+        ALOGE("missing device proc: %s", "vkCreateAttachmentView");
+        success = false;
+    }
+    vtbl.DestroyAttachmentView = reinterpret_cast<PFN_vkDestroyAttachmentView>(get_proc_addr(device, "vkDestroyAttachmentView"));
+    if (UNLIKELY(!vtbl.DestroyAttachmentView)) {
+        ALOGE("missing device proc: %s", "vkDestroyAttachmentView");
+        success = false;
+    }
+    vtbl.CreateShaderModule = reinterpret_cast<PFN_vkCreateShaderModule>(get_proc_addr(device, "vkCreateShaderModule"));
+    if (UNLIKELY(!vtbl.CreateShaderModule)) {
+        ALOGE("missing device proc: %s", "vkCreateShaderModule");
+        success = false;
+    }
+    vtbl.DestroyShaderModule = reinterpret_cast<PFN_vkDestroyShaderModule>(get_proc_addr(device, "vkDestroyShaderModule"));
+    if (UNLIKELY(!vtbl.DestroyShaderModule)) {
+        ALOGE("missing device proc: %s", "vkDestroyShaderModule");
+        success = false;
+    }
+    vtbl.CreateShader = reinterpret_cast<PFN_vkCreateShader>(get_proc_addr(device, "vkCreateShader"));
+    if (UNLIKELY(!vtbl.CreateShader)) {
+        ALOGE("missing device proc: %s", "vkCreateShader");
+        success = false;
+    }
+    vtbl.DestroyShader = reinterpret_cast<PFN_vkDestroyShader>(get_proc_addr(device, "vkDestroyShader"));
+    if (UNLIKELY(!vtbl.DestroyShader)) {
+        ALOGE("missing device proc: %s", "vkDestroyShader");
+        success = false;
+    }
+    vtbl.CreatePipelineCache = reinterpret_cast<PFN_vkCreatePipelineCache>(get_proc_addr(device, "vkCreatePipelineCache"));
+    if (UNLIKELY(!vtbl.CreatePipelineCache)) {
+        ALOGE("missing device proc: %s", "vkCreatePipelineCache");
+        success = false;
+    }
+    vtbl.DestroyPipelineCache = reinterpret_cast<PFN_vkDestroyPipelineCache>(get_proc_addr(device, "vkDestroyPipelineCache"));
+    if (UNLIKELY(!vtbl.DestroyPipelineCache)) {
+        ALOGE("missing device proc: %s", "vkDestroyPipelineCache");
+        success = false;
+    }
+    vtbl.GetPipelineCacheSize = reinterpret_cast<PFN_vkGetPipelineCacheSize>(get_proc_addr(device, "vkGetPipelineCacheSize"));
+    if (UNLIKELY(!vtbl.GetPipelineCacheSize)) {
+        ALOGE("missing device proc: %s", "vkGetPipelineCacheSize");
+        success = false;
+    }
+    vtbl.GetPipelineCacheData = reinterpret_cast<PFN_vkGetPipelineCacheData>(get_proc_addr(device, "vkGetPipelineCacheData"));
+    if (UNLIKELY(!vtbl.GetPipelineCacheData)) {
+        ALOGE("missing device proc: %s", "vkGetPipelineCacheData");
+        success = false;
+    }
+    vtbl.MergePipelineCaches = reinterpret_cast<PFN_vkMergePipelineCaches>(get_proc_addr(device, "vkMergePipelineCaches"));
+    if (UNLIKELY(!vtbl.MergePipelineCaches)) {
+        ALOGE("missing device proc: %s", "vkMergePipelineCaches");
+        success = false;
+    }
+    vtbl.CreateGraphicsPipelines = reinterpret_cast<PFN_vkCreateGraphicsPipelines>(get_proc_addr(device, "vkCreateGraphicsPipelines"));
+    if (UNLIKELY(!vtbl.CreateGraphicsPipelines)) {
+        ALOGE("missing device proc: %s", "vkCreateGraphicsPipelines");
+        success = false;
+    }
+    vtbl.CreateComputePipelines = reinterpret_cast<PFN_vkCreateComputePipelines>(get_proc_addr(device, "vkCreateComputePipelines"));
+    if (UNLIKELY(!vtbl.CreateComputePipelines)) {
+        ALOGE("missing device proc: %s", "vkCreateComputePipelines");
+        success = false;
+    }
+    vtbl.DestroyPipeline = reinterpret_cast<PFN_vkDestroyPipeline>(get_proc_addr(device, "vkDestroyPipeline"));
+    if (UNLIKELY(!vtbl.DestroyPipeline)) {
+        ALOGE("missing device proc: %s", "vkDestroyPipeline");
+        success = false;
+    }
+    vtbl.CreatePipelineLayout = reinterpret_cast<PFN_vkCreatePipelineLayout>(get_proc_addr(device, "vkCreatePipelineLayout"));
+    if (UNLIKELY(!vtbl.CreatePipelineLayout)) {
+        ALOGE("missing device proc: %s", "vkCreatePipelineLayout");
+        success = false;
+    }
+    vtbl.DestroyPipelineLayout = reinterpret_cast<PFN_vkDestroyPipelineLayout>(get_proc_addr(device, "vkDestroyPipelineLayout"));
+    if (UNLIKELY(!vtbl.DestroyPipelineLayout)) {
+        ALOGE("missing device proc: %s", "vkDestroyPipelineLayout");
+        success = false;
+    }
+    vtbl.CreateSampler = reinterpret_cast<PFN_vkCreateSampler>(get_proc_addr(device, "vkCreateSampler"));
+    if (UNLIKELY(!vtbl.CreateSampler)) {
+        ALOGE("missing device proc: %s", "vkCreateSampler");
+        success = false;
+    }
+    vtbl.DestroySampler = reinterpret_cast<PFN_vkDestroySampler>(get_proc_addr(device, "vkDestroySampler"));
+    if (UNLIKELY(!vtbl.DestroySampler)) {
+        ALOGE("missing device proc: %s", "vkDestroySampler");
+        success = false;
+    }
+    vtbl.CreateDescriptorSetLayout = reinterpret_cast<PFN_vkCreateDescriptorSetLayout>(get_proc_addr(device, "vkCreateDescriptorSetLayout"));
+    if (UNLIKELY(!vtbl.CreateDescriptorSetLayout)) {
+        ALOGE("missing device proc: %s", "vkCreateDescriptorSetLayout");
+        success = false;
+    }
+    vtbl.DestroyDescriptorSetLayout = reinterpret_cast<PFN_vkDestroyDescriptorSetLayout>(get_proc_addr(device, "vkDestroyDescriptorSetLayout"));
+    if (UNLIKELY(!vtbl.DestroyDescriptorSetLayout)) {
+        ALOGE("missing device proc: %s", "vkDestroyDescriptorSetLayout");
+        success = false;
+    }
+    vtbl.CreateDescriptorPool = reinterpret_cast<PFN_vkCreateDescriptorPool>(get_proc_addr(device, "vkCreateDescriptorPool"));
+    if (UNLIKELY(!vtbl.CreateDescriptorPool)) {
+        ALOGE("missing device proc: %s", "vkCreateDescriptorPool");
+        success = false;
+    }
+    vtbl.DestroyDescriptorPool = reinterpret_cast<PFN_vkDestroyDescriptorPool>(get_proc_addr(device, "vkDestroyDescriptorPool"));
+    if (UNLIKELY(!vtbl.DestroyDescriptorPool)) {
+        ALOGE("missing device proc: %s", "vkDestroyDescriptorPool");
+        success = false;
+    }
+    vtbl.ResetDescriptorPool = reinterpret_cast<PFN_vkResetDescriptorPool>(get_proc_addr(device, "vkResetDescriptorPool"));
+    if (UNLIKELY(!vtbl.ResetDescriptorPool)) {
+        ALOGE("missing device proc: %s", "vkResetDescriptorPool");
+        success = false;
+    }
+    vtbl.AllocDescriptorSets = reinterpret_cast<PFN_vkAllocDescriptorSets>(get_proc_addr(device, "vkAllocDescriptorSets"));
+    if (UNLIKELY(!vtbl.AllocDescriptorSets)) {
+        ALOGE("missing device proc: %s", "vkAllocDescriptorSets");
+        success = false;
+    }
+    vtbl.FreeDescriptorSets = reinterpret_cast<PFN_vkFreeDescriptorSets>(get_proc_addr(device, "vkFreeDescriptorSets"));
+    if (UNLIKELY(!vtbl.FreeDescriptorSets)) {
+        ALOGE("missing device proc: %s", "vkFreeDescriptorSets");
+        success = false;
+    }
+    vtbl.UpdateDescriptorSets = reinterpret_cast<PFN_vkUpdateDescriptorSets>(get_proc_addr(device, "vkUpdateDescriptorSets"));
+    if (UNLIKELY(!vtbl.UpdateDescriptorSets)) {
+        ALOGE("missing device proc: %s", "vkUpdateDescriptorSets");
+        success = false;
+    }
+    vtbl.CreateDynamicViewportState = reinterpret_cast<PFN_vkCreateDynamicViewportState>(get_proc_addr(device, "vkCreateDynamicViewportState"));
+    if (UNLIKELY(!vtbl.CreateDynamicViewportState)) {
+        ALOGE("missing device proc: %s", "vkCreateDynamicViewportState");
+        success = false;
+    }
+    vtbl.DestroyDynamicViewportState = reinterpret_cast<PFN_vkDestroyDynamicViewportState>(get_proc_addr(device, "vkDestroyDynamicViewportState"));
+    if (UNLIKELY(!vtbl.DestroyDynamicViewportState)) {
+        ALOGE("missing device proc: %s", "vkDestroyDynamicViewportState");
+        success = false;
+    }
+    vtbl.CreateDynamicRasterState = reinterpret_cast<PFN_vkCreateDynamicRasterState>(get_proc_addr(device, "vkCreateDynamicRasterState"));
+    if (UNLIKELY(!vtbl.CreateDynamicRasterState)) {
+        ALOGE("missing device proc: %s", "vkCreateDynamicRasterState");
+        success = false;
+    }
+    vtbl.DestroyDynamicRasterState = reinterpret_cast<PFN_vkDestroyDynamicRasterState>(get_proc_addr(device, "vkDestroyDynamicRasterState"));
+    if (UNLIKELY(!vtbl.DestroyDynamicRasterState)) {
+        ALOGE("missing device proc: %s", "vkDestroyDynamicRasterState");
+        success = false;
+    }
+    vtbl.CreateDynamicColorBlendState = reinterpret_cast<PFN_vkCreateDynamicColorBlendState>(get_proc_addr(device, "vkCreateDynamicColorBlendState"));
+    if (UNLIKELY(!vtbl.CreateDynamicColorBlendState)) {
+        ALOGE("missing device proc: %s", "vkCreateDynamicColorBlendState");
+        success = false;
+    }
+    vtbl.DestroyDynamicColorBlendState = reinterpret_cast<PFN_vkDestroyDynamicColorBlendState>(get_proc_addr(device, "vkDestroyDynamicColorBlendState"));
+    if (UNLIKELY(!vtbl.DestroyDynamicColorBlendState)) {
+        ALOGE("missing device proc: %s", "vkDestroyDynamicColorBlendState");
+        success = false;
+    }
+    vtbl.CreateDynamicDepthStencilState = reinterpret_cast<PFN_vkCreateDynamicDepthStencilState>(get_proc_addr(device, "vkCreateDynamicDepthStencilState"));
+    if (UNLIKELY(!vtbl.CreateDynamicDepthStencilState)) {
+        ALOGE("missing device proc: %s", "vkCreateDynamicDepthStencilState");
+        success = false;
+    }
+    vtbl.DestroyDynamicDepthStencilState = reinterpret_cast<PFN_vkDestroyDynamicDepthStencilState>(get_proc_addr(device, "vkDestroyDynamicDepthStencilState"));
+    if (UNLIKELY(!vtbl.DestroyDynamicDepthStencilState)) {
+        ALOGE("missing device proc: %s", "vkDestroyDynamicDepthStencilState");
+        success = false;
+    }
+    vtbl.CreateFramebuffer = reinterpret_cast<PFN_vkCreateFramebuffer>(get_proc_addr(device, "vkCreateFramebuffer"));
+    if (UNLIKELY(!vtbl.CreateFramebuffer)) {
+        ALOGE("missing device proc: %s", "vkCreateFramebuffer");
+        success = false;
+    }
+    vtbl.DestroyFramebuffer = reinterpret_cast<PFN_vkDestroyFramebuffer>(get_proc_addr(device, "vkDestroyFramebuffer"));
+    if (UNLIKELY(!vtbl.DestroyFramebuffer)) {
+        ALOGE("missing device proc: %s", "vkDestroyFramebuffer");
+        success = false;
+    }
+    vtbl.CreateRenderPass = reinterpret_cast<PFN_vkCreateRenderPass>(get_proc_addr(device, "vkCreateRenderPass"));
+    if (UNLIKELY(!vtbl.CreateRenderPass)) {
+        ALOGE("missing device proc: %s", "vkCreateRenderPass");
+        success = false;
+    }
+    vtbl.DestroyRenderPass = reinterpret_cast<PFN_vkDestroyRenderPass>(get_proc_addr(device, "vkDestroyRenderPass"));
+    if (UNLIKELY(!vtbl.DestroyRenderPass)) {
+        ALOGE("missing device proc: %s", "vkDestroyRenderPass");
+        success = false;
+    }
+    vtbl.GetRenderAreaGranularity = reinterpret_cast<PFN_vkGetRenderAreaGranularity>(get_proc_addr(device, "vkGetRenderAreaGranularity"));
+    if (UNLIKELY(!vtbl.GetRenderAreaGranularity)) {
+        ALOGE("missing device proc: %s", "vkGetRenderAreaGranularity");
+        success = false;
+    }
+    vtbl.CreateCommandPool = reinterpret_cast<PFN_vkCreateCommandPool>(get_proc_addr(device, "vkCreateCommandPool"));
+    if (UNLIKELY(!vtbl.CreateCommandPool)) {
+        ALOGE("missing device proc: %s", "vkCreateCommandPool");
+        success = false;
+    }
+    vtbl.DestroyCommandPool = reinterpret_cast<PFN_vkDestroyCommandPool>(get_proc_addr(device, "vkDestroyCommandPool"));
+    if (UNLIKELY(!vtbl.DestroyCommandPool)) {
+        ALOGE("missing device proc: %s", "vkDestroyCommandPool");
+        success = false;
+    }
+    vtbl.ResetCommandPool = reinterpret_cast<PFN_vkResetCommandPool>(get_proc_addr(device, "vkResetCommandPool"));
+    if (UNLIKELY(!vtbl.ResetCommandPool)) {
+        ALOGE("missing device proc: %s", "vkResetCommandPool");
+        success = false;
+    }
+    vtbl.CreateCommandBuffer = reinterpret_cast<PFN_vkCreateCommandBuffer>(get_proc_addr(device, "vkCreateCommandBuffer"));
+    if (UNLIKELY(!vtbl.CreateCommandBuffer)) {
+        ALOGE("missing device proc: %s", "vkCreateCommandBuffer");
+        success = false;
+    }
+    vtbl.DestroyCommandBuffer = reinterpret_cast<PFN_vkDestroyCommandBuffer>(get_proc_addr(device, "vkDestroyCommandBuffer"));
+    if (UNLIKELY(!vtbl.DestroyCommandBuffer)) {
+        ALOGE("missing device proc: %s", "vkDestroyCommandBuffer");
+        success = false;
+    }
+    vtbl.BeginCommandBuffer = reinterpret_cast<PFN_vkBeginCommandBuffer>(get_proc_addr(device, "vkBeginCommandBuffer"));
+    if (UNLIKELY(!vtbl.BeginCommandBuffer)) {
+        ALOGE("missing device proc: %s", "vkBeginCommandBuffer");
+        success = false;
+    }
+    vtbl.EndCommandBuffer = reinterpret_cast<PFN_vkEndCommandBuffer>(get_proc_addr(device, "vkEndCommandBuffer"));
+    if (UNLIKELY(!vtbl.EndCommandBuffer)) {
+        ALOGE("missing device proc: %s", "vkEndCommandBuffer");
+        success = false;
+    }
+    vtbl.ResetCommandBuffer = reinterpret_cast<PFN_vkResetCommandBuffer>(get_proc_addr(device, "vkResetCommandBuffer"));
+    if (UNLIKELY(!vtbl.ResetCommandBuffer)) {
+        ALOGE("missing device proc: %s", "vkResetCommandBuffer");
+        success = false;
+    }
+    vtbl.CmdBindPipeline = reinterpret_cast<PFN_vkCmdBindPipeline>(get_proc_addr(device, "vkCmdBindPipeline"));
+    if (UNLIKELY(!vtbl.CmdBindPipeline)) {
+        ALOGE("missing device proc: %s", "vkCmdBindPipeline");
+        success = false;
+    }
+    vtbl.CmdBindDynamicViewportState = reinterpret_cast<PFN_vkCmdBindDynamicViewportState>(get_proc_addr(device, "vkCmdBindDynamicViewportState"));
+    if (UNLIKELY(!vtbl.CmdBindDynamicViewportState)) {
+        ALOGE("missing device proc: %s", "vkCmdBindDynamicViewportState");
+        success = false;
+    }
+    vtbl.CmdBindDynamicRasterState = reinterpret_cast<PFN_vkCmdBindDynamicRasterState>(get_proc_addr(device, "vkCmdBindDynamicRasterState"));
+    if (UNLIKELY(!vtbl.CmdBindDynamicRasterState)) {
+        ALOGE("missing device proc: %s", "vkCmdBindDynamicRasterState");
+        success = false;
+    }
+    vtbl.CmdBindDynamicColorBlendState = reinterpret_cast<PFN_vkCmdBindDynamicColorBlendState>(get_proc_addr(device, "vkCmdBindDynamicColorBlendState"));
+    if (UNLIKELY(!vtbl.CmdBindDynamicColorBlendState)) {
+        ALOGE("missing device proc: %s", "vkCmdBindDynamicColorBlendState");
+        success = false;
+    }
+    vtbl.CmdBindDynamicDepthStencilState = reinterpret_cast<PFN_vkCmdBindDynamicDepthStencilState>(get_proc_addr(device, "vkCmdBindDynamicDepthStencilState"));
+    if (UNLIKELY(!vtbl.CmdBindDynamicDepthStencilState)) {
+        ALOGE("missing device proc: %s", "vkCmdBindDynamicDepthStencilState");
+        success = false;
+    }
+    vtbl.CmdBindDescriptorSets = reinterpret_cast<PFN_vkCmdBindDescriptorSets>(get_proc_addr(device, "vkCmdBindDescriptorSets"));
+    if (UNLIKELY(!vtbl.CmdBindDescriptorSets)) {
+        ALOGE("missing device proc: %s", "vkCmdBindDescriptorSets");
+        success = false;
+    }
+    vtbl.CmdBindIndexBuffer = reinterpret_cast<PFN_vkCmdBindIndexBuffer>(get_proc_addr(device, "vkCmdBindIndexBuffer"));
+    if (UNLIKELY(!vtbl.CmdBindIndexBuffer)) {
+        ALOGE("missing device proc: %s", "vkCmdBindIndexBuffer");
+        success = false;
+    }
+    vtbl.CmdBindVertexBuffers = reinterpret_cast<PFN_vkCmdBindVertexBuffers>(get_proc_addr(device, "vkCmdBindVertexBuffers"));
+    if (UNLIKELY(!vtbl.CmdBindVertexBuffers)) {
+        ALOGE("missing device proc: %s", "vkCmdBindVertexBuffers");
+        success = false;
+    }
+    vtbl.CmdDraw = reinterpret_cast<PFN_vkCmdDraw>(get_proc_addr(device, "vkCmdDraw"));
+    if (UNLIKELY(!vtbl.CmdDraw)) {
+        ALOGE("missing device proc: %s", "vkCmdDraw");
+        success = false;
+    }
+    vtbl.CmdDrawIndexed = reinterpret_cast<PFN_vkCmdDrawIndexed>(get_proc_addr(device, "vkCmdDrawIndexed"));
+    if (UNLIKELY(!vtbl.CmdDrawIndexed)) {
+        ALOGE("missing device proc: %s", "vkCmdDrawIndexed");
+        success = false;
+    }
+    vtbl.CmdDrawIndirect = reinterpret_cast<PFN_vkCmdDrawIndirect>(get_proc_addr(device, "vkCmdDrawIndirect"));
+    if (UNLIKELY(!vtbl.CmdDrawIndirect)) {
+        ALOGE("missing device proc: %s", "vkCmdDrawIndirect");
+        success = false;
+    }
+    vtbl.CmdDrawIndexedIndirect = reinterpret_cast<PFN_vkCmdDrawIndexedIndirect>(get_proc_addr(device, "vkCmdDrawIndexedIndirect"));
+    if (UNLIKELY(!vtbl.CmdDrawIndexedIndirect)) {
+        ALOGE("missing device proc: %s", "vkCmdDrawIndexedIndirect");
+        success = false;
+    }
+    vtbl.CmdDispatch = reinterpret_cast<PFN_vkCmdDispatch>(get_proc_addr(device, "vkCmdDispatch"));
+    if (UNLIKELY(!vtbl.CmdDispatch)) {
+        ALOGE("missing device proc: %s", "vkCmdDispatch");
+        success = false;
+    }
+    vtbl.CmdDispatchIndirect = reinterpret_cast<PFN_vkCmdDispatchIndirect>(get_proc_addr(device, "vkCmdDispatchIndirect"));
+    if (UNLIKELY(!vtbl.CmdDispatchIndirect)) {
+        ALOGE("missing device proc: %s", "vkCmdDispatchIndirect");
+        success = false;
+    }
+    vtbl.CmdCopyBuffer = reinterpret_cast<PFN_vkCmdCopyBuffer>(get_proc_addr(device, "vkCmdCopyBuffer"));
+    if (UNLIKELY(!vtbl.CmdCopyBuffer)) {
+        ALOGE("missing device proc: %s", "vkCmdCopyBuffer");
+        success = false;
+    }
+    vtbl.CmdCopyImage = reinterpret_cast<PFN_vkCmdCopyImage>(get_proc_addr(device, "vkCmdCopyImage"));
+    if (UNLIKELY(!vtbl.CmdCopyImage)) {
+        ALOGE("missing device proc: %s", "vkCmdCopyImage");
+        success = false;
+    }
+    vtbl.CmdBlitImage = reinterpret_cast<PFN_vkCmdBlitImage>(get_proc_addr(device, "vkCmdBlitImage"));
+    if (UNLIKELY(!vtbl.CmdBlitImage)) {
+        ALOGE("missing device proc: %s", "vkCmdBlitImage");
+        success = false;
+    }
+    vtbl.CmdCopyBufferToImage = reinterpret_cast<PFN_vkCmdCopyBufferToImage>(get_proc_addr(device, "vkCmdCopyBufferToImage"));
+    if (UNLIKELY(!vtbl.CmdCopyBufferToImage)) {
+        ALOGE("missing device proc: %s", "vkCmdCopyBufferToImage");
+        success = false;
+    }
+    vtbl.CmdCopyImageToBuffer = reinterpret_cast<PFN_vkCmdCopyImageToBuffer>(get_proc_addr(device, "vkCmdCopyImageToBuffer"));
+    if (UNLIKELY(!vtbl.CmdCopyImageToBuffer)) {
+        ALOGE("missing device proc: %s", "vkCmdCopyImageToBuffer");
+        success = false;
+    }
+    vtbl.CmdUpdateBuffer = reinterpret_cast<PFN_vkCmdUpdateBuffer>(get_proc_addr(device, "vkCmdUpdateBuffer"));
+    if (UNLIKELY(!vtbl.CmdUpdateBuffer)) {
+        ALOGE("missing device proc: %s", "vkCmdUpdateBuffer");
+        success = false;
+    }
+    vtbl.CmdFillBuffer = reinterpret_cast<PFN_vkCmdFillBuffer>(get_proc_addr(device, "vkCmdFillBuffer"));
+    if (UNLIKELY(!vtbl.CmdFillBuffer)) {
+        ALOGE("missing device proc: %s", "vkCmdFillBuffer");
+        success = false;
+    }
+    vtbl.CmdClearColorImage = reinterpret_cast<PFN_vkCmdClearColorImage>(get_proc_addr(device, "vkCmdClearColorImage"));
+    if (UNLIKELY(!vtbl.CmdClearColorImage)) {
+        ALOGE("missing device proc: %s", "vkCmdClearColorImage");
+        success = false;
+    }
+    vtbl.CmdClearDepthStencilImage = reinterpret_cast<PFN_vkCmdClearDepthStencilImage>(get_proc_addr(device, "vkCmdClearDepthStencilImage"));
+    if (UNLIKELY(!vtbl.CmdClearDepthStencilImage)) {
+        ALOGE("missing device proc: %s", "vkCmdClearDepthStencilImage");
+        success = false;
+    }
+    vtbl.CmdClearColorAttachment = reinterpret_cast<PFN_vkCmdClearColorAttachment>(get_proc_addr(device, "vkCmdClearColorAttachment"));
+    if (UNLIKELY(!vtbl.CmdClearColorAttachment)) {
+        ALOGE("missing device proc: %s", "vkCmdClearColorAttachment");
+        success = false;
+    }
+    vtbl.CmdClearDepthStencilAttachment = reinterpret_cast<PFN_vkCmdClearDepthStencilAttachment>(get_proc_addr(device, "vkCmdClearDepthStencilAttachment"));
+    if (UNLIKELY(!vtbl.CmdClearDepthStencilAttachment)) {
+        ALOGE("missing device proc: %s", "vkCmdClearDepthStencilAttachment");
+        success = false;
+    }
+    vtbl.CmdResolveImage = reinterpret_cast<PFN_vkCmdResolveImage>(get_proc_addr(device, "vkCmdResolveImage"));
+    if (UNLIKELY(!vtbl.CmdResolveImage)) {
+        ALOGE("missing device proc: %s", "vkCmdResolveImage");
+        success = false;
+    }
+    vtbl.CmdSetEvent = reinterpret_cast<PFN_vkCmdSetEvent>(get_proc_addr(device, "vkCmdSetEvent"));
+    if (UNLIKELY(!vtbl.CmdSetEvent)) {
+        ALOGE("missing device proc: %s", "vkCmdSetEvent");
+        success = false;
+    }
+    vtbl.CmdResetEvent = reinterpret_cast<PFN_vkCmdResetEvent>(get_proc_addr(device, "vkCmdResetEvent"));
+    if (UNLIKELY(!vtbl.CmdResetEvent)) {
+        ALOGE("missing device proc: %s", "vkCmdResetEvent");
+        success = false;
+    }
+    vtbl.CmdWaitEvents = reinterpret_cast<PFN_vkCmdWaitEvents>(get_proc_addr(device, "vkCmdWaitEvents"));
+    if (UNLIKELY(!vtbl.CmdWaitEvents)) {
+        ALOGE("missing device proc: %s", "vkCmdWaitEvents");
+        success = false;
+    }
+    vtbl.CmdPipelineBarrier = reinterpret_cast<PFN_vkCmdPipelineBarrier>(get_proc_addr(device, "vkCmdPipelineBarrier"));
+    if (UNLIKELY(!vtbl.CmdPipelineBarrier)) {
+        ALOGE("missing device proc: %s", "vkCmdPipelineBarrier");
+        success = false;
+    }
+    vtbl.CmdBeginQuery = reinterpret_cast<PFN_vkCmdBeginQuery>(get_proc_addr(device, "vkCmdBeginQuery"));
+    if (UNLIKELY(!vtbl.CmdBeginQuery)) {
+        ALOGE("missing device proc: %s", "vkCmdBeginQuery");
+        success = false;
+    }
+    vtbl.CmdEndQuery = reinterpret_cast<PFN_vkCmdEndQuery>(get_proc_addr(device, "vkCmdEndQuery"));
+    if (UNLIKELY(!vtbl.CmdEndQuery)) {
+        ALOGE("missing device proc: %s", "vkCmdEndQuery");
+        success = false;
+    }
+    vtbl.CmdResetQueryPool = reinterpret_cast<PFN_vkCmdResetQueryPool>(get_proc_addr(device, "vkCmdResetQueryPool"));
+    if (UNLIKELY(!vtbl.CmdResetQueryPool)) {
+        ALOGE("missing device proc: %s", "vkCmdResetQueryPool");
+        success = false;
+    }
+    vtbl.CmdWriteTimestamp = reinterpret_cast<PFN_vkCmdWriteTimestamp>(get_proc_addr(device, "vkCmdWriteTimestamp"));
+    if (UNLIKELY(!vtbl.CmdWriteTimestamp)) {
+        ALOGE("missing device proc: %s", "vkCmdWriteTimestamp");
+        success = false;
+    }
+    vtbl.CmdCopyQueryPoolResults = reinterpret_cast<PFN_vkCmdCopyQueryPoolResults>(get_proc_addr(device, "vkCmdCopyQueryPoolResults"));
+    if (UNLIKELY(!vtbl.CmdCopyQueryPoolResults)) {
+        ALOGE("missing device proc: %s", "vkCmdCopyQueryPoolResults");
+        success = false;
+    }
+    vtbl.CmdPushConstants = reinterpret_cast<PFN_vkCmdPushConstants>(get_proc_addr(device, "vkCmdPushConstants"));
+    if (UNLIKELY(!vtbl.CmdPushConstants)) {
+        ALOGE("missing device proc: %s", "vkCmdPushConstants");
+        success = false;
+    }
+    vtbl.CmdBeginRenderPass = reinterpret_cast<PFN_vkCmdBeginRenderPass>(get_proc_addr(device, "vkCmdBeginRenderPass"));
+    if (UNLIKELY(!vtbl.CmdBeginRenderPass)) {
+        ALOGE("missing device proc: %s", "vkCmdBeginRenderPass");
+        success = false;
+    }
+    vtbl.CmdNextSubpass = reinterpret_cast<PFN_vkCmdNextSubpass>(get_proc_addr(device, "vkCmdNextSubpass"));
+    if (UNLIKELY(!vtbl.CmdNextSubpass)) {
+        ALOGE("missing device proc: %s", "vkCmdNextSubpass");
+        success = false;
+    }
+    vtbl.CmdEndRenderPass = reinterpret_cast<PFN_vkCmdEndRenderPass>(get_proc_addr(device, "vkCmdEndRenderPass"));
+    if (UNLIKELY(!vtbl.CmdEndRenderPass)) {
+        ALOGE("missing device proc: %s", "vkCmdEndRenderPass");
+        success = false;
+    }
+    vtbl.CmdExecuteCommands = reinterpret_cast<PFN_vkCmdExecuteCommands>(get_proc_addr(device, "vkCmdExecuteCommands"));
+    if (UNLIKELY(!vtbl.CmdExecuteCommands)) {
+        ALOGE("missing device proc: %s", "vkCmdExecuteCommands");
+        success = false;
+    }
+    // clang-format on
+    return success;
+}
+
+}  // namespace vulkan
diff --git a/vulkan/libvulkan/get_proc_addr.cpp.tmpl b/vulkan/libvulkan/get_proc_addr.cpp.tmpl
new file mode 100644
index 0000000..d4ced56
--- /dev/null
+++ b/vulkan/libvulkan/get_proc_addr.cpp.tmpl
@@ -0,0 +1,180 @@
+{{Include "../api/templates/vulkan_common.tmpl"}}
+{{Global "clang-format" (Strings "clang-format" "-style=file")}}
+{{Macro "DefineGlobals" $}}
+{{$ | Macro "get_proc_addr.cpp" | Format (Global "clang-format") | Write "get_proc_addr.cpp"}}
+
+
+{{/*
+-------------------------------------------------------------------------------
+  Entry point
+-------------------------------------------------------------------------------
+*/}}
+{{define "get_proc_addr.cpp"}}
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */

+// This file is generated. Do not edit manually!
+// To regenerate: $ apic template ../api/vulkan.api get_proc_addr.cpp.tmpl
+// Requires apic from https://android.googlesource.com/platform/tools/gpu/.

+#include <algorithm>
+#include <log/log.h>
+#include "loader.h"
+using namespace vulkan;

+#define UNLIKELY(expr) __builtin_expect((expr), 0)

+namespace {

+struct NameProcEntry {
+    const char* name;
+    PFN_vkVoidFunction proc;
+};

+struct NameOffsetEntry {
+    const char* name;
+    size_t offset;
+};

+template <typename TEntry, size_t N>
+const TEntry* FindProcEntry(const TEntry(&table)[N], const char* name) {
+    auto entry = std::lower_bound(
+        table, table + N, name,
+        [](const TEntry& e, const char* n) { return strcmp(e.name, n) < 0; });
+    if (entry != (table + N) && strcmp(entry->name, name) == 0)
+        return entry;
+    return nullptr;
+}

+const NameProcEntry kInstanceProcTbl[] = {«
+  // clang-format off
+  {{range $f := SortBy (AllCommands $) "FunctionName"}}
+    {{if eq (Macro "Vtbl" $f) "Instance"}}
+      {"{{Macro "FunctionName" $f}}", reinterpret_cast<PFN_vkVoidFunction>({{Macro "FunctionName" $f}})},
+    {{end}}
+  {{end}}
+  // clang-format on
+»};

+const NameProcEntry kDeviceProcTbl[] = {«
+  // clang-format off
+  {{range $f := SortBy (AllCommands $) "FunctionName"}}
+    {{if eq (Macro "Vtbl" $f) "Device"}}
+      {"{{Macro "FunctionName" $f}}", reinterpret_cast<PFN_vkVoidFunction>({{Macro "FunctionName" $f}})},
+    {{end}}
+  {{end}}
+  // clang-format on
+»};

+const NameOffsetEntry kInstanceOffsetTbl[] = {«
+  // clang-format off
+  {{range $f := SortBy (AllCommands $) "FunctionName"}}
+    {{if eq (Macro "Vtbl" $f) "Instance"}}
+      {"{{Macro "FunctionName" $f}}", offsetof(InstanceVtbl, {{TrimPrefix "vk" (Macro "FunctionName" $f)}})},
+    {{end}}
+  {{end}}
+  // clang-format on
+»};

+const NameOffsetEntry kDeviceOffsetTbl[] = {«
+  // clang-format off
+  {{range $f := SortBy (AllCommands $) "FunctionName"}}
+    {{if eq (Macro "Vtbl" $f) "Device"}}
+      {"{{Macro "FunctionName" $f}}", offsetof(DeviceVtbl, {{TrimPrefix "vk" (Macro "FunctionName" $f)}})},
+    {{end}}
+  {{end}}
+  // clang-format on
+»};

+} // namespace

+namespace vulkan {

+PFN_vkVoidFunction GetGlobalInstanceProcAddr(const char* name) {
+    if (strcmp(name, "vkGetDeviceProcAddr") == 0)
+        return reinterpret_cast<PFN_vkVoidFunction>(vkGetDeviceProcAddr);
+    const NameProcEntry* entry = FindProcEntry(kInstanceProcTbl, name);
+    return entry ? entry->proc : nullptr;
+}

+PFN_vkVoidFunction GetGlobalDeviceProcAddr(const char* name) {
+    const NameProcEntry* entry = FindProcEntry(kDeviceProcTbl, name);
+    return entry ? entry->proc : nullptr;
+}

+PFN_vkVoidFunction GetSpecificInstanceProcAddr(const InstanceVtbl* vtbl,
+                                               const char* name) {
+    const NameOffsetEntry* entry = FindProcEntry(kInstanceOffsetTbl, name);
+    if (!entry)
+        return nullptr;
+    const unsigned char* base = reinterpret_cast<const unsigned char*>(vtbl);
+    return reinterpret_cast<PFN_vkVoidFunction>(
+        const_cast<unsigned char*>(base) + entry->offset);
+}

+PFN_vkVoidFunction GetSpecificDeviceProcAddr(const DeviceVtbl* vtbl,
+                                             const char* name) {
+    const NameOffsetEntry* entry = FindProcEntry(kDeviceOffsetTbl, name);
+    if (!entry)
+        return nullptr;
+    const unsigned char* base = reinterpret_cast<const unsigned char*>(vtbl);
+    return reinterpret_cast<PFN_vkVoidFunction>(
+        const_cast<unsigned char*>(base) + entry->offset);
+}

+bool LoadInstanceVtbl(VkInstance instance,
+                      PFN_vkGetInstanceProcAddr get_proc_addr,
+                      InstanceVtbl& vtbl) {«
+    bool success = true;
+    // clang-format off
+    {{range $f := AllCommands $}}
+      {{if eq (Macro "Vtbl" $f) "Instance"}}
+    vtbl.{{TrimPrefix "vk" (Macro "FunctionName" $f)}} = §
+        reinterpret_cast<{{Macro "FunctionPtrName" $f}}>(§
+            get_proc_addr(instance, "{{Macro "FunctionName" $f}}"));
+    if (UNLIKELY(!vtbl.{{TrimPrefix "vk" (Macro "FunctionName" $f)}})) {
+        ALOGE("missing instance proc: %s", "{{Macro "FunctionName" $f}}");
+        success = false;
+    }
+      {{end}}
+    {{end}}
+    // clang-format on
+    return success;
+»}

+bool LoadDeviceVtbl(VkDevice device,
+                    PFN_vkGetDeviceProcAddr get_proc_addr,
+                    DeviceVtbl& vtbl) {«
+    bool success = true;
+    // clang-format off
+    {{range $f := AllCommands $}}
+      {{if eq (Macro "Vtbl" $f) "Device"}}
+    vtbl.{{TrimPrefix "vk" (Macro "FunctionName" $f)}} = §
+        reinterpret_cast<{{Macro "FunctionPtrName" $f}}>(§
+            get_proc_addr(device, "{{Macro "FunctionName" $f}}"));
+    if (UNLIKELY(!vtbl.{{TrimPrefix "vk" (Macro "FunctionName" $f)}})) {
+        ALOGE("missing device proc: %s", "{{Macro "FunctionName" $f}}");
+        success = false;
+    }
+      {{end}}
+    {{end}}
+    // clang-format on
+    return success;
+»}

+} // namespace vulkan

+{{end}}
diff --git a/vulkan/libvulkan/loader.cpp b/vulkan/libvulkan/loader.cpp
new file mode 100644
index 0000000..a99e90a
--- /dev/null
+++ b/vulkan/libvulkan/loader.cpp
@@ -0,0 +1,532 @@
+// module header
+#include "loader.h"
+// standard C headers
+#include <inttypes.h>
+#include <malloc.h>
+#include <pthread.h>
+#include <string.h>
+// standard C++ headers
+#include <algorithm>
+#include <mutex>
+// platform/library headers
+#include <hardware/hwvulkan.h>
+#include <log/log.h>
+
+using namespace vulkan;
+
+static const uint32_t kMaxPhysicalDevices = 4;
+
+struct VkInstance_T {
+    VkInstance_T(const VkAllocCallbacks* alloc_callbacks)
+        : vtbl(&vtbl_storage), alloc(alloc_callbacks), num_physical_devices(0) {
+        memset(&vtbl_storage, 0, sizeof(vtbl_storage));
+        memset(physical_devices, 0, sizeof(physical_devices));
+        memset(&drv.vtbl, 0, sizeof(drv.vtbl));
+        drv.GetDeviceProcAddr = nullptr;
+        drv.num_physical_devices = 0;
+    }
+
+    InstanceVtbl* vtbl;
+    InstanceVtbl vtbl_storage;
+
+    const VkAllocCallbacks* alloc;
+    uint32_t num_physical_devices;
+    VkPhysicalDevice physical_devices[kMaxPhysicalDevices];
+
+    struct Driver {
+        // Pointers to driver entry points. Used explicitly by the loader; not
+        // set as the dispatch table for any objects.
+        InstanceVtbl vtbl;
+
+        // Pointer to the driver's get_device_proc_addr, must be valid for any
+        // of the driver's physical devices. Not part of the InstanceVtbl since
+        // it's not an Instance/PhysicalDevice function.
+        PFN_vkGetDeviceProcAddr GetDeviceProcAddr;
+
+        // Number of physical devices owned by this driver.
+        uint32_t num_physical_devices;
+    } drv;  // may eventually be an array
+};
+
+// -----------------------------------------------------------------------------
+
+namespace {
+
+typedef VkInstance_T Instance;
+
+struct Device {
+    Device(const VkAllocCallbacks* alloc_callbacks) : alloc(alloc_callbacks) {
+        memset(&vtbl_storage, 0, sizeof(vtbl_storage));
+        vtbl_storage.device = this;
+    }
+    DeviceVtbl vtbl_storage;
+    const VkAllocCallbacks* alloc;
+};
+
+// -----------------------------------------------------------------------------
+// Utility Code
+
+inline const InstanceVtbl* GetVtbl(VkPhysicalDevice physicalDevice) {
+    return *reinterpret_cast<InstanceVtbl**>(physicalDevice);
+}
+
+inline const DeviceVtbl* GetVtbl(VkDevice device) {
+    return *reinterpret_cast<DeviceVtbl**>(device);
+}
+
+void* DefaultAlloc(void*, size_t size, size_t alignment, VkSystemAllocType) {
+    return memalign(alignment, size);
+}
+
+void DefaultFree(void*, void* pMem) {
+    free(pMem);
+}
+
+const VkAllocCallbacks kDefaultAllocCallbacks = {
+    .pUserData = nullptr,
+    .pfnAlloc = DefaultAlloc,
+    .pfnFree = DefaultFree,
+};
+
+hwvulkan_device_t* g_hwdevice;
+bool EnsureInitialized() {
+    static std::once_flag once_flag;
+    static const hwvulkan_module_t* module;
+
+    std::call_once(once_flag, []() {
+        int result;
+        result = hw_get_module("vulkan",
+                               reinterpret_cast<const hw_module_t**>(&module));
+        if (result != 0) {
+            ALOGE("failed to load vulkan hal: %s (%d)", strerror(-result),
+                  result);
+            return;
+        }
+        result = module->common.methods->open(
+            &module->common, HWVULKAN_DEVICE_0,
+            reinterpret_cast<hw_device_t**>(&g_hwdevice));
+        if (result != 0) {
+            ALOGE("failed to open vulkan driver: %s (%d)", strerror(-result),
+                  result);
+            module = nullptr;
+            return;
+        }
+    });
+
+    return module != nullptr && g_hwdevice != nullptr;
+}
+
+void DestroyDevice(Device* device) {
+    const VkAllocCallbacks* alloc = device->alloc;
+    device->~Device();
+    alloc->pfnFree(alloc->pUserData, device);
+}
+
+// -----------------------------------------------------------------------------
+// "Bottom" functions. These are called at the end of the instance dispatch
+// chain.
+
+VkResult DestroyInstanceBottom(VkInstance instance) {
+    // These checks allow us to call DestroyInstanceBottom from any error path
+    // in CreateInstanceBottom, before the driver instance is fully initialized.
+    if (instance->drv.vtbl.instance != VK_NULL_HANDLE &&
+        instance->drv.vtbl.DestroyInstance) {
+        instance->drv.vtbl.DestroyInstance(instance->drv.vtbl.instance);
+    }
+    const VkAllocCallbacks* alloc = instance->alloc;
+    instance->~VkInstance_T();
+    alloc->pfnFree(alloc->pUserData, instance);
+    return VK_SUCCESS;
+}
+
+VkResult CreateInstanceBottom(const VkInstanceCreateInfo* create_info,
+                              VkInstance* instance_ptr) {
+    Instance* instance = *instance_ptr;
+    VkResult result;
+
+    result =
+        g_hwdevice->CreateInstance(create_info, &instance->drv.vtbl.instance);
+    if (result != VK_SUCCESS) {
+        DestroyInstanceBottom(instance);
+        return result;
+    }
+
+    if (!LoadInstanceVtbl(instance->drv.vtbl.instance,
+                          g_hwdevice->GetInstanceProcAddr,
+                          instance->drv.vtbl)) {
+        DestroyInstanceBottom(instance);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+
+    // vkGetDeviceProcAddr has a bootstrapping problem. We require that it be
+    // queryable from the Instance, and that the resulting function work for any
+    // VkDevice created from the instance.
+    instance->drv.GetDeviceProcAddr = reinterpret_cast<PFN_vkGetDeviceProcAddr>(
+        g_hwdevice->GetInstanceProcAddr(instance->drv.vtbl.instance,
+                                        "vkGetDeviceProcAddr"));
+    if (!instance->drv.GetDeviceProcAddr) {
+        ALOGE("missing instance proc: \"%s\"", "vkGetDeviceProcAddr");
+        DestroyInstanceBottom(instance);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+
+    hwvulkan_dispatch_t* dispatch =
+        reinterpret_cast<hwvulkan_dispatch_t*>(instance->drv.vtbl.instance);
+    if (dispatch->magic == HWVULKAN_DISPATCH_MAGIC) {
+        // Skip setting dispatch->vtbl on the driver instance handle, since we
+        // never intentionally call through it; we go through Instance::drv.vtbl
+        // instead.
+    } else {
+        ALOGE("invalid VkInstance dispatch magic: 0x%" PRIxPTR,
+              dispatch->magic);
+        DestroyInstanceBottom(instance);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+
+    uint32_t num_physical_devices = 0;
+    result = instance->drv.vtbl.EnumeratePhysicalDevices(
+        instance->drv.vtbl.instance, &num_physical_devices, nullptr);
+    if (result != VK_SUCCESS) {
+        DestroyInstanceBottom(instance);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+    num_physical_devices = std::min(num_physical_devices, kMaxPhysicalDevices);
+    result = instance->drv.vtbl.EnumeratePhysicalDevices(
+        instance->drv.vtbl.instance, &num_physical_devices,
+        instance->physical_devices);
+    if (result != VK_SUCCESS) {
+        DestroyInstanceBottom(instance);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+    for (uint32_t i = 0; i < num_physical_devices; i++) {
+        dispatch = reinterpret_cast<hwvulkan_dispatch_t*>(
+            instance->physical_devices[i]);
+        if (dispatch->magic != HWVULKAN_DISPATCH_MAGIC) {
+            ALOGE("invalid VkPhysicalDevice dispatch magic: 0x%" PRIxPTR,
+                  dispatch->magic);
+            DestroyInstanceBottom(instance);
+            return VK_ERROR_INITIALIZATION_FAILED;
+        }
+        dispatch->vtbl = instance->vtbl;
+    }
+    instance->drv.num_physical_devices = num_physical_devices;
+
+    instance->num_physical_devices = instance->drv.num_physical_devices;
+    return VK_SUCCESS;
+}
+
+VkResult EnumeratePhysicalDevicesBottom(VkInstance instance,
+                                        uint32_t* pdev_count,
+                                        VkPhysicalDevice* pdevs) {
+    uint32_t count = instance->num_physical_devices;
+    if (pdevs) {
+        count = std::min(count, *pdev_count);
+        std::copy(instance->physical_devices,
+                  instance->physical_devices + count, pdevs);
+    }
+    *pdev_count = count;
+    return VK_SUCCESS;
+}
+
+VkResult GetPhysicalDeviceFeaturesBottom(VkPhysicalDevice pdev,
+                                         VkPhysicalDeviceFeatures* features) {
+    return GetVtbl(pdev)
+        ->instance->drv.vtbl.GetPhysicalDeviceFeatures(pdev, features);
+}
+
+VkResult GetPhysicalDeviceFormatPropertiesBottom(
+    VkPhysicalDevice pdev,
+    VkFormat format,
+    VkFormatProperties* properties) {
+    return GetVtbl(pdev)->instance->drv.vtbl.GetPhysicalDeviceFormatProperties(
+        pdev, format, properties);
+}
+
+VkResult GetPhysicalDeviceImageFormatPropertiesBottom(
+    VkPhysicalDevice pdev,
+    VkFormat format,
+    VkImageType type,
+    VkImageTiling tiling,
+    VkImageUsageFlags usage,
+    VkImageFormatProperties* properties) {
+    return GetVtbl(pdev)
+        ->instance->drv.vtbl.GetPhysicalDeviceImageFormatProperties(
+            pdev, format, type, tiling, usage, properties);
+}
+
+VkResult GetPhysicalDeviceLimitsBottom(VkPhysicalDevice pdev,
+                                       VkPhysicalDeviceLimits* limits) {
+    return GetVtbl(pdev)
+        ->instance->drv.vtbl.GetPhysicalDeviceLimits(pdev, limits);
+}
+
+VkResult GetPhysicalDevicePropertiesBottom(
+    VkPhysicalDevice pdev,
+    VkPhysicalDeviceProperties* properties) {
+    return GetVtbl(pdev)
+        ->instance->drv.vtbl.GetPhysicalDeviceProperties(pdev, properties);
+}
+
+VkResult GetPhysicalDeviceQueueCountBottom(VkPhysicalDevice pdev,
+                                           uint32_t* count) {
+    return GetVtbl(pdev)
+        ->instance->drv.vtbl.GetPhysicalDeviceQueueCount(pdev, count);
+}
+
+VkResult GetPhysicalDeviceQueuePropertiesBottom(
+    VkPhysicalDevice pdev,
+    uint32_t count,
+    VkPhysicalDeviceQueueProperties* properties) {
+    return GetVtbl(pdev)->instance->drv.vtbl.GetPhysicalDeviceQueueProperties(
+        pdev, count, properties);
+}
+
+VkResult GetPhysicalDeviceMemoryPropertiesBottom(
+    VkPhysicalDevice pdev,
+    VkPhysicalDeviceMemoryProperties* properties) {
+    return GetVtbl(pdev)->instance->drv.vtbl.GetPhysicalDeviceMemoryProperties(
+        pdev, properties);
+}
+
+VkResult CreateDeviceBottom(VkPhysicalDevice pdev,
+                            const VkDeviceCreateInfo* create_info,
+                            VkDevice* out_device) {
+    const Instance& instance = *static_cast<Instance*>(GetVtbl(pdev)->instance);
+    VkResult result;
+
+    void* mem = instance.alloc->pfnAlloc(instance.alloc->pUserData,
+                                         sizeof(Device), alignof(Device),
+                                         VK_SYSTEM_ALLOC_TYPE_API_OBJECT);
+    if (!mem)
+        return VK_ERROR_OUT_OF_HOST_MEMORY;
+    Device* device = new (mem) Device(instance.alloc);
+
+    VkDevice drv_device;
+    result = instance.drv.vtbl.CreateDevice(pdev, create_info, &drv_device);
+    if (result != VK_SUCCESS) {
+        DestroyDevice(device);
+        return result;
+    }
+
+    if (!LoadDeviceVtbl(drv_device, instance.drv.GetDeviceProcAddr,
+                        device->vtbl_storage)) {
+        if (device->vtbl_storage.DestroyDevice)
+            device->vtbl_storage.DestroyDevice(drv_device);
+        DestroyDevice(device);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+
+    hwvulkan_dispatch_t* dispatch =
+        reinterpret_cast<hwvulkan_dispatch_t*>(drv_device);
+    if (dispatch->magic != HWVULKAN_DISPATCH_MAGIC) {
+        ALOGE("invalid VkDevice dispatch magic: 0x%" PRIxPTR, dispatch->magic);
+        device->vtbl_storage.DestroyDevice(drv_device);
+        DestroyDevice(device);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+    dispatch->vtbl = &device->vtbl_storage;
+
+    // TODO: insert device layer entry points into device->vtbl_storage here?
+
+    *out_device = drv_device;
+    return VK_SUCCESS;
+}
+
+VkResult GetPhysicalDeviceExtensionPropertiesBottom(
+    VkPhysicalDevice pdev,
+    const char* layer_name,
+    uint32_t* properties_count,
+    VkExtensionProperties* properties) {
+    // TODO: what are we supposed to do with layer_name here?
+    return GetVtbl(pdev)
+        ->instance->drv.vtbl.GetPhysicalDeviceExtensionProperties(
+            pdev, layer_name, properties_count, properties);
+}
+
+VkResult GetPhysicalDeviceLayerPropertiesBottom(VkPhysicalDevice pdev,
+                                                uint32_t* properties_count,
+                                                VkLayerProperties* properties) {
+    return GetVtbl(pdev)->instance->drv.vtbl.GetPhysicalDeviceLayerProperties(
+        pdev, properties_count, properties);
+}
+
+VkResult GetPhysicalDeviceSparseImageFormatPropertiesBottom(
+    VkPhysicalDevice pdev,
+    VkFormat format,
+    VkImageType type,
+    uint32_t samples,
+    VkImageUsageFlags usage,
+    VkImageTiling tiling,
+    uint32_t* properties_count,
+    VkSparseImageFormatProperties* properties) {
+    return GetVtbl(pdev)
+        ->instance->drv.vtbl.GetPhysicalDeviceSparseImageFormatProperties(
+            pdev, format, type, samples, usage, tiling, properties_count,
+            properties);
+}
+
+PFN_vkVoidFunction GetInstanceProcAddrBottom(VkInstance, const char*);
+
+const InstanceVtbl kBottomInstanceFunctions = {
+    // clang-format off
+    .instance = nullptr,
+    .CreateInstance = CreateInstanceBottom,
+    .DestroyInstance = DestroyInstanceBottom,
+    .GetInstanceProcAddr = GetInstanceProcAddrBottom,
+    .EnumeratePhysicalDevices = EnumeratePhysicalDevicesBottom,
+    .GetPhysicalDeviceFeatures = GetPhysicalDeviceFeaturesBottom,
+    .GetPhysicalDeviceFormatProperties = GetPhysicalDeviceFormatPropertiesBottom,
+    .GetPhysicalDeviceImageFormatProperties = GetPhysicalDeviceImageFormatPropertiesBottom,
+    .GetPhysicalDeviceLimits = GetPhysicalDeviceLimitsBottom,
+    .GetPhysicalDeviceProperties = GetPhysicalDevicePropertiesBottom,
+    .GetPhysicalDeviceQueueCount = GetPhysicalDeviceQueueCountBottom,
+    .GetPhysicalDeviceQueueProperties = GetPhysicalDeviceQueuePropertiesBottom,
+    .GetPhysicalDeviceMemoryProperties = GetPhysicalDeviceMemoryPropertiesBottom,
+    .CreateDevice = CreateDeviceBottom,
+    .GetPhysicalDeviceExtensionProperties = GetPhysicalDeviceExtensionPropertiesBottom,
+    .GetPhysicalDeviceLayerProperties = GetPhysicalDeviceLayerPropertiesBottom,
+    .GetPhysicalDeviceSparseImageFormatProperties = GetPhysicalDeviceSparseImageFormatPropertiesBottom,
+    // clang-format on
+};
+
+PFN_vkVoidFunction GetInstanceProcAddrBottom(VkInstance, const char* name) {
+    // The bottom GetInstanceProcAddr is only called by the innermost layer,
+    // when there is one, when it initializes its own dispatch table.
+    return GetSpecificInstanceProcAddr(&kBottomInstanceFunctions, name);
+}
+
+}  // namespace
+
+// -----------------------------------------------------------------------------
+// Global functions. These are called directly from the loader entry points,
+// without going through a dispatch table.
+
+namespace vulkan {
+
+VkResult GetGlobalExtensionProperties(const char* /*layer_name*/,
+                                      uint32_t* count,
+                                      VkExtensionProperties* /*properties*/) {
+    if (!count)
+        return VK_ERROR_INVALID_POINTER;
+    if (!EnsureInitialized())
+        return VK_ERROR_UNAVAILABLE;
+
+    // TODO: not yet implemented
+    ALOGW("vkGetGlobalExtensionProperties not implemented");
+
+    *count = 0;
+    return VK_SUCCESS;
+}
+
+VkResult GetGlobalLayerProperties(uint32_t* count,
+                                  VkLayerProperties* /*properties*/) {
+    if (!count)
+        return VK_ERROR_INVALID_POINTER;
+    if (!EnsureInitialized())
+        return VK_ERROR_UNAVAILABLE;
+
+    // TODO: not yet implemented
+    ALOGW("vkGetGlobalLayerProperties not implemented");
+
+    *count = 0;
+    return VK_SUCCESS;
+}
+
+VkResult CreateInstance(const VkInstanceCreateInfo* create_info,
+                        VkInstance* out_instance) {
+    VkResult result;
+
+    if (!EnsureInitialized())
+        return VK_ERROR_UNAVAILABLE;
+
+    VkInstanceCreateInfo local_create_info = *create_info;
+    if (!local_create_info.pAllocCb)
+        local_create_info.pAllocCb = &kDefaultAllocCallbacks;
+    create_info = &local_create_info;
+
+    void* instance_mem = create_info->pAllocCb->pfnAlloc(
+        create_info->pAllocCb->pUserData, sizeof(Instance), alignof(Instance),
+        VK_SYSTEM_ALLOC_TYPE_API_OBJECT);
+    if (!instance_mem)
+        return VK_ERROR_OUT_OF_HOST_MEMORY;
+    Instance* instance = new (instance_mem) Instance(create_info->pAllocCb);
+
+    instance->vtbl_storage = kBottomInstanceFunctions;
+    instance->vtbl_storage.instance = instance;
+
+    // TODO: Insert enabled layers into instance->dispatch_vtbl here.
+
+    // TODO: We'll want to call CreateInstance through the dispatch table
+    // instead of calling the loader's terminator
+    *out_instance = instance;
+    result = CreateInstanceBottom(create_info, out_instance);
+    if (result <= 0) {
+        // For every layer, including the loader top and bottom layers:
+        // - If a call to the next CreateInstance fails, the layer must clean
+        //   up anything it has successfully done so far, and propagate the
+        //   error upwards.
+        // - If a layer successfully calls the next layer's CreateInstance, and
+        //   afterwards must fail for some reason, it must call the next layer's
+        //   DestroyInstance before returning.
+        // - The layer must not call the next layer's DestroyInstance if that
+        //   layer's CreateInstance wasn't called, or returned failure.
+
+        // On failure, CreateInstanceBottom frees the instance struct, so it's
+        // already gone at this point. Nothing to do.
+    }
+
+    return result;
+}
+
+PFN_vkVoidFunction GetInstanceProcAddr(VkInstance instance, const char* name) {
+    if (!instance)
+        return GetGlobalInstanceProcAddr(name);
+    // For special-case functions we always return the loader entry
+    if (strcmp(name, "vkGetInstanceProcAddr") == 0 ||
+        strcmp(name, "vkGetDeviceProcAddr") == 0) {
+        return GetGlobalInstanceProcAddr(name);
+    }
+    return GetSpecificInstanceProcAddr(instance->vtbl, name);
+}
+
+PFN_vkVoidFunction GetDeviceProcAddr(VkDevice device, const char* name) {
+    if (!device)
+        return GetGlobalDeviceProcAddr(name);
+    // For special-case functions we always return the loader entry
+    if (strcmp(name, "vkGetDeviceQueue") == 0 ||
+        strcmp(name, "vkDestroyDevice") == 0) {
+        return GetGlobalDeviceProcAddr(name);
+    }
+    return GetSpecificDeviceProcAddr(GetVtbl(device), name);
+}
+
+VkResult GetDeviceQueue(VkDevice drv_device,
+                        uint32_t family,
+                        uint32_t index,
+                        VkQueue* out_queue) {
+    VkResult result;
+    VkQueue queue;
+    const DeviceVtbl* vtbl = GetVtbl(drv_device);
+    result = vtbl->GetDeviceQueue(drv_device, family, index, &queue);
+    if (result != VK_SUCCESS)
+        return result;
+    hwvulkan_dispatch_t* dispatch =
+        reinterpret_cast<hwvulkan_dispatch_t*>(queue);
+    if (dispatch->magic != HWVULKAN_DISPATCH_MAGIC && dispatch->vtbl != &vtbl) {
+        ALOGE("invalid VkQueue dispatch magic: 0x%" PRIxPTR, dispatch->magic);
+        return VK_ERROR_INITIALIZATION_FAILED;
+    }
+    dispatch->vtbl = vtbl;
+    *out_queue = queue;
+    return VK_SUCCESS;
+}
+
+VkResult DestroyDevice(VkDevice drv_device) {
+    const DeviceVtbl* vtbl = GetVtbl(drv_device);
+    Device* device = static_cast<Device*>(vtbl->device);
+    vtbl->DestroyDevice(drv_device);
+    DestroyDevice(device);
+    return VK_SUCCESS;
+}
+
+}  // namespace vulkan
diff --git a/vulkan/libvulkan/loader.h b/vulkan/libvulkan/loader.h
new file mode 100644
index 0000000..1f6a1d4
--- /dev/null
+++ b/vulkan/libvulkan/loader.h
@@ -0,0 +1,211 @@
+#ifndef LIBVULKAN_LOADER_H
+#define LIBVULKAN_LOADER_H 1
+
+#define VK_PROTOTYPES
+#include <vulkan/vulkan.h>
+
+namespace vulkan {
+
+struct InstanceVtbl {
+    // clang-format off
+    VkInstance instance;
+
+    PFN_vkCreateInstance CreateInstance;
+    PFN_vkDestroyInstance DestroyInstance;
+    PFN_vkGetInstanceProcAddr GetInstanceProcAddr;
+    PFN_vkEnumeratePhysicalDevices EnumeratePhysicalDevices;
+
+    PFN_vkGetPhysicalDeviceFeatures GetPhysicalDeviceFeatures;
+    PFN_vkGetPhysicalDeviceFormatProperties GetPhysicalDeviceFormatProperties;
+    PFN_vkGetPhysicalDeviceImageFormatProperties GetPhysicalDeviceImageFormatProperties;
+    PFN_vkGetPhysicalDeviceLimits GetPhysicalDeviceLimits;
+    PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties;
+    PFN_vkGetPhysicalDeviceQueueCount GetPhysicalDeviceQueueCount;
+    PFN_vkGetPhysicalDeviceQueueProperties GetPhysicalDeviceQueueProperties;
+    PFN_vkGetPhysicalDeviceMemoryProperties GetPhysicalDeviceMemoryProperties;
+    PFN_vkCreateDevice CreateDevice;
+    PFN_vkGetPhysicalDeviceExtensionProperties GetPhysicalDeviceExtensionProperties;
+    PFN_vkGetPhysicalDeviceLayerProperties GetPhysicalDeviceLayerProperties;
+    PFN_vkGetPhysicalDeviceSparseImageFormatProperties GetPhysicalDeviceSparseImageFormatProperties;
+    // clang-format on
+};
+
+struct DeviceVtbl {
+    void* device;
+
+    PFN_vkGetDeviceProcAddr GetDeviceProcAddr;
+    PFN_vkDestroyDevice DestroyDevice;
+    PFN_vkGetDeviceQueue GetDeviceQueue;
+    PFN_vkDeviceWaitIdle DeviceWaitIdle;
+    PFN_vkAllocMemory AllocMemory;
+    PFN_vkFreeMemory FreeMemory;
+    PFN_vkMapMemory MapMemory;
+    PFN_vkUnmapMemory UnmapMemory;
+    PFN_vkFlushMappedMemoryRanges FlushMappedMemoryRanges;
+    PFN_vkInvalidateMappedMemoryRanges InvalidateMappedMemoryRanges;
+    PFN_vkGetDeviceMemoryCommitment GetDeviceMemoryCommitment;
+    PFN_vkBindBufferMemory BindBufferMemory;
+    PFN_vkBindImageMemory BindImageMemory;
+    PFN_vkGetBufferMemoryRequirements GetBufferMemoryRequirements;
+    PFN_vkGetImageMemoryRequirements GetImageMemoryRequirements;
+    PFN_vkGetImageSparseMemoryRequirements GetImageSparseMemoryRequirements;
+    PFN_vkCreateFence CreateFence;
+    PFN_vkDestroyFence DestroyFence;
+    PFN_vkResetFences ResetFences;
+    PFN_vkGetFenceStatus GetFenceStatus;
+    PFN_vkWaitForFences WaitForFences;
+    PFN_vkCreateSemaphore CreateSemaphore;
+    PFN_vkDestroySemaphore DestroySemaphore;
+    PFN_vkCreateEvent CreateEvent;
+    PFN_vkDestroyEvent DestroyEvent;
+    PFN_vkGetEventStatus GetEventStatus;
+    PFN_vkSetEvent SetEvent;
+    PFN_vkResetEvent ResetEvent;
+    PFN_vkCreateQueryPool CreateQueryPool;
+    PFN_vkDestroyQueryPool DestroyQueryPool;
+    PFN_vkGetQueryPoolResults GetQueryPoolResults;
+    PFN_vkCreateBuffer CreateBuffer;
+    PFN_vkDestroyBuffer DestroyBuffer;
+    PFN_vkCreateBufferView CreateBufferView;
+    PFN_vkDestroyBufferView DestroyBufferView;
+    PFN_vkCreateImage CreateImage;
+    PFN_vkDestroyImage DestroyImage;
+    PFN_vkGetImageSubresourceLayout GetImageSubresourceLayout;
+    PFN_vkCreateImageView CreateImageView;
+    PFN_vkDestroyImageView DestroyImageView;
+    PFN_vkCreateAttachmentView CreateAttachmentView;
+    PFN_vkDestroyAttachmentView DestroyAttachmentView;
+    PFN_vkCreateShaderModule CreateShaderModule;
+    PFN_vkDestroyShaderModule DestroyShaderModule;
+    PFN_vkCreateShader CreateShader;
+    PFN_vkDestroyShader DestroyShader;
+    PFN_vkCreatePipelineCache CreatePipelineCache;
+    PFN_vkDestroyPipelineCache DestroyPipelineCache;
+    PFN_vkGetPipelineCacheSize GetPipelineCacheSize;
+    PFN_vkGetPipelineCacheData GetPipelineCacheData;
+    PFN_vkMergePipelineCaches MergePipelineCaches;
+    PFN_vkCreateGraphicsPipelines CreateGraphicsPipelines;
+    PFN_vkCreateComputePipelines CreateComputePipelines;
+    PFN_vkDestroyPipeline DestroyPipeline;
+    PFN_vkCreatePipelineLayout CreatePipelineLayout;
+    PFN_vkDestroyPipelineLayout DestroyPipelineLayout;
+    PFN_vkCreateSampler CreateSampler;
+    PFN_vkDestroySampler DestroySampler;
+    PFN_vkCreateDescriptorSetLayout CreateDescriptorSetLayout;
+    PFN_vkDestroyDescriptorSetLayout DestroyDescriptorSetLayout;
+    PFN_vkCreateDescriptorPool CreateDescriptorPool;
+    PFN_vkDestroyDescriptorPool DestroyDescriptorPool;
+    PFN_vkResetDescriptorPool ResetDescriptorPool;
+    PFN_vkAllocDescriptorSets AllocDescriptorSets;
+    PFN_vkFreeDescriptorSets FreeDescriptorSets;
+    PFN_vkUpdateDescriptorSets UpdateDescriptorSets;
+    PFN_vkCreateDynamicViewportState CreateDynamicViewportState;
+    PFN_vkDestroyDynamicViewportState DestroyDynamicViewportState;
+    PFN_vkCreateDynamicRasterState CreateDynamicRasterState;
+    PFN_vkDestroyDynamicRasterState DestroyDynamicRasterState;
+    PFN_vkCreateDynamicColorBlendState CreateDynamicColorBlendState;
+    PFN_vkDestroyDynamicColorBlendState DestroyDynamicColorBlendState;
+    PFN_vkCreateDynamicDepthStencilState CreateDynamicDepthStencilState;
+    PFN_vkDestroyDynamicDepthStencilState DestroyDynamicDepthStencilState;
+    PFN_vkCreateFramebuffer CreateFramebuffer;
+    PFN_vkDestroyFramebuffer DestroyFramebuffer;
+    PFN_vkCreateRenderPass CreateRenderPass;
+    PFN_vkDestroyRenderPass DestroyRenderPass;
+    PFN_vkGetRenderAreaGranularity GetRenderAreaGranularity;
+    PFN_vkCreateCommandPool CreateCommandPool;
+    PFN_vkDestroyCommandPool DestroyCommandPool;
+    PFN_vkResetCommandPool ResetCommandPool;
+    PFN_vkCreateCommandBuffer CreateCommandBuffer;
+    PFN_vkDestroyCommandBuffer DestroyCommandBuffer;
+
+    PFN_vkQueueSubmit QueueSubmit;
+    PFN_vkQueueWaitIdle QueueWaitIdle;
+    PFN_vkQueueBindSparseBufferMemory QueueBindSparseBufferMemory;
+    PFN_vkQueueBindSparseImageOpaqueMemory QueueBindSparseImageOpaqueMemory;
+    PFN_vkQueueBindSparseImageMemory QueueBindSparseImageMemory;
+    PFN_vkQueueSignalSemaphore QueueSignalSemaphore;
+    PFN_vkQueueWaitSemaphore QueueWaitSemaphore;
+
+    PFN_vkBeginCommandBuffer BeginCommandBuffer;
+    PFN_vkEndCommandBuffer EndCommandBuffer;
+    PFN_vkResetCommandBuffer ResetCommandBuffer;
+    PFN_vkCmdBindPipeline CmdBindPipeline;
+    PFN_vkCmdBindDynamicViewportState CmdBindDynamicViewportState;
+    PFN_vkCmdBindDynamicRasterState CmdBindDynamicRasterState;
+    PFN_vkCmdBindDynamicColorBlendState CmdBindDynamicColorBlendState;
+    PFN_vkCmdBindDynamicDepthStencilState CmdBindDynamicDepthStencilState;
+    PFN_vkCmdBindDescriptorSets CmdBindDescriptorSets;
+    PFN_vkCmdBindIndexBuffer CmdBindIndexBuffer;
+    PFN_vkCmdBindVertexBuffers CmdBindVertexBuffers;
+    PFN_vkCmdDraw CmdDraw;
+    PFN_vkCmdDrawIndexed CmdDrawIndexed;
+    PFN_vkCmdDrawIndirect CmdDrawIndirect;
+    PFN_vkCmdDrawIndexedIndirect CmdDrawIndexedIndirect;
+    PFN_vkCmdDispatch CmdDispatch;
+    PFN_vkCmdDispatchIndirect CmdDispatchIndirect;
+    PFN_vkCmdCopyBuffer CmdCopyBuffer;
+    PFN_vkCmdCopyImage CmdCopyImage;
+    PFN_vkCmdBlitImage CmdBlitImage;
+    PFN_vkCmdCopyBufferToImage CmdCopyBufferToImage;
+    PFN_vkCmdCopyImageToBuffer CmdCopyImageToBuffer;
+    PFN_vkCmdUpdateBuffer CmdUpdateBuffer;
+    PFN_vkCmdFillBuffer CmdFillBuffer;
+    PFN_vkCmdClearColorImage CmdClearColorImage;
+    PFN_vkCmdClearDepthStencilImage CmdClearDepthStencilImage;
+    PFN_vkCmdClearColorAttachment CmdClearColorAttachment;
+    PFN_vkCmdClearDepthStencilAttachment CmdClearDepthStencilAttachment;
+    PFN_vkCmdResolveImage CmdResolveImage;
+    PFN_vkCmdSetEvent CmdSetEvent;
+    PFN_vkCmdResetEvent CmdResetEvent;
+    PFN_vkCmdWaitEvents CmdWaitEvents;
+    PFN_vkCmdPipelineBarrier CmdPipelineBarrier;
+    PFN_vkCmdBeginQuery CmdBeginQuery;
+    PFN_vkCmdEndQuery CmdEndQuery;
+    PFN_vkCmdResetQueryPool CmdResetQueryPool;
+    PFN_vkCmdWriteTimestamp CmdWriteTimestamp;
+    PFN_vkCmdCopyQueryPoolResults CmdCopyQueryPoolResults;
+    PFN_vkCmdPushConstants CmdPushConstants;
+    PFN_vkCmdBeginRenderPass CmdBeginRenderPass;
+    PFN_vkCmdNextSubpass CmdNextSubpass;
+    PFN_vkCmdEndRenderPass CmdEndRenderPass;
+    PFN_vkCmdExecuteCommands CmdExecuteCommands;
+};
+
+// -----------------------------------------------------------------------------
+// loader.cpp
+
+VkResult GetGlobalExtensionProperties(const char* layer_name,
+                                      uint32_t* count,
+                                      VkExtensionProperties* properties);
+VkResult GetGlobalLayerProperties(uint32_t* count,
+                                  VkLayerProperties* properties);
+VkResult CreateInstance(const VkInstanceCreateInfo* create_info,
+                        VkInstance* instance);
+PFN_vkVoidFunction GetInstanceProcAddr(VkInstance instance, const char* name);
+PFN_vkVoidFunction GetDeviceProcAddr(VkDevice drv_device, const char* name);
+VkResult GetDeviceQueue(VkDevice drv_device,
+                        uint32_t family,
+                        uint32_t index,
+                        VkQueue* out_queue);
+VkResult DestroyDevice(VkDevice drv_device);
+
+// -----------------------------------------------------------------------------
+// get_proc_addr.cpp
+
+PFN_vkVoidFunction GetGlobalInstanceProcAddr(const char* name);
+PFN_vkVoidFunction GetGlobalDeviceProcAddr(const char* name);
+PFN_vkVoidFunction GetSpecificInstanceProcAddr(const InstanceVtbl* vtbl,
+                                               const char* name);
+PFN_vkVoidFunction GetSpecificDeviceProcAddr(const DeviceVtbl* vtbl,
+                                             const char* name);
+
+bool LoadInstanceVtbl(VkInstance instance,
+                      PFN_vkGetInstanceProcAddr get_proc_addr,
+                      InstanceVtbl& vtbl);
+bool LoadDeviceVtbl(VkDevice device,
+                    PFN_vkGetDeviceProcAddr get_proc_addr,
+                    DeviceVtbl& vtbl);
+
+}  // namespace vulkan
+
+#endif  // LIBVULKAN_LOADER_H