summaryrefslogtreecommitdiff
path: root/src/vulkan
diff options
context:
space:
mode:
Diffstat (limited to 'src/vulkan')
-rw-r--r--src/vulkan/vk_impl.cpp694
-rw-r--r--src/vulkan/vk_types.hpp285
2 files changed, 979 insertions, 0 deletions
diff --git a/src/vulkan/vk_impl.cpp b/src/vulkan/vk_impl.cpp
new file mode 100644
index 0000000..aaa1bb0
--- /dev/null
+++ b/src/vulkan/vk_impl.cpp
@@ -0,0 +1,694 @@
+#include "vk_types.hpp"
+#include "../logging.hpp"
+
+#include <cstring>
+#include <stdexcept>
+#include <vector>
+#include <optional>
+
+namespace bionic_fg::vk {
+
+// ─── Utils ───────────────────────────────────────────────────────────────────
+
+uint32_t findMemoryType(const Device& dev, uint32_t typeBits,
+ VkMemoryPropertyFlags props) {
+ VkPhysicalDeviceMemoryProperties memProps;
+ dev.memPropsFn()(dev.physical(), &memProps);
+ for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
+ if ((typeBits & (1u << i)) &&
+ (memProps.memoryTypes[i].propertyFlags & props) == props) {
+ return i;
+ }
+ }
+ throw VkError(VK_ERROR_UNKNOWN, "No suitable memory type found");
+}
+
+// ─── Device ──────────────────────────────────────────────────────────────────
+
+static const char* kRequiredDeviceExts[] = {
+#ifdef __ANDROID__
+ "VK_ANDROID_external_memory_android_hardware_buffer",
+ "VK_KHR_external_memory",
+ "VK_KHR_sampler_ycbcr_conversion",
+ "VK_KHR_dedicated_allocation",
+ "VK_KHR_get_memory_requirements2",
+ "VK_KHR_bind_memory2",
+ "VK_KHR_maintenance1",
+ // NOTE: VK_KHR_external_memory_capabilities and
+ // VK_KHR_get_physical_device_properties2 are *instance* extensions (and core
+ // since Vulkan 1.1) — they are enabled on the instance below, not the device.
+ // Listing them here made vkEnumerateDeviceExtensionProperties always report
+ // them missing ("Device ext not available"); harmless (they were filtered
+ // out before vkCreateDevice) but misleading, so they are removed.
+#endif
+};
+
+static bool hasExt(const std::vector<VkExtensionProperties>& exts, const char* name) {
+ for (const auto& e : exts)
+ if (std::strcmp(e.extensionName, name) == 0) return true;
+ return false;
+}
+
+Device Device::create() {
+ // Instance
+ VkApplicationInfo appInfo{};
+ appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+ appInfo.pApplicationName = "BionicFG";
+ appInfo.apiVersion = VK_API_VERSION_1_1;
+
+ std::vector<const char*> instanceExts;
+#ifdef __ANDROID__
+ instanceExts.push_back("VK_KHR_external_memory_capabilities");
+ instanceExts.push_back("VK_KHR_get_physical_device_properties2");
+#endif
+
+ VkInstanceCreateInfo ici{};
+ ici.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+ ici.pApplicationInfo = &appInfo;
+ ici.enabledExtensionCount = static_cast<uint32_t>(instanceExts.size());
+ ici.ppEnabledExtensionNames = instanceExts.data();
+
+ VkInstance inst = VK_NULL_HANDLE;
+ VkResult res = vkCreateInstance(&ici, nullptr, &inst);
+ if (res != VK_SUCCESS || !inst)
+ throw VkError(res, "vkCreateInstance failed");
+
+ // Pick first physical device with a compute queue
+ uint32_t devCount = 0;
+ vkEnumeratePhysicalDevices(inst, &devCount, nullptr);
+ if (devCount == 0) {
+ vkDestroyInstance(inst, nullptr);
+ throw VkError(VK_ERROR_INITIALIZATION_FAILED, "No Vulkan physical devices");
+ }
+ std::vector<VkPhysicalDevice> physDevs(devCount);
+ vkEnumeratePhysicalDevices(inst, &devCount, physDevs.data());
+
+ VkPhysicalDevice phys = VK_NULL_HANDLE;
+ uint32_t computeQF = 0;
+ for (auto pd : physDevs) {
+ uint32_t qfCount = 0;
+ vkGetPhysicalDeviceQueueFamilyProperties(pd, &qfCount, nullptr);
+ std::vector<VkQueueFamilyProperties> qfProps(qfCount);
+ vkGetPhysicalDeviceQueueFamilyProperties(pd, &qfCount, qfProps.data());
+ for (uint32_t i = 0; i < qfCount; ++i) {
+ if (qfProps[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
+ phys = pd;
+ computeQF = i;
+ break;
+ }
+ }
+ if (phys) break;
+ }
+ if (!phys) {
+ vkDestroyInstance(inst, nullptr);
+ throw VkError(VK_ERROR_INITIALIZATION_FAILED, "No compute-capable GPU");
+ }
+
+ // Check/collect device extensions
+ uint32_t extCount = 0;
+ vkEnumerateDeviceExtensionProperties(phys, nullptr, &extCount, nullptr);
+ std::vector<VkExtensionProperties> availExts(extCount);
+ vkEnumerateDeviceExtensionProperties(phys, nullptr, &extCount, availExts.data());
+
+ std::vector<const char*> enabledExts;
+ for (const char* ext : kRequiredDeviceExts) {
+ if (hasExt(availExts, ext))
+ enabledExts.push_back(ext);
+ else
+ BFG_LOGW("Device ext not available: %s", ext);
+ }
+
+ const float qPri = 1.0f;
+ VkDeviceQueueCreateInfo qci{};
+ qci.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ qci.queueFamilyIndex = computeQF;
+ qci.queueCount = 1;
+ qci.pQueuePriorities = &qPri;
+
+ VkDeviceCreateInfo dci{};
+ dci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+ dci.queueCreateInfoCount = 1;
+ dci.pQueueCreateInfos = &qci;
+ dci.enabledExtensionCount = static_cast<uint32_t>(enabledExts.size());
+ dci.ppEnabledExtensionNames = enabledExts.data();
+
+ VkDevice dev = VK_NULL_HANDLE;
+ res = vkCreateDevice(phys, &dci, nullptr, &dev);
+ if (res != VK_SUCCESS || !dev) {
+ vkDestroyInstance(inst, nullptr);
+ throw VkError(res, "vkCreateDevice failed");
+ }
+
+ VkQueue queue = VK_NULL_HANDLE;
+ vkGetDeviceQueue(dev, computeQF, 0, &queue);
+
+ Device d;
+ d.instance_ = inst;
+ d.device_ = dev;
+ d.physical_ = phys;
+ d.computeFamily_ = computeQF;
+ d.computeQueue_ = queue;
+ BFG_LOGI("Device created: phys=%p dev=%p computeQF=%u", (void*)phys, (void*)dev, computeQF);
+ return d;
+}
+
+Device Device::wrap(VkInstance inst, VkPhysicalDevice phys, VkDevice dev,
+ uint32_t computeFamily, VkQueue queue,
+ PFN_vkGetPhysicalDeviceMemoryProperties memPropsFn) {
+ Device d;
+ d.owned_ = false;
+ d.instance_ = inst;
+ d.physical_ = phys;
+ d.device_ = dev;
+ d.computeFamily_ = computeFamily;
+ d.computeQueue_ = queue;
+ d.memPropsFn_ = memPropsFn;
+ return d;
+}
+
+void Device::destroy() {
+ if (!owned_) { device_ = VK_NULL_HANDLE; instance_ = VK_NULL_HANDLE; return; }
+ if (device_) vkDestroyDevice(device_, nullptr);
+ if (instance_) vkDestroyInstance(instance_, nullptr);
+ device_ = VK_NULL_HANDLE; instance_ = VK_NULL_HANDLE;
+}
+
+// ─── Image ───────────────────────────────────────────────────────────────────
+
+Image::Image(const Device& dev, const ImageInfo& info) {
+ extent_ = info.extent;
+ format_ = info.format;
+ external_ = false;
+
+ VkImageCreateInfo ici{};
+ ici.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+ ici.imageType = VK_IMAGE_TYPE_2D;
+ ici.format = info.format;
+ ici.extent = { info.extent.width, info.extent.height, 1 };
+ ici.mipLevels = 1;
+ ici.arrayLayers = 1;
+ ici.samples = VK_SAMPLE_COUNT_1_BIT;
+ ici.tiling = VK_IMAGE_TILING_OPTIMAL;
+ ici.usage = info.usage;
+ ici.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ ici.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+ VkResult res = vkCreateImage(dev.handle(), &ici, nullptr, &image_);
+ if (res != VK_SUCCESS) throw VkError(res, "vkCreateImage failed");
+
+ VkMemoryRequirements memReq{};
+ vkGetImageMemoryRequirements(dev.handle(), image_, &memReq);
+ uint32_t mt = findMemoryType(dev, memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+ VkMemoryAllocateInfo mai{};
+ mai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ mai.allocationSize = memReq.size;
+ mai.memoryTypeIndex = mt;
+
+ res = vkAllocateMemory(dev.handle(), &mai, nullptr, &memory_);
+ if (res != VK_SUCCESS) {
+ vkDestroyImage(dev.handle(), image_, nullptr);
+ throw VkError(res, "vkAllocateMemory (image) failed");
+ }
+ vkBindImageMemory(dev.handle(), image_, memory_, 0);
+
+ VkImageViewCreateInfo vci{};
+ vci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ vci.image = image_;
+ vci.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ vci.format = info.format;
+ vci.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+ vci.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+ vci.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+ vci.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+ vci.subresourceRange.aspectMask = info.aspect;
+ vci.subresourceRange.baseMipLevel = 0;
+ vci.subresourceRange.levelCount = 1;
+ vci.subresourceRange.baseArrayLayer = 0;
+ vci.subresourceRange.layerCount = 1;
+
+ res = vkCreateImageView(dev.handle(), &vci, nullptr, &view_);
+ if (res != VK_SUCCESS) {
+ vkFreeMemory(dev.handle(), memory_, nullptr);
+ vkDestroyImage(dev.handle(), image_, nullptr);
+ throw VkError(res, "vkCreateImageView failed");
+ }
+ layout = VK_IMAGE_LAYOUT_UNDEFINED;
+}
+
+#ifdef __ANDROID__
+Image::Image(const Device& dev, const ImageInfo& info, AHardwareBuffer* ahb) {
+ extent_ = info.extent;
+ format_ = info.format;
+ external_ = true;
+
+ VkAndroidHardwareBufferPropertiesANDROID ahbProps{};
+ ahbProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID;
+ auto fn = reinterpret_cast<PFN_vkGetAndroidHardwareBufferPropertiesANDROID>(
+ vkGetDeviceProcAddr(dev.handle(), "vkGetAndroidHardwareBufferPropertiesANDROID"));
+ if (!fn) throw VkError(VK_ERROR_EXTENSION_NOT_PRESENT,
+ "vkGetAndroidHardwareBufferPropertiesANDROID not found");
+ VkResult res = fn(dev.handle(), ahb, &ahbProps);
+ if (res != VK_SUCCESS) throw VkError(res, "vkGetAndroidHardwareBufferPropertiesANDROID");
+
+ VkExternalMemoryImageCreateInfo emici{};
+ emici.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
+ emici.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;
+
+ VkImageCreateInfo ici{};
+ ici.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+ ici.pNext = &emici;
+ ici.imageType = VK_IMAGE_TYPE_2D;
+ ici.format = info.format;
+ ici.extent = { info.extent.width, info.extent.height, 1 };
+ ici.mipLevels = 1;
+ ici.arrayLayers = 1;
+ ici.samples = VK_SAMPLE_COUNT_1_BIT;
+ ici.tiling = VK_IMAGE_TILING_OPTIMAL;
+ ici.usage = info.usage;
+ ici.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ ici.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+ res = vkCreateImage(dev.handle(), &ici, nullptr, &image_);
+ if (res != VK_SUCCESS) throw VkError(res, "vkCreateImage (AHB) failed");
+
+ VkMemoryDedicatedAllocateInfo dedicatedInfo{};
+ dedicatedInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
+ dedicatedInfo.image = image_;
+
+ VkImportAndroidHardwareBufferInfoANDROID importInfo{};
+ importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID;
+ importInfo.pNext = &dedicatedInfo;
+ importInfo.buffer = ahb;
+
+ VkMemoryAllocateInfo mai{};
+ mai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ mai.pNext = &importInfo;
+ mai.allocationSize = ahbProps.allocationSize;
+ mai.memoryTypeIndex = 0;
+ // find compatible mem type from AHB props
+ VkPhysicalDeviceMemoryProperties memProps{};
+ dev.memPropsFn()(dev.physical(), &memProps);
+ for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
+ if (ahbProps.memoryTypeBits & (1u << i)) { mai.memoryTypeIndex = i; break; }
+ }
+
+ res = vkAllocateMemory(dev.handle(), &mai, nullptr, &memory_);
+ if (res != VK_SUCCESS) {
+ vkDestroyImage(dev.handle(), image_, nullptr);
+ throw VkError(res, "vkAllocateMemory (AHB) failed");
+ }
+ vkBindImageMemory(dev.handle(), image_, memory_, 0);
+
+ VkImageViewCreateInfo vci{};
+ vci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ vci.image = image_;
+ vci.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ vci.format = info.format;
+ vci.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+ vci.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+ vci.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+ vci.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+ vci.subresourceRange.aspectMask = info.aspect;
+ vci.subresourceRange.baseMipLevel = 0;
+ vci.subresourceRange.levelCount = 1;
+ vci.subresourceRange.baseArrayLayer = 0;
+ vci.subresourceRange.layerCount = 1;
+
+ res = vkCreateImageView(dev.handle(), &vci, nullptr, &view_);
+ if (res != VK_SUCCESS) {
+ vkFreeMemory(dev.handle(), memory_, nullptr);
+ vkDestroyImage(dev.handle(), image_, nullptr);
+ throw VkError(res, "vkCreateImageView (AHB) failed");
+ }
+ layout = VK_IMAGE_LAYOUT_GENERAL; // AHB images start in GENERAL
+}
+#endif
+
+void Image::destroy(const Device& dev) {
+ if (view_) vkDestroyImageView(dev.handle(), view_, nullptr);
+ if (memory_) vkFreeMemory(dev.handle(), memory_, nullptr);
+ if (image_) vkDestroyImage(dev.handle(), image_, nullptr);
+ view_ = VK_NULL_HANDLE;
+ memory_ = VK_NULL_HANDLE;
+ image_ = VK_NULL_HANDLE;
+}
+
+// ─── Buffer ──────────────────────────────────────────────────────────────────
+
+Buffer::Buffer(const Device& dev, VkDeviceSize size, VkBufferUsageFlags usage,
+ const void* initialData) {
+ size_ = size;
+ VkBufferCreateInfo bci{};
+ bci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ bci.size = size;
+ bci.usage = usage;
+ bci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ VkResult res = vkCreateBuffer(dev.handle(), &bci, nullptr, &buffer_);
+ if (res != VK_SUCCESS) throw VkError(res, "vkCreateBuffer failed");
+
+ VkMemoryRequirements memReq{};
+ vkGetBufferMemoryRequirements(dev.handle(), buffer_, &memReq);
+ uint32_t mt = findMemoryType(dev, memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+ VkMemoryAllocateInfo mai{};
+ mai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ mai.allocationSize = memReq.size;
+ mai.memoryTypeIndex = mt;
+ res = vkAllocateMemory(dev.handle(), &mai, nullptr, &memory_);
+ if (res != VK_SUCCESS) {
+ vkDestroyBuffer(dev.handle(), buffer_, nullptr);
+ throw VkError(res, "vkAllocateMemory (buffer) failed");
+ }
+ vkBindBufferMemory(dev.handle(), buffer_, memory_, 0);
+ vkMapMemory(dev.handle(), memory_, 0, size, 0, &mapped_);
+ if (initialData && mapped_) std::memcpy(mapped_, initialData, size);
+}
+
+void Buffer::write(const Device& dev, const void* data, VkDeviceSize sz) {
+ (void)dev;
+ if (mapped_ && data) std::memcpy(mapped_, data, sz);
+}
+
+void Buffer::destroy(const Device& dev) {
+ if (mapped_) vkUnmapMemory(dev.handle(), memory_);
+ if (memory_) vkFreeMemory(dev.handle(), memory_, nullptr);
+ if (buffer_) vkDestroyBuffer(dev.handle(), buffer_, nullptr);
+ buffer_ = VK_NULL_HANDLE; memory_ = VK_NULL_HANDLE; mapped_ = nullptr;
+}
+
+// ─── CommandPool / CommandBuffer ─────────────────────────────────────────────
+
+CommandPool::CommandPool(const Device& dev) {
+ VkCommandPoolCreateInfo cpci{};
+ cpci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+ cpci.queueFamilyIndex = dev.computeFamily();
+ cpci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+ VkResult res = vkCreateCommandPool(dev.handle(), &cpci, nullptr, &pool_);
+ if (res != VK_SUCCESS) throw VkError(res, "vkCreateCommandPool failed");
+}
+void CommandPool::destroy(const Device& dev) {
+ if (pool_) vkDestroyCommandPool(dev.handle(), pool_, nullptr);
+ pool_ = VK_NULL_HANDLE;
+}
+
+CommandBuffer::CommandBuffer(const Device& dev, const CommandPool& pool) {
+ VkCommandBufferAllocateInfo ai{};
+ ai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+ ai.commandPool = pool.handle();
+ ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ ai.commandBufferCount = 1;
+ VkResult res = vkAllocateCommandBuffers(dev.handle(), &ai, &buf_);
+ if (res != VK_SUCCESS) throw VkError(res, "vkAllocateCommandBuffers failed");
+}
+
+void CommandBuffer::begin() {
+ VkCommandBufferBeginInfo bi{};
+ bi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+ bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+ vkBeginCommandBuffer(buf_, &bi);
+}
+void CommandBuffer::end() { vkEndCommandBuffer(buf_); }
+
+void CommandBuffer::submit(const Device& dev, VkFence fence) {
+ VkSubmitInfo si{};
+ si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+ si.commandBufferCount = 1;
+ si.pCommandBuffers = &buf_;
+ vkQueueSubmit(dev.computeQueue(), 1, &si, fence);
+}
+void CommandBuffer::submitAndWait(const Device& dev) {
+ submit(dev);
+ vkQueueWaitIdle(dev.computeQueue());
+}
+void CommandBuffer::destroy(const Device& dev, const CommandPool& pool) {
+ if (buf_) vkFreeCommandBuffers(dev.handle(), pool.handle(), 1, &buf_);
+ buf_ = VK_NULL_HANDLE;
+}
+
+// ─── Fence ───────────────────────────────────────────────────────────────────
+
+Fence::Fence(const Device& dev, bool signaled) {
+ VkFenceCreateInfo fci{};
+ fci.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+ fci.flags = signaled ? VK_FENCE_CREATE_SIGNALED_BIT : 0u;
+ VkResult res = vkCreateFence(dev.handle(), &fci, nullptr, &fence_);
+ if (res != VK_SUCCESS) throw VkError(res, "vkCreateFence failed");
+}
+void Fence::wait(const Device& dev) {
+ vkWaitForFences(dev.handle(), 1, &fence_, VK_TRUE, UINT64_MAX);
+}
+void Fence::reset(const Device& dev) { vkResetFences(dev.handle(), 1, &fence_); }
+void Fence::destroy(const Device& dev) {
+ if (fence_) vkDestroyFence(dev.handle(), fence_, nullptr);
+ fence_ = VK_NULL_HANDLE;
+}
+
+// ─── Sampler ─────────────────────────────────────────────────────────────────
+
+Sampler::Sampler(const Device& dev, VkFilter filter, VkSamplerAddressMode mode) {
+ VkSamplerCreateInfo sci{};
+ sci.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+ sci.magFilter = filter;
+ sci.minFilter = filter;
+ sci.addressModeU = mode;
+ sci.addressModeV = mode;
+ sci.addressModeW = mode;
+ VkResult res = vkCreateSampler(dev.handle(), &sci, nullptr, &sampler_);
+ if (res != VK_SUCCESS) throw VkError(res, "vkCreateSampler failed");
+}
+void Sampler::destroy(const Device& dev) {
+ if (sampler_) vkDestroySampler(dev.handle(), sampler_, nullptr);
+ sampler_ = VK_NULL_HANDLE;
+}
+
+// ─── Descriptor ──────────────────────────────────────────────────────────────
+
+DescriptorPool::DescriptorPool(const Device& dev, uint32_t maxSets,
+ const std::vector<VkDescriptorPoolSize>& sizes) {
+ VkDescriptorPoolCreateInfo pci{};
+ pci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+ pci.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
+ pci.maxSets = maxSets;
+ pci.poolSizeCount = static_cast<uint32_t>(sizes.size());
+ pci.pPoolSizes = sizes.data();
+ VkResult res = vkCreateDescriptorPool(dev.handle(), &pci, nullptr, &pool_);
+ if (res != VK_SUCCESS) throw VkError(res, "vkCreateDescriptorPool failed");
+}
+void DescriptorPool::destroy(const Device& dev) {
+ if (pool_) vkDestroyDescriptorPool(dev.handle(), pool_, nullptr);
+ pool_ = VK_NULL_HANDLE;
+}
+
+DescriptorSetLayout::DescriptorSetLayout(const Device& dev,
+ const std::vector<DescriptorBinding>& bindings) {
+ std::vector<VkDescriptorSetLayoutBinding> vkBindings;
+ vkBindings.reserve(bindings.size());
+ for (const auto& b : bindings) {
+ VkDescriptorSetLayoutBinding vkb{};
+ vkb.binding = b.binding;
+ vkb.descriptorType = b.type;
+ vkb.descriptorCount = b.count;
+ vkb.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+ vkBindings.push_back(vkb);
+ }
+ VkDescriptorSetLayoutCreateInfo lci{};
+ lci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+ lci.bindingCount = static_cast<uint32_t>(vkBindings.size());
+ lci.pBindings = vkBindings.data();
+ VkResult res = vkCreateDescriptorSetLayout(dev.handle(), &lci, nullptr, &layout_);
+ if (res != VK_SUCCESS) throw VkError(res, "vkCreateDescriptorSetLayout failed");
+}
+void DescriptorSetLayout::destroy(const Device& dev) {
+ if (layout_) vkDestroyDescriptorSetLayout(dev.handle(), layout_, nullptr);
+ layout_ = VK_NULL_HANDLE;
+}
+
+DescriptorSet::DescriptorSet(const Device& dev, VkDescriptorPool pool,
+ VkDescriptorSetLayout layout) {
+ VkDescriptorSetAllocateInfo ai{};
+ ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+ ai.descriptorPool = pool;
+ ai.descriptorSetCount = 1;
+ ai.pSetLayouts = &layout;
+ VkResult res = vkAllocateDescriptorSets(dev.handle(), &ai, &set_);
+ if (res != VK_SUCCESS) throw VkError(res, "vkAllocateDescriptorSets failed");
+}
+
+void DescriptorSet::bindUBO(const Device& dev, uint32_t binding, const Buffer& buf) {
+ VkDescriptorBufferInfo bi{};
+ bi.buffer = buf.handle(); bi.offset = 0; bi.range = buf.size();
+ VkWriteDescriptorSet w{};
+ w.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ w.dstSet = set_;
+ w.dstBinding = binding;
+ w.descriptorCount = 1;
+ w.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ w.pBufferInfo = &bi;
+ vkUpdateDescriptorSets(dev.handle(), 1, &w, 0, nullptr);
+}
+
+void DescriptorSet::bindStorageImage(const Device& dev, uint32_t binding, const Image& img) {
+ VkDescriptorImageInfo ii{};
+ ii.imageView = img.view();
+ ii.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+ VkWriteDescriptorSet w{};
+ w.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ w.dstSet = set_;
+ w.dstBinding = binding;
+ w.descriptorCount = 1;
+ w.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+ w.pImageInfo = &ii;
+ vkUpdateDescriptorSets(dev.handle(), 1, &w, 0, nullptr);
+}
+
+void DescriptorSet::bindSampledImage(const Device& dev, uint32_t binding,
+ const Image& img, VkImageLayout layout) {
+ VkDescriptorImageInfo ii{};
+ ii.imageView = img.view();
+ ii.imageLayout = layout;
+ VkWriteDescriptorSet w{};
+ w.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ w.dstSet = set_;
+ w.dstBinding = binding;
+ w.descriptorCount = 1;
+ w.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+ w.pImageInfo = &ii;
+ vkUpdateDescriptorSets(dev.handle(), 1, &w, 0, nullptr);
+}
+
+void DescriptorSet::bindSampler(const Device& dev, uint32_t binding, const Sampler& sampler) {
+ VkDescriptorImageInfo ii{};
+ ii.sampler = sampler.handle();
+ VkWriteDescriptorSet w{};
+ w.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ w.dstSet = set_;
+ w.dstBinding = binding;
+ w.descriptorCount = 1;
+ w.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
+ w.pImageInfo = &ii;
+ vkUpdateDescriptorSets(dev.handle(), 1, &w, 0, nullptr);
+}
+
+void DescriptorSet::bindCombinedImageSampler(const Device& dev, uint32_t binding,
+ const Image& img, const Sampler& sampler, VkImageLayout layout) {
+ VkDescriptorImageInfo ii{};
+ ii.sampler = sampler.handle();
+ ii.imageView = img.view();
+ ii.imageLayout = layout;
+ VkWriteDescriptorSet w{};
+ w.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ w.dstSet = set_;
+ w.dstBinding = binding;
+ w.descriptorCount = 1;
+ w.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ w.pImageInfo = &ii;
+ vkUpdateDescriptorSets(dev.handle(), 1, &w, 0, nullptr);
+}
+
+// ─── ComputePipeline ─────────────────────────────────────────────────────────
+
+ComputePipeline::ComputePipeline(const Device& dev,
+ const uint8_t* spirvData, size_t spirvBytes,
+ VkDescriptorSetLayout descriptorLayout,
+ const VkPushConstantRange* pushRange) {
+ VkShaderModuleCreateInfo smci{};
+ smci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+ smci.codeSize = spirvBytes;
+ smci.pCode = reinterpret_cast<const uint32_t*>(spirvData);
+ VkShaderModule shaderMod = VK_NULL_HANDLE;
+ VkResult res = vkCreateShaderModule(dev.handle(), &smci, nullptr, &shaderMod);
+ if (res != VK_SUCCESS) throw VkError(res, "vkCreateShaderModule failed");
+
+ VkPipelineLayoutCreateInfo plci{};
+ plci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+ plci.setLayoutCount = 1;
+ plci.pSetLayouts = &descriptorLayout;
+ if (pushRange) {
+ plci.pushConstantRangeCount = 1;
+ plci.pPushConstantRanges = pushRange;
+ }
+ res = vkCreatePipelineLayout(dev.handle(), &plci, nullptr, &layout_);
+ if (res != VK_SUCCESS) {
+ vkDestroyShaderModule(dev.handle(), shaderMod, nullptr);
+ throw VkError(res, "vkCreatePipelineLayout failed");
+ }
+
+ VkPipelineShaderStageCreateInfo ssci{};
+ ssci.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ ssci.stage = VK_SHADER_STAGE_COMPUTE_BIT;
+ ssci.module = shaderMod;
+ ssci.pName = "main";
+
+ VkComputePipelineCreateInfo cpci{};
+ cpci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
+ cpci.stage = ssci;
+ cpci.layout = layout_;
+ res = vkCreateComputePipelines(dev.handle(), VK_NULL_HANDLE, 1, &cpci, nullptr, &pipeline_);
+ vkDestroyShaderModule(dev.handle(), shaderMod, nullptr);
+ if (res != VK_SUCCESS) {
+ vkDestroyPipelineLayout(dev.handle(), layout_, nullptr);
+ throw VkError(res, "vkCreateComputePipelines failed");
+ }
+}
+
+void ComputePipeline::bind(VkCommandBuffer cmd) const {
+ vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_);
+}
+void ComputePipeline::bindDescriptorSet(VkCommandBuffer cmd, VkDescriptorSet set) const {
+ vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,
+ layout_, 0, 1, &set, 0, nullptr);
+}
+void ComputePipeline::dispatch(VkCommandBuffer cmd,
+ uint32_t gx, uint32_t gy, uint32_t gz) const {
+ vkCmdDispatch(cmd, gx, gy, gz);
+}
+void ComputePipeline::destroy(const Device& dev) {
+ if (pipeline_) vkDestroyPipeline(dev.handle(), pipeline_, nullptr);
+ if (layout_) vkDestroyPipelineLayout(dev.handle(), layout_, nullptr);
+ pipeline_ = VK_NULL_HANDLE; layout_ = VK_NULL_HANDLE;
+}
+
+// ─── Barriers ────────────────────────────────────────────────────────────────
+
+void imageBarrier(VkCommandBuffer cmd, VkImage image,
+ VkPipelineStageFlags srcStage, VkAccessFlags srcAccess,
+ VkPipelineStageFlags dstStage, VkAccessFlags dstAccess,
+ VkImageLayout oldLayout, VkImageLayout newLayout,
+ uint32_t srcQF, uint32_t dstQF) {
+ VkImageMemoryBarrier b{};
+ b.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ b.srcAccessMask = srcAccess;
+ b.dstAccessMask = dstAccess;
+ b.oldLayout = oldLayout;
+ b.newLayout = newLayout;
+ b.srcQueueFamilyIndex = srcQF;
+ b.dstQueueFamilyIndex = dstQF;
+ b.image = image;
+ b.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ b.subresourceRange.levelCount = 1;
+ b.subresourceRange.layerCount = 1;
+ vkCmdPipelineBarrier(cmd, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &b);
+}
+
+void acquireFromExternal(VkCommandBuffer cmd, const Image& img,
+ uint32_t dstQueueFamily, VkAccessFlags dstAccess) {
+ imageBarrier(cmd, img.handle(),
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0,
+ VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, dstAccess,
+ img.layout, img.layout,
+ VK_QUEUE_FAMILY_EXTERNAL, dstQueueFamily);
+}
+
+void releaseToExternal(VkCommandBuffer cmd, const Image& img,
+ uint32_t srcQueueFamily, VkAccessFlags srcAccess) {
+ imageBarrier(cmd, img.handle(),
+ VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, srcAccess,
+ VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0,
+ img.layout, img.layout,
+ srcQueueFamily, VK_QUEUE_FAMILY_EXTERNAL);
+}
+
+} // namespace bionic_fg::vk
diff --git a/src/vulkan/vk_types.hpp b/src/vulkan/vk_types.hpp
new file mode 100644
index 0000000..fb62ba6
--- /dev/null
+++ b/src/vulkan/vk_types.hpp
@@ -0,0 +1,285 @@
+#pragma once
+
+#include <vulkan/vulkan.h>
+#ifdef __ANDROID__
+#include <vulkan/vulkan_android.h>
+#include <android/hardware_buffer.h>
+#endif
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace bionic_fg::vk {
+
+// ─── Forward decls ──────────────────────────────────────────────────────────
+
+class Device;
+
+// ─── Error ──────────────────────────────────────────────────────────────────
+
+struct VkError {
+ VkResult code;
+ std::string msg;
+ VkError(VkResult r, const char* m) : code(r), msg(m) {}
+};
+
+// ─── Device ─────────────────────────────────────────────────────────────────
+
+class Device {
+public:
+ Device() = default;
+ explicit Device(std::nullptr_t) {}
+
+ // Creates a standalone Vulkan device on the first capable physical device.
+ // On Android, requests the AHB extension chain automatically.
+ static Device create();
+ // Wrap an existing device (owned=false: destroy() does nothing).
+ // `memPropsFn` should be the next-layer dispatch for
+ // vkGetPhysicalDeviceMemoryProperties (obtained via the layer's
+ // nextGetInstanceProcAddr): calling the global loader symbol from inside a
+ // layer with a chain-level physical-device handle can crash. Pass nullptr
+ // (e.g. the standalone path) to fall back to the global symbol.
+ static Device wrap(VkInstance inst, VkPhysicalDevice phys, VkDevice dev,
+ uint32_t computeFamily, VkQueue queue,
+ PFN_vkGetPhysicalDeviceMemoryProperties memPropsFn = nullptr);
+
+ VkDevice handle() const { return device_; }
+ VkPhysicalDevice physical() const { return physical_; }
+ uint32_t computeFamily() const { return computeFamily_; }
+ VkQueue computeQueue() const { return computeQueue_; }
+ // Physical-device memory-properties query routed through the layer dispatch
+ // when wrapped; falls back to the global symbol otherwise.
+ PFN_vkGetPhysicalDeviceMemoryProperties memPropsFn() const {
+ return memPropsFn_ ? memPropsFn_ : &vkGetPhysicalDeviceMemoryProperties;
+ }
+ bool valid() const { return device_ != VK_NULL_HANDLE; }
+
+ void destroy();
+
+private:
+ bool owned_ = true;
+ VkInstance instance_ = VK_NULL_HANDLE;
+ VkDevice device_ = VK_NULL_HANDLE;
+ VkPhysicalDevice physical_ = VK_NULL_HANDLE;
+ uint32_t computeFamily_= 0;
+ VkQueue computeQueue_ = VK_NULL_HANDLE;
+ PFN_vkGetPhysicalDeviceMemoryProperties memPropsFn_ = nullptr;
+};
+
+// ─── Image ───────────────────────────────────────────────────────────────────
+
+struct ImageInfo {
+ VkExtent2D extent = {0,0};
+ VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
+ VkImageUsageFlags usage = VK_IMAGE_USAGE_STORAGE_BIT|VK_IMAGE_USAGE_SAMPLED_BIT;
+ VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT;
+};
+
+class Image {
+public:
+ Image() = default;
+ // Device-local allocation
+ Image(const Device& dev, const ImageInfo& info);
+#ifdef __ANDROID__
+ // AHardwareBuffer import
+ Image(const Device& dev, const ImageInfo& info, AHardwareBuffer* ahb);
+#endif
+ void destroy(const Device& dev);
+
+ VkImage handle() const { return image_; }
+ VkImageView view() const { return view_; }
+ VkExtent2D extent() const { return extent_; }
+ VkFormat format() const { return format_; }
+ bool external() const { return external_; }
+ bool valid() const { return image_ != VK_NULL_HANDLE; }
+
+ VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+private:
+ VkImage image_ = VK_NULL_HANDLE;
+ VkDeviceMemory memory_ = VK_NULL_HANDLE;
+ VkImageView view_ = VK_NULL_HANDLE;
+ VkExtent2D extent_ = {};
+ VkFormat format_ = VK_FORMAT_UNDEFINED;
+ bool external_= false;
+};
+
+// ─── Buffer ──────────────────────────────────────────────────────────────────
+
+class Buffer {
+public:
+ Buffer() = default;
+ Buffer(const Device& dev, VkDeviceSize size, VkBufferUsageFlags usage,
+ const void* initialData = nullptr);
+ void destroy(const Device& dev);
+ void write(const Device& dev, const void* data, VkDeviceSize size);
+
+ VkBuffer handle() const { return buffer_; }
+ VkDeviceSize size() const { return size_; }
+ bool valid() const { return buffer_ != VK_NULL_HANDLE; }
+ const void* mapped() const { return mapped_; }
+
+private:
+ VkBuffer buffer_ = VK_NULL_HANDLE;
+ VkDeviceMemory memory_ = VK_NULL_HANDLE;
+ VkDeviceSize size_ = 0;
+ void* mapped_ = nullptr;
+};
+
+// ─── CommandPool / CommandBuffer ─────────────────────────────────────────────
+
+class CommandPool {
+public:
+ CommandPool() = default;
+ CommandPool(const Device& dev);
+ void destroy(const Device& dev);
+ VkCommandPool handle() const { return pool_; }
+ bool valid() const { return pool_ != VK_NULL_HANDLE; }
+private:
+ VkCommandPool pool_ = VK_NULL_HANDLE;
+};
+
+class CommandBuffer {
+public:
+ CommandBuffer() = default;
+ CommandBuffer(const Device& dev, const CommandPool& pool);
+ void destroy(const Device& dev, const CommandPool& pool);
+ void begin();
+ void end();
+ void submit(const Device& dev, VkFence fence = VK_NULL_HANDLE);
+ void submitAndWait(const Device& dev);
+ VkCommandBuffer handle() const { return buf_; }
+ bool valid() const { return buf_ != VK_NULL_HANDLE; }
+private:
+ VkCommandBuffer buf_ = VK_NULL_HANDLE;
+};
+
+// ─── Fence ───────────────────────────────────────────────────────────────────
+
+class Fence {
+public:
+ Fence() = default;
+ explicit Fence(const Device& dev, bool signaled = false);
+ void destroy(const Device& dev);
+ void wait(const Device& dev);
+ void reset(const Device& dev);
+ VkFence handle() const { return fence_; }
+ bool valid() const { return fence_ != VK_NULL_HANDLE; }
+private:
+ VkFence fence_ = VK_NULL_HANDLE;
+};
+
+// ─── Sampler ─────────────────────────────────────────────────────────────────
+
+class Sampler {
+public:
+ Sampler() = default;
+ Sampler(const Device& dev, VkFilter filter = VK_FILTER_LINEAR,
+ VkSamplerAddressMode mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER);
+ void destroy(const Device& dev);
+ VkSampler handle() const { return sampler_; }
+ bool valid() const { return sampler_ != VK_NULL_HANDLE; }
+private:
+ VkSampler sampler_ = VK_NULL_HANDLE;
+};
+
+// ─── Descriptor helpers ──────────────────────────────────────────────────────
+
+struct DescriptorBinding {
+ uint32_t binding;
+ VkDescriptorType type;
+ uint32_t count = 1;
+};
+
+class DescriptorPool {
+public:
+ DescriptorPool() = default;
+ DescriptorPool(const Device& dev, uint32_t maxSets,
+ const std::vector<VkDescriptorPoolSize>& sizes);
+ void destroy(const Device& dev);
+ VkDescriptorPool handle() const { return pool_; }
+ bool valid() const { return pool_ != VK_NULL_HANDLE; }
+private:
+ VkDescriptorPool pool_ = VK_NULL_HANDLE;
+};
+
+class DescriptorSetLayout {
+public:
+ DescriptorSetLayout() = default;
+ DescriptorSetLayout(const Device& dev,
+ const std::vector<DescriptorBinding>& bindings);
+ void destroy(const Device& dev);
+ VkDescriptorSetLayout handle() const { return layout_; }
+ bool valid() const { return layout_ != VK_NULL_HANDLE; }
+private:
+ VkDescriptorSetLayout layout_ = VK_NULL_HANDLE;
+};
+
+class DescriptorSet {
+public:
+ DescriptorSet() = default;
+ DescriptorSet(const Device& dev, VkDescriptorPool pool,
+ VkDescriptorSetLayout layout);
+
+ void bindUBO(const Device& dev, uint32_t binding, const Buffer& buf);
+ void bindStorageImage(const Device& dev, uint32_t binding, const Image& img);
+ void bindSampledImage(const Device& dev, uint32_t binding, const Image& img,
+ VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+ void bindSampler(const Device& dev, uint32_t binding, const Sampler& sampler);
+ void bindCombinedImageSampler(const Device& dev, uint32_t binding,
+ const Image& img, const Sampler& sampler,
+ VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+ VkDescriptorSet handle() const { return set_; }
+ bool valid() const { return set_ != VK_NULL_HANDLE; }
+private:
+ VkDescriptorSet set_ = VK_NULL_HANDLE;
+};
+
+// ─── Pipeline ────────────────────────────────────────────────────────────────
+
+class ComputePipeline {
+public:
+ ComputePipeline() = default;
+ ComputePipeline(const Device& dev,
+ const uint8_t* spirvData, size_t spirvBytes,
+ VkDescriptorSetLayout descriptorLayout,
+ const VkPushConstantRange* pushRange = nullptr);
+ void destroy(const Device& dev);
+ void bind(VkCommandBuffer cmd) const;
+ void bindDescriptorSet(VkCommandBuffer cmd, VkDescriptorSet set) const;
+ void dispatch(VkCommandBuffer cmd, uint32_t gx, uint32_t gy, uint32_t gz = 1) const;
+ VkPipeline handle() const { return pipeline_; }
+ VkPipelineLayout layout() const { return layout_; }
+ bool valid() const { return pipeline_ != VK_NULL_HANDLE; }
+private:
+ VkPipeline pipeline_= VK_NULL_HANDLE;
+ VkPipelineLayout layout_ = VK_NULL_HANDLE;
+};
+
+// ─── Barrier helpers ─────────────────────────────────────────────────────────
+
+void imageBarrier(VkCommandBuffer cmd, VkImage image,
+ VkPipelineStageFlags srcStage, VkAccessFlags srcAccess,
+ VkPipelineStageFlags dstStage, VkAccessFlags dstAccess,
+ VkImageLayout oldLayout, VkImageLayout newLayout,
+ uint32_t srcQueueFamily = VK_QUEUE_FAMILY_IGNORED,
+ uint32_t dstQueueFamily = VK_QUEUE_FAMILY_IGNORED);
+
+void acquireFromExternal(VkCommandBuffer cmd, const Image& img,
+ uint32_t dstQueueFamily,
+ VkAccessFlags dstAccess);
+
+void releaseToExternal(VkCommandBuffer cmd, const Image& img,
+ uint32_t srcQueueFamily,
+ VkAccessFlags srcAccess);
+
+// ─── Utilities ───────────────────────────────────────────────────────────────
+
+uint32_t findMemoryType(const Device& dev, uint32_t typeBits,
+ VkMemoryPropertyFlags props);
+
+} // namespace bionic_fg::vk