diff --git a/justfile b/justfile index bda2788..c0661a2 100644 --- a/justfile +++ b/justfile @@ -3,9 +3,28 @@ default: build -# Build the project +CC := "clang" +CXX := "clang++" +VK_FLAGS := "-DVK_NO_PROTOTYPES -I$VULKAN_SDK/include -I$VULKAN_SDK/include/vma" +BUILDDIR := "build" + +# Build the project (object files only — no entry point yet) build: - @echo "TODO: implement build" + mkdir -p {{BUILDDIR}} + {{CXX}} -g -c -Wno-nullability-completeness {{VK_FLAGS}} \ + src/prism/rhi/vulkan/profiles/vulkan_profiles.cpp \ + -o {{BUILDDIR}}/vulkan_profiles.o + {{CXX}} -g -c -Wno-nullability-completeness {{VK_FLAGS}} \ + src/prism/rhi/vulkan/pr_rhi_vk_vma.cpp \ + -o {{BUILDDIR}}/pr_rhi_vk_vma.o + {{CC}} -g -c {{VK_FLAGS}} $VULKAN_SDK/include/volk/volk.c -o {{BUILDDIR}}/volk.o + {{CC}} -g -c {{VK_FLAGS}} src/prism/rhi/vulkan/pr_rhi_vk.c -o {{BUILDDIR}}/pr_rhi_vk.o + {{CC}} -g -c src/wapp/wapp.c -o {{BUILDDIR}}/wapp.o + @echo "--- build done (objects in {{BUILDDIR}}/) ---" + +# Clean object files +clean: + rm -rf {{BUILDDIR}} # Run linter / typecheck lint: diff --git a/src/prism/rhi/vulkan/pr_rhi_vk.c b/src/prism/rhi/vulkan/pr_rhi_vk.c new file mode 100644 index 0000000..1f0c95c --- /dev/null +++ b/src/prism/rhi/vulkan/pr_rhi_vk.c @@ -0,0 +1,2014 @@ +// vim:fileencoding=utf-8:foldmethod=marker +// +// Vulkan backend — single-file implementation of the RHI API. +// Uses volk (dynamic loader) + vulkan_profiles + VMA. +// Profile: VP_PRISM_DESKTOP_2026 + +#include "pr_rhi_vk.h" +#include "profiles/vulkan_profiles.h" +#include +#include +#include + +// ============================================================================ +// Typedefs for wapp arrays of Vulkan handle types +// ============================================================================ + +typedef VkPhysicalDevice *VkPhysicalDeviceArray; +typedef VkQueueFamilyProperties2 *VkQueueFamilyProperties2Array; +typedef VkImage *VkImageArray; +typedef VkImageView *VkImageViewArray; +typedef VkDescriptorSetLayout *VkDescriptorSetLayoutArray; +typedef VkPushConstantRange *VkPushConstantRangeArray; +typedef VkCommandBuffer *VkCommandBufferArray; + +// ============================================================================ +// Helpers +// ============================================================================ + +wp_intern void _abort(const char *msg); +wp_intern void _checkVk(VkResult res, const char *site); +wp_intern void _checkVkBool(VkResult res, const char *site); +wp_intern VkFormat _toVkFormat(PrRhiFormat fmt); +wp_intern VkImageUsageFlags _toVkTextureUsage(PrRhiTextureUsage usage); +wp_intern VkBufferUsageFlags _toVkBufferUsage(PrRhiBufferUsage usage); +wp_intern VkMemoryPropertyFlags _toVkMemoryProperty(PrRhiMemoryUsage mem); +wp_intern VmaAllocationCreateFlags _toVmaFlags(PrRhiMemoryUsage mem); +wp_intern VkImageLayout _toVkImageLayout(PrRhiImageLayout layout); +wp_intern VkShaderStageFlags _toVkShaderStage(PrRhiShaderStage stage); +wp_intern VkDescriptorType _toVkDescriptorType(PrRhiDescriptorType type); +wp_intern VkDescriptorBindingFlags _toVkBindingFlags(PrRhiDescriptorBindingFlag flags); +wp_intern VkCompareOp _toVkCompareOp(PrRhiCompareOp op); +wp_intern VkPrimitiveTopology _toVkTopology(PrRhiPrimitiveTopology topo); +wp_intern VkIndexType _toVkIndexType(PrRhiIndexType it); +wp_intern VkPresentModeKHR _toVkPresentMode(PrRhiPresentMode mode); +wp_intern VkFilter _toVkFilter(PrRhiFilter f); +wp_intern VkSamplerMipmapMode _toVkMipmapMode(PrRhiMipmapMode m); +wp_intern VkSamplerAddressMode _toVkAddressMode(PrRhiAddressMode m); + +wp_persist const VpProfileProperties _profile = { + .profileName = VP_PRISM_DESKTOP_2026_NAME, + .specVersion = VP_PRISM_DESKTOP_2026_SPEC_VERSION, +}; + +wp_persist const VkFormat _default_image_format = VK_FORMAT_B8G8R8A8_SRGB; +wp_persist const VkColorSpaceKHR _default_colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + +// ============================================================================ +// Helper implementations +// ============================================================================ + +void _abort(const char *msg) { + // TODO: use wapp abort when available + __builtin_trap(); +} + +void _checkVk(VkResult res, const char *site) { + if (res != VK_SUCCESS) _abort(site); +} + +void _checkVkBool(VkResult res, const char *site) { + if (res < VK_SUCCESS) _abort(site); +} + +VkFormat _toVkFormat(PrRhiFormat fmt) { + switch (fmt) { + case PR_RHI_FORMAT_R8G8B8A8_SRGB: return VK_FORMAT_R8G8B8A8_SRGB; + case PR_RHI_FORMAT_R8G8B8A8_UNORM: return VK_FORMAT_R8G8B8A8_UNORM; + case PR_RHI_FORMAT_R16G16B16A16_SFLOAT: return VK_FORMAT_R16G16B16A16_SFLOAT; + case PR_RHI_FORMAT_R32G32B32A32_SFLOAT: return VK_FORMAT_R32G32B32A32_SFLOAT; + case PR_RHI_FORMAT_R32G32B32_SFLOAT: return VK_FORMAT_R32G32B32_SFLOAT; + case PR_RHI_FORMAT_R32G32_SFLOAT: return VK_FORMAT_R32G32_SFLOAT; + case PR_RHI_FORMAT_R32_SFLOAT: return VK_FORMAT_R32_SFLOAT; + case PR_RHI_FORMAT_D24_UNORM_S8_UINT: return VK_FORMAT_D24_UNORM_S8_UINT; + case PR_RHI_FORMAT_D32_SFLOAT_S8_UINT: return VK_FORMAT_D32_SFLOAT_S8_UINT; + case PR_RHI_FORMAT_B8G8R8A8_SRGB: return VK_FORMAT_B8G8R8A8_SRGB; + default: return VK_FORMAT_UNDEFINED; + } +} + +VkImageUsageFlags _toVkTextureUsage(PrRhiTextureUsage usage) { + VkImageUsageFlags flags = 0; + if (usage & PR_RHI_TEXTURE_USAGE_SAMPLED) flags |= VK_IMAGE_USAGE_SAMPLED_BIT; + if (usage & PR_RHI_TEXTURE_USAGE_COLOR_ATTACHMENT) flags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + if (usage & PR_RHI_TEXTURE_USAGE_DEPTH_ATTACHMENT) flags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + if (usage & PR_RHI_TEXTURE_USAGE_STORAGE) flags |= VK_IMAGE_USAGE_STORAGE_BIT; + if (usage & PR_RHI_TEXTURE_USAGE_TRANSFER_SRC) flags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + if (usage & PR_RHI_TEXTURE_USAGE_TRANSFER_DST) flags |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + return flags; +} + +VkBufferUsageFlags _toVkBufferUsage(PrRhiBufferUsage usage) { + VkBufferUsageFlags flags = 0; + if (usage & PR_RHI_BUFFER_USAGE_VERTEX) flags |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + if (usage & PR_RHI_BUFFER_USAGE_INDEX) flags |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + if (usage & PR_RHI_BUFFER_USAGE_UNIFORM) flags |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + if (usage & PR_RHI_BUFFER_USAGE_STORAGE) flags |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + if (usage & PR_RHI_BUFFER_USAGE_TRANSFER_SRC) flags |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + if (usage & PR_RHI_BUFFER_USAGE_TRANSFER_DST) flags |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; + if (usage & PR_RHI_BUFFER_USAGE_SHADER_DEVICE_ADDRESS) flags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + return flags; +} + +VkMemoryPropertyFlags _toVkMemoryProperty(PrRhiMemoryUsage mem) { + switch (mem) { + case PR_RHI_MEMORY_GPU_ONLY: return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + case PR_RHI_MEMORY_CPU_TO_GPU: return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + case PR_RHI_MEMORY_CPU_ONLY: return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + default: return 0; + } +} + +VmaAllocationCreateFlags _toVmaFlags(PrRhiMemoryUsage mem) { + switch (mem) { + case PR_RHI_MEMORY_GPU_ONLY: + return 0; + case PR_RHI_MEMORY_CPU_TO_GPU: + return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT; + case PR_RHI_MEMORY_CPU_ONLY: + return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT; + default: + return 0; + } +} + +VkImageLayout _toVkImageLayout(PrRhiImageLayout layout) { + switch (layout) { + case PR_RHI_LAYOUT_UNDEFINED: return VK_IMAGE_LAYOUT_UNDEFINED; + case PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL: return VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + case PR_RHI_LAYOUT_READ_ONLY_OPTIMAL: return VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL; + case PR_RHI_LAYOUT_TRANSFER_SRC_OPTIMAL: return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + case PR_RHI_LAYOUT_TRANSFER_DST_OPTIMAL: return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + case PR_RHI_LAYOUT_PRESENT_SRC: return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + case PR_RHI_LAYOUT_GENERAL: return VK_IMAGE_LAYOUT_GENERAL; + default: return VK_IMAGE_LAYOUT_UNDEFINED; + } +} + +VkShaderStageFlags _toVkShaderStage(PrRhiShaderStage stage) { + VkShaderStageFlags flags = 0; + if (stage & PR_RHI_SHADER_STAGE_VERTEX) flags |= VK_SHADER_STAGE_VERTEX_BIT; + if (stage & PR_RHI_SHADER_STAGE_FRAGMENT) flags |= VK_SHADER_STAGE_FRAGMENT_BIT; + if (stage & PR_RHI_SHADER_STAGE_COMPUTE) flags |= VK_SHADER_STAGE_COMPUTE_BIT; + return flags; +} + +VkDescriptorType _toVkDescriptorType(PrRhiDescriptorType type) { + switch (type) { + case PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + case PR_RHI_DESCRIPTOR_TYPE_STORAGE_IMAGE: return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + case PR_RHI_DESCRIPTOR_TYPE_UNIFORM_BUFFER: return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + case PR_RHI_DESCRIPTOR_TYPE_STORAGE_BUFFER: return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + default: return VK_DESCRIPTOR_TYPE_MAX_ENUM; + } +} + +VkDescriptorBindingFlags _toVkBindingFlags(PrRhiDescriptorBindingFlag flags) { + VkDescriptorBindingFlags vk = 0; + if (flags & PR_RHI_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND) vk |= VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT; + if (flags & PR_RHI_DESCRIPTOR_BINDING_PARTIALLY_BOUND) vk |= VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT; + if (flags & PR_RHI_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT) vk |= VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT; + return vk; +} + +VkCompareOp _toVkCompareOp(PrRhiCompareOp op) { + switch (op) { + case PR_RHI_COMPARE_OP_NEVER: return VK_COMPARE_OP_NEVER; + case PR_RHI_COMPARE_OP_LESS: return VK_COMPARE_OP_LESS; + case PR_RHI_COMPARE_OP_EQUAL: return VK_COMPARE_OP_EQUAL; + case PR_RHI_COMPARE_OP_LESS_OR_EQUAL: return VK_COMPARE_OP_LESS_OR_EQUAL; + case PR_RHI_COMPARE_OP_GREATER: return VK_COMPARE_OP_GREATER; + case PR_RHI_COMPARE_OP_NOT_EQUAL: return VK_COMPARE_OP_NOT_EQUAL; + case PR_RHI_COMPARE_OP_GREATER_OR_EQUAL: return VK_COMPARE_OP_GREATER_OR_EQUAL; + case PR_RHI_COMPARE_OP_ALWAYS: return VK_COMPARE_OP_ALWAYS; + default: return VK_COMPARE_OP_ALWAYS; + } +} + +VkPrimitiveTopology _toVkTopology(PrRhiPrimitiveTopology topo) { + switch (topo) { + case PR_RHI_TOPOLOGY_POINT_LIST: return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + case PR_RHI_TOPOLOGY_LINE_LIST: return VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + case PR_RHI_TOPOLOGY_LINE_STRIP: return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; + case PR_RHI_TOPOLOGY_TRIANGLE_LIST: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + case PR_RHI_TOPOLOGY_TRIANGLE_STRIP: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + case PR_RHI_TOPOLOGY_TRIANGLE_FAN: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; + case PR_RHI_TOPOLOGY_LINE_LIST_WITH_ADJACENCY: return VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY; + case PR_RHI_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY: return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY; + case PR_RHI_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY; + case PR_RHI_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY; + case PR_RHI_TOPOLOGY_PATCH_LIST: return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; + default: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + } +} + +VkIndexType _toVkIndexType(PrRhiIndexType it) { + switch (it) { + case PR_RHI_INDEX_TYPE_UINT16: return VK_INDEX_TYPE_UINT16; + case PR_RHI_INDEX_TYPE_UINT32: return VK_INDEX_TYPE_UINT32; + default: return VK_INDEX_TYPE_UINT16; + } +} + +VkPresentModeKHR _toVkPresentMode(PrRhiPresentMode mode) { + switch (mode) { + case PR_RHI_PRESENT_MODE_IMMEDIATE: return VK_PRESENT_MODE_IMMEDIATE_KHR; + case PR_RHI_PRESENT_MODE_FIFO: return VK_PRESENT_MODE_FIFO_KHR; + case PR_RHI_PRESENT_MODE_MAILBOX: return VK_PRESENT_MODE_MAILBOX_KHR; + default: return VK_PRESENT_MODE_FIFO_KHR; + } +} + +VkFilter _toVkFilter(PrRhiFilter f) { + switch (f) { + case PR_RHI_FILTER_NEAREST: return VK_FILTER_NEAREST; + case PR_RHI_FILTER_LINEAR: return VK_FILTER_LINEAR; + default: return VK_FILTER_NEAREST; + } +} + +VkSamplerMipmapMode _toVkMipmapMode(PrRhiMipmapMode m) { + switch (m) { + case PR_RHI_MIPMAP_MODE_NEAREST: return VK_SAMPLER_MIPMAP_MODE_NEAREST; + case PR_RHI_MIPMAP_MODE_LINEAR: return VK_SAMPLER_MIPMAP_MODE_LINEAR; + default: return VK_SAMPLER_MIPMAP_MODE_NEAREST; + } +} + +VkSamplerAddressMode _toVkAddressMode(PrRhiAddressMode m) { + switch (m) { + case PR_RHI_ADDRESS_MODE_REPEAT: return VK_SAMPLER_ADDRESS_MODE_REPEAT; + case PR_RHI_ADDRESS_MODE_CLAMP_TO_EDGE: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + case PR_RHI_ADDRESS_MODE_CLAMP_TO_BORDER: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + default: return VK_SAMPLER_ADDRESS_MODE_REPEAT; + } +} + +// ============================================================================ +// Instance +// ============================================================================ + +PrRhiInstance *prRhiCreateInstanceVk(PrRhiInstanceDesc desc, WpAllocator *alloc) { + (void)desc; + + VkBool32 supported = VK_FALSE; + _checkVk(vpGetInstanceProfileSupport(NULL, &_profile, &supported), "vpGetInstanceProfileSupport"); + if (!supported) _abort("VP_PRISM_DESKTOP_2026 not supported at instance level"); + + VkApplicationInfo app_info = {}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pApplicationName = desc.app_name ? desc.app_name : "Prism"; + app_info.applicationVersion = desc.app_version; + app_info.apiVersion = VP_PRISM_DESKTOP_2026_MIN_API_VERSION; + + VkInstanceCreateInfo instance_info = {}; + instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instance_info.pApplicationInfo = &app_info; + + if (desc.extra_extensions && wpArrayCount(desc.extra_extensions) > 0) { + instance_info.enabledExtensionCount = (u32)wpArrayCount(desc.extra_extensions); + instance_info.ppEnabledExtensionNames = (const char *const *)desc.extra_extensions; + } + + VpInstanceCreateInfo vp_instance_info = {}; + vp_instance_info.pCreateInfo = &instance_info; + vp_instance_info.enabledFullProfileCount = 1; + vp_instance_info.pEnabledFullProfiles = &_profile; + + VkInstance vk_instance = VK_NULL_HANDLE; + _checkVk(vpCreateInstance(&vp_instance_info, NULL, &vk_instance), "vpCreateInstance"); + + volkLoadInstance(vk_instance); + + PrRhiInstance *inst = wpMemAllocatorAlloc(alloc, sizeof(PrRhiInstance)); + if (!inst) _abort("alloc failed for PrRhiInstance"); + inst->handle = vk_instance; + inst->debug_messenger = NULL; + + return inst; +} + +void prRhiDestroyInstanceVk(PrRhiInstance *inst, WpAllocator *alloc) { + if (!inst) return; + vkDestroyInstance((VkInstance)inst->handle, NULL); + wpMemAllocatorFree(alloc, (void**)&inst, sizeof(PrRhiInstance)); +} + +// ============================================================================ +// Physical device enumeration +// ============================================================================ + +PrRhiPhysicalDeviceArray prRhiGetPhysicalDevicesVk(PrRhiInstance *inst, const WpAllocator *scratch) { + VkInstance vk_inst = (VkInstance)inst->handle; + + u32 count = 0; + _checkVk(vkEnumeratePhysicalDevices(vk_inst, &count, NULL), "vkEnumeratePhysicalDevices"); + if (count == 0) _abort("no physical devices"); + + VkPhysicalDeviceArray vk_devices = wpArrayAllocCapacity(VkPhysicalDevice, scratch, count, WP_ARRAY_INIT_FILLED); + if (!vk_devices) _abort("alloc failed for physical device array"); + + _checkVk(vkEnumeratePhysicalDevices(vk_inst, &count, vk_devices), "vkEnumeratePhysicalDevices"); + wpArraySetCount(vk_devices, count); + + PrRhiPhysicalDeviceArray pdevs = wpArrayAllocCapacity(PrRhiPhysicalDevice *, scratch, count, WP_ARRAY_INIT_NONE); + if (!pdevs) _abort("alloc failed for PrRhiPhysicalDevice array"); + + for (u32 i = 0; i < count; ++i) { + PrRhiPhysicalDevice *pdev = (PrRhiPhysicalDevice *)wpMemAllocatorAlloc(scratch, sizeof(PrRhiPhysicalDevice)); + if (!pdev) _abort("alloc failed for PrRhiPhysicalDevice"); + pdev->handle = vk_devices[i]; + pdev->instance = inst; + wpArrayAppendCapped(PrRhiPhysicalDevice *, pdevs, &pdev); + } + + return pdevs; +} + +void prRhiGetPhysicalDeviceNameVk(PrRhiPhysicalDevice *pdev, WpStr8 *out) { + VkPhysicalDeviceProperties2 props = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; + vkGetPhysicalDeviceProperties2((VkPhysicalDevice)pdev->handle, &props); + const char *name = props.properties.deviceName; + out->buf = (c8*)name; + out->size = strlen(name); + out->capacity = 0; +} + +void prRhiGetPhysicalDeviceDriverInfoVk(PrRhiPhysicalDevice *pdev, WpStr8 *out) { + VkPhysicalDeviceDriverProperties driver_props = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES }; + VkPhysicalDeviceProperties2 props = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, .pNext = &driver_props }; + vkGetPhysicalDeviceProperties2((VkPhysicalDevice)pdev->handle, &props); + const char *info = driver_props.driverInfo; + out->buf = (c8*)info; + out->size = strlen(info); + out->capacity = 0; +} + +// ============================================================================ +// Surface +// ============================================================================ + +PrRhiSurface *prRhiCreateSurfaceVk(PrRhiInstance *inst, void *window_handle, WpAllocator *alloc) { + (void)inst; + // window_handle is already a VkSurfaceKHR created by the caller (e.g. via SDL). + PrRhiSurface *surface = wpMemAllocatorAlloc(alloc, sizeof(PrRhiSurface)); + if (!surface) _abort("alloc failed for PrRhiSurface"); + surface->handle = window_handle; + return surface; +} + +void prRhiDestroySurfaceVk(PrRhiInstance *inst, PrRhiSurface *surface, WpAllocator *alloc) { + if (!surface) return; + vkDestroySurfaceKHR((VkInstance)inst->handle, (VkSurfaceKHR)surface->handle, NULL); + wpMemAllocatorFree(alloc, (void**)&surface, sizeof(PrRhiSurface)); +} + +PrRhiSurfaceCapabilities prRhiGetSurfaceCapabilitiesVk(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface) { + VkSurfaceCapabilitiesKHR caps = {}; + _checkVk(vkGetPhysicalDeviceSurfaceCapabilitiesKHR((VkPhysicalDevice)pdev->handle, + (VkSurfaceKHR)surface->handle, &caps), + "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); + + PrRhiSurfaceCapabilities out = { + .min_image_count = caps.minImageCount, + .max_image_count = caps.maxImageCount, + .current_width = caps.currentExtent.width, + .current_height = caps.currentExtent.height, + .min_width = caps.minImageExtent.width, + .min_height = caps.minImageExtent.height, + .max_width = caps.maxImageExtent.width, + .max_height = caps.maxImageExtent.height, + }; + return out; +} + +// ============================================================================ +// Device +// ============================================================================ + +PrRhiDevice *prRhiCreateDeviceVk(PrRhiPhysicalDevice *pdev, PrRhiSurface *surface, + PrRhiDeviceDesc desc, WpAllocator *alloc) { + VkInstance vk_inst = (VkInstance)pdev->instance->handle; + VkPhysicalDevice vk_pdev = (VkPhysicalDevice)pdev->handle; + VkSurfaceKHR vk_surface = (VkSurfaceKHR)surface->handle; + + // Select queue family + u32 queue_family_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties2(vk_pdev, &queue_family_count, NULL); + if (queue_family_count == 0) _abort("no queue families"); + + VkQueueFamilyProperties2Array qf_props = + wpArrayAllocCapacity(VkQueueFamilyProperties2, alloc, queue_family_count, WP_ARRAY_INIT_FILLED); + if (!qf_props) _abort("alloc failed for queue family props"); + + for (u32 i = 0; i < queue_family_count; ++i) { + qf_props[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2; + } + vkGetPhysicalDeviceQueueFamilyProperties2(vk_pdev, &queue_family_count, qf_props); + wpArraySetCount(qf_props, queue_family_count); + + u32 family_index = UINT32_MAX; + for (u32 i = 0; i < queue_family_count; ++i) { + if (qf_props[i].queueFamilyProperties.queueFlags & + (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT)) { + family_index = i; + break; + } + } + if (family_index == UINT32_MAX) _abort("no suitable queue family"); + + // Check presentation support + VkBool32 present_supported = VK_FALSE; + _checkVk(vkGetPhysicalDeviceSurfaceSupportKHR(vk_pdev, family_index, vk_surface, &present_supported), + "vkGetPhysicalDeviceSurfaceSupportKHR"); + if (!present_supported) _abort("no presentation support"); + + wpArrayDealloc(VkQueueFamilyProperties2, alloc, &qf_props); + + // Check device profile support + VkBool32 profile_supported = VK_FALSE; + _checkVk(vpGetPhysicalDeviceProfileSupport(vk_inst, vk_pdev, &_profile, &profile_supported), + "vpGetPhysicalDeviceProfileSupport"); + if (!profile_supported) _abort("device does not support profile"); + + // Create device + f32 queue_priority = 1.0f; + VkDeviceQueueCreateInfo queue_create_info = {}; + queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info.queueCount = 1; + queue_create_info.queueFamilyIndex = family_index; + queue_create_info.pQueuePriorities = &queue_priority; + + VkDeviceCreateInfo device_info = {}; + device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_info.queueCreateInfoCount = 1; + device_info.pQueueCreateInfos = &queue_create_info; + + VpDeviceCreateInfo vp_device_info = {}; + vp_device_info.pCreateInfo = &device_info; + vp_device_info.enabledFullProfileCount = 1; + vp_device_info.pEnabledFullProfiles = &_profile; + + VkDevice vk_device = VK_NULL_HANDLE; + _checkVk(vpCreateDevice(vk_pdev, &vp_device_info, NULL, &vk_device), "vpCreateDevice"); + + volkLoadDevice(vk_device); + + VkQueue vk_queue = VK_NULL_HANDLE; + vkGetDeviceQueue(vk_device, family_index, 0, &vk_queue); + + // Create VMA allocator + VmaVulkanFunctions vk_functions = {}; + vk_functions.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + vk_functions.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + vk_functions.vkCreateImage = vkCreateImage; + + VmaAllocatorCreateInfo vma_info = {}; + vma_info.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT | + VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT | + VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT; + vma_info.instance = vk_inst; + vma_info.physicalDevice = vk_pdev; + vma_info.device = vk_device; + vma_info.pVulkanFunctions = &vk_functions; + + VmaAllocator vma_allocator = VK_NULL_HANDLE; + _checkVk(vmaCreateAllocator(&vma_info, &vma_allocator), "vmaCreateAllocator"); + + PrRhiDevice *device = wpMemAllocatorAlloc(alloc, sizeof(PrRhiDevice)); + if (!device) _abort("alloc failed for PrRhiDevice"); + device->handle = vk_device; + device->queue = vk_queue; + device->queue_family_index = family_index; + device->present_mode = _toVkPresentMode(desc.present_mode); + device->physical_device = vk_pdev; + device->allocator = vma_allocator; + + return device; +} + +void prRhiDestroyDeviceVk(PrRhiDevice *device, WpAllocator *alloc) { + if (!device) return; + vkDeviceWaitIdle((VkDevice)device->handle); + vmaDestroyAllocator((VmaAllocator)device->allocator); + vkDestroyDevice((VkDevice)device->handle, NULL); + wpMemAllocatorFree(alloc, (void**)&device, sizeof(PrRhiDevice)); +} + +void prRhiDeviceWaitIdleVk(PrRhiDevice *device) { + _checkVk(vkDeviceWaitIdle((VkDevice)device->handle), "vkDeviceWaitIdle"); +} + +// ============================================================================ +// Swapchain +// ============================================================================ + +static PrRhiTexture *_createSwapchainTexture(PrRhiDevice *device, VkImage vk_image, + VkFormat vk_format, u32 width, u32 height, + VkImageView vk_view, WpAllocator *alloc) { + PrRhiTexture *tex = wpMemAllocatorAlloc(alloc, sizeof(PrRhiTexture)); + if (!tex) _abort("alloc failed for swapchain texture"); + tex->image = vk_image; + tex->view = vk_view; + tex->allocation = NULL; + tex->width = width; + tex->height = height; + tex->format = vk_format; + return tex; +} + +static VkFormat _pickDepthFormat(VkPhysicalDevice vk_pdev) { + const VkFormat candidates[] = { VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT }; + for (u32 i = 0; i < (u32)(sizeof(candidates) / sizeof(candidates[0])); ++i) { + VkFormatProperties2 props = { .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2 }; + vkGetPhysicalDeviceFormatProperties2(vk_pdev, candidates[i], &props); + if (props.formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) { + return candidates[i]; + } + } + return VK_FORMAT_UNDEFINED; +} + +PrRhiSwapchain *prRhiCreateSwapchainVk(PrRhiDevice *device, PrRhiSwapchainDesc desc, + WpAllocator *alloc) { + VkDevice vk_device = (VkDevice)device->handle; + VkPhysicalDevice vk_pdev = (VkPhysicalDevice)device->physical_device; + VkSurfaceKHR vk_surface = (VkSurfaceKHR)desc.surface->handle; + + VkSurfaceCapabilitiesKHR caps = {}; + _checkVk(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk_pdev, vk_surface, &caps), + "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); + + VkExtent2D extent = caps.currentExtent; + if (extent.width == 0xffffffff) { + extent.width = desc.width; + extent.height = desc.height; + } + + VkSwapchainCreateInfoKHR swapchain_info = {}; + swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapchain_info.surface = vk_surface; + swapchain_info.minImageCount = caps.minImageCount; + swapchain_info.imageFormat = _default_image_format; + swapchain_info.imageColorSpace = _default_colorspace; + swapchain_info.imageExtent = extent; + swapchain_info.imageArrayLayers = 1; + swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + swapchain_info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + swapchain_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + swapchain_info.presentMode = (VkPresentModeKHR)device->present_mode; + + VkSwapchainKHR vk_swapchain = VK_NULL_HANDLE; + _checkVk(vkCreateSwapchainKHR(vk_device, &swapchain_info, NULL, &vk_swapchain), + "vkCreateSwapchainKHR"); + + u32 image_count = 0; + _checkVk(vkGetSwapchainImagesKHR(vk_device, vk_swapchain, &image_count, NULL), + "vkGetSwapchainImagesKHR"); + + VkImageArray vk_images = wpArrayAllocCapacity(VkImage, alloc, image_count, WP_ARRAY_INIT_FILLED); + if (!vk_images) _abort("alloc failed for swapchain VkImage array"); + _checkVk(vkGetSwapchainImagesKHR(vk_device, vk_swapchain, &image_count, vk_images), + "vkGetSwapchainImagesKHR"); + wpArraySetCount(vk_images, image_count); + + PrRhiTextureArray images = wpArrayAllocCapacity(PrRhiTexture *, alloc, image_count, WP_ARRAY_INIT_NONE); + if (!images) _abort("alloc failed for swapchain image array"); + + for (u32 i = 0; i < image_count; ++i) { + VkImageViewCreateInfo view_info = {}; + view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + view_info.image = vk_images[i]; + view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_info.format = _default_image_format; + view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + view_info.subresourceRange.levelCount = 1; + view_info.subresourceRange.layerCount = 1; + + VkImageView vk_view = VK_NULL_HANDLE; + _checkVk(vkCreateImageView(vk_device, &view_info, NULL, &vk_view), "vkCreateImageView"); + + PrRhiTexture *tex = _createSwapchainTexture(device, vk_images[i], _default_image_format, + extent.width, extent.height, vk_view, alloc); + wpArrayAppendCapped(PrRhiTexture *, images, &tex); + } + + wpArrayDealloc(VkImage, alloc, &vk_images); + + PrRhiSwapchain *swapchain = wpMemAllocatorAlloc(alloc, sizeof(PrRhiSwapchain)); + if (!swapchain) _abort("alloc failed for PrRhiSwapchain"); + swapchain->device = device; + swapchain->handle = vk_swapchain; + swapchain->surface = desc.surface; + swapchain->image_count = image_count; + swapchain->images = images; + swapchain->depth = NULL; + swapchain->format = _default_image_format; + swapchain->width = extent.width; + swapchain->height = extent.height; + swapchain->current_image_index = 0; + + // Create depth texture if requested + if (desc.has_depth) { + VkFormat depth_fmt = _pickDepthFormat(vk_pdev); + if (depth_fmt == VK_FORMAT_UNDEFINED) _abort("no suitable depth format"); + + VkImageCreateInfo depth_img_info = {}; + depth_img_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + depth_img_info.imageType = VK_IMAGE_TYPE_2D; + depth_img_info.format = depth_fmt; + depth_img_info.extent.width = extent.width; + depth_img_info.extent.height = extent.height; + depth_img_info.extent.depth = 1; + depth_img_info.mipLevels = 1; + depth_img_info.arrayLayers = 1; + depth_img_info.samples = VK_SAMPLE_COUNT_1_BIT; + depth_img_info.tiling = VK_IMAGE_TILING_OPTIMAL; + depth_img_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + depth_img_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VmaAllocationCreateInfo depth_alloc_info = {}; + depth_alloc_info.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + depth_alloc_info.usage = VMA_MEMORY_USAGE_AUTO; + + VkImage depth_image = VK_NULL_HANDLE; + VmaAllocation depth_allocation = VK_NULL_HANDLE; + _checkVk(vmaCreateImage((VmaAllocator)device->allocator, &depth_img_info, &depth_alloc_info, + &depth_image, &depth_allocation, NULL), + "vmaCreateImage (depth)"); + + VkImageViewCreateInfo depth_view_info = {}; + depth_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + depth_view_info.image = depth_image; + depth_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + depth_view_info.format = depth_fmt; + depth_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + depth_view_info.subresourceRange.levelCount = 1; + depth_view_info.subresourceRange.layerCount = 1; + + VkImageView depth_view = VK_NULL_HANDLE; + _checkVk(vkCreateImageView(vk_device, &depth_view_info, NULL, &depth_view), "vkCreateImageView (depth)"); + + PrRhiTexture *depth_tex = wpMemAllocatorAlloc(alloc, sizeof(PrRhiTexture)); + if (!depth_tex) _abort("alloc failed for depth texture"); + depth_tex->image = depth_image; + depth_tex->view = depth_view; + depth_tex->allocation = depth_allocation; + depth_tex->width = extent.width; + depth_tex->height = extent.height; + depth_tex->format = depth_fmt; + + swapchain->depth = depth_tex; + } + + return swapchain; +} + +void prRhiDestroySwapchainVk(PrRhiDevice *device, PrRhiSwapchain *swapchain, WpAllocator *alloc) { + if (!swapchain) return; + + VkDevice vk_device = (VkDevice)device->handle; + + // Destroy per-image views and PrRhiTexture structs + for (u32 i = 0; i < swapchain->image_count; ++i) { + PrRhiTexture *tex = swapchain->images[i]; + vkDestroyImageView(vk_device, (VkImageView)tex->view, NULL); + // VkImage is owned by swapchain — skip vmaDestroyImage + wpMemAllocatorFree(alloc, (void**)&tex, sizeof(PrRhiTexture)); + } + wpArrayDealloc(PrRhiTexture *, alloc, &swapchain->images); + + // Destroy depth + if (swapchain->depth) { + PrRhiTexture *depth = swapchain->depth; + vkDestroyImageView(vk_device, (VkImageView)depth->view, NULL); + vmaDestroyImage((VmaAllocator)device->allocator, (VkImage)depth->image, + (VmaAllocation)depth->allocation); + wpMemAllocatorFree(alloc, (void**)&depth, sizeof(PrRhiTexture)); + } + + vkDestroySwapchainKHR(vk_device, (VkSwapchainKHR)swapchain->handle, NULL); + wpMemAllocatorFree(alloc, (void**)&swapchain, sizeof(PrRhiSwapchain)); +} + +PrRhiSwapchainResult prRhiAcquireNextImageVk(PrRhiDevice *device, PrRhiSwapchain *swapchain, + PrRhiSemaphore *signal_semaphore, u32 *out_image_index) { + VkSemaphore vk_semaphore = signal_semaphore ? (VkSemaphore)signal_semaphore->handle : VK_NULL_HANDLE; + VkResult res = vkAcquireNextImageKHR((VkDevice)device->handle, + (VkSwapchainKHR)swapchain->handle, + UINT64_MAX, vk_semaphore, VK_NULL_HANDLE, + &swapchain->current_image_index); + if (res == VK_ERROR_OUT_OF_DATE_KHR) return PR_RHI_SWAPCHAIN_OUT_OF_DATE; + _checkVk(res, "vkAcquireNextImageKHR"); + if (out_image_index) *out_image_index = swapchain->current_image_index; + return PR_RHI_SWAPCHAIN_SUCCESS; +} + +PrRhiSwapchainResult prRhiPresentVk(PrRhiDevice *device, PrRhiSwapchain *swapchain, + PrRhiSemaphore *wait_semaphore) { + VkSemaphore vk_semaphore = wait_semaphore ? (VkSemaphore)wait_semaphore->handle : VK_NULL_HANDLE; + + VkPresentInfoKHR present_info = {}; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + present_info.waitSemaphoreCount = wait_semaphore ? 1 : 0; + present_info.pWaitSemaphores = &vk_semaphore; + present_info.swapchainCount = 1; + present_info.pSwapchains = (VkSwapchainKHR *)&swapchain->handle; + present_info.pImageIndices = &swapchain->current_image_index; + + VkResult res = vkQueuePresentKHR((VkQueue)device->queue, &present_info); + if (res == VK_ERROR_OUT_OF_DATE_KHR) return PR_RHI_SWAPCHAIN_OUT_OF_DATE; + _checkVk(res, "vkQueuePresentKHR"); + return PR_RHI_SWAPCHAIN_SUCCESS; +} + +void prRhiRecreateSwapchainVk(PrRhiDevice *device, PrRhiSwapchain **swapchain, + u32 width, u32 height, WpAllocator *alloc) { + PrRhiSwapchain *old = *swapchain; + if (!old) return; + + VkDevice vk_device = (VkDevice)device->handle; + VkPhysicalDevice vk_pdev = (VkPhysicalDevice)device->physical_device; + + VkExtent2D extent = { width, height }; + + VkSwapchainCreateInfoKHR swapchain_info = {}; + swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapchain_info.oldSwapchain = (VkSwapchainKHR)old->handle; + swapchain_info.surface = (VkSurfaceKHR)((PrRhiSurface *)old->surface)->handle; + swapchain_info.minImageCount = old->image_count; + swapchain_info.imageFormat = (VkFormat)old->format; + swapchain_info.imageColorSpace = _default_colorspace; + swapchain_info.imageExtent = extent; + swapchain_info.imageArrayLayers = 1; + swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + swapchain_info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + swapchain_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + swapchain_info.presentMode = (VkPresentModeKHR)device->present_mode; + + VkSwapchainKHR vk_new_swapchain = VK_NULL_HANDLE; + _checkVk(vkCreateSwapchainKHR(vk_device, &swapchain_info, NULL, &vk_new_swapchain), + "vkCreateSwapchainKHR (recreate)"); + + // Destroy old image views and PrRhiTexture structs (but not VkImages — owned by old swapchain) + for (u32 i = 0; i < old->image_count; ++i) { + PrRhiTexture *tex = old->images[i]; + vkDestroyImageView(vk_device, (VkImageView)tex->view, NULL); + wpMemAllocatorFree(alloc, (void**)&tex, sizeof(PrRhiTexture)); + } + wpArrayDealloc(PrRhiTexture *, alloc, &old->images); + + // Destroy old depth + if (old->depth) { + PrRhiTexture *depth = old->depth; + vkDestroyImageView(vk_device, (VkImageView)depth->view, NULL); + vmaDestroyImage((VmaAllocator)device->allocator, (VkImage)depth->image, + (VmaAllocation)depth->allocation); + wpMemAllocatorFree(alloc, (void**)&depth, sizeof(PrRhiTexture)); + } + + // Destroy old swapchain + vkDestroySwapchainKHR(vk_device, (VkSwapchainKHR)old->handle, NULL); + + // Get new swapchain images + u32 new_image_count = 0; + _checkVk(vkGetSwapchainImagesKHR(vk_device, vk_new_swapchain, &new_image_count, NULL), + "vkGetSwapchainImagesKHR (recreate)"); + + VkImageArray vk_images = wpArrayAllocCapacity(VkImage, alloc, new_image_count, WP_ARRAY_INIT_FILLED); + if (!vk_images) _abort("alloc failed for swapchain VkImage array (recreate)"); + _checkVk(vkGetSwapchainImagesKHR(vk_device, vk_new_swapchain, &new_image_count, vk_images), + "vkGetSwapchainImagesKHR (recreate)"); + wpArraySetCount(vk_images, new_image_count); + + PrRhiTextureArray new_images = wpArrayAllocCapacity(PrRhiTexture *, alloc, new_image_count, WP_ARRAY_INIT_NONE); + if (!new_images) _abort("alloc failed for swapchain image array (recreate)"); + + for (u32 i = 0; i < new_image_count; ++i) { + VkImageViewCreateInfo view_info = {}; + view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + view_info.image = vk_images[i]; + view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_info.format = (VkFormat)old->format; + view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + view_info.subresourceRange.levelCount = 1; + view_info.subresourceRange.layerCount = 1; + + VkImageView vk_view = VK_NULL_HANDLE; + _checkVk(vkCreateImageView(vk_device, &view_info, NULL, &vk_view), "vkCreateImageView (recreate)"); + + PrRhiTexture *tex = _createSwapchainTexture(device, vk_images[i], (VkFormat)old->format, + extent.width, extent.height, vk_view, alloc); + wpArrayAppendCapped(PrRhiTexture *, new_images, &tex); + } + + wpArrayDealloc(VkImage, alloc, &vk_images); + + // Recreate depth + PrRhiTexture *new_depth = NULL; + { + VkFormat depth_fmt = _pickDepthFormat(vk_pdev); + if (depth_fmt == VK_FORMAT_UNDEFINED) _abort("no suitable depth format (recreate)"); + + VkImageCreateInfo depth_img_info = {}; + depth_img_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + depth_img_info.imageType = VK_IMAGE_TYPE_2D; + depth_img_info.format = depth_fmt; + depth_img_info.extent.width = extent.width; + depth_img_info.extent.height = extent.height; + depth_img_info.extent.depth = 1; + depth_img_info.mipLevels = 1; + depth_img_info.arrayLayers = 1; + depth_img_info.samples = VK_SAMPLE_COUNT_1_BIT; + depth_img_info.tiling = VK_IMAGE_TILING_OPTIMAL; + depth_img_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + depth_img_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VmaAllocationCreateInfo depth_alloc_info = {}; + depth_alloc_info.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + depth_alloc_info.usage = VMA_MEMORY_USAGE_AUTO; + + VkImage depth_image = VK_NULL_HANDLE; + VmaAllocation depth_allocation = VK_NULL_HANDLE; + _checkVk(vmaCreateImage((VmaAllocator)device->allocator, &depth_img_info, &depth_alloc_info, + &depth_image, &depth_allocation, NULL), + "vmaCreateImage (depth recreate)"); + + VkImageViewCreateInfo depth_view_info = {}; + depth_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + depth_view_info.image = depth_image; + depth_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + depth_view_info.format = depth_fmt; + depth_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + depth_view_info.subresourceRange.levelCount = 1; + depth_view_info.subresourceRange.layerCount = 1; + + VkImageView depth_view = VK_NULL_HANDLE; + _checkVk(vkCreateImageView(vk_device, &depth_view_info, NULL, &depth_view), "vkCreateImageView (depth recreate)"); + + new_depth = wpMemAllocatorAlloc(alloc, sizeof(PrRhiTexture)); + if (!new_depth) _abort("alloc failed for depth texture (recreate)"); + new_depth->image = depth_image; + new_depth->view = depth_view; + new_depth->allocation = depth_allocation; + new_depth->width = extent.width; + new_depth->height = extent.height; + new_depth->format = depth_fmt; + } + + // Update old swapchain in-place + old->handle = vk_new_swapchain; + old->image_count = new_image_count; + old->images = new_images; + old->depth = new_depth; + old->width = extent.width; + old->height = extent.height; + old->current_image_index = 0; + + *swapchain = old; +} + +PrRhiTexture *prRhiGetSwapchainTextureVk(PrRhiSwapchain *swapchain, u32 image_index) { + if (image_index >= swapchain->image_count) return NULL; + return swapchain->images[image_index]; +} + +PrRhiTexture *prRhiGetSwapchainDepthTextureVk(PrRhiSwapchain *swapchain) { + return swapchain->depth; +} + +// ============================================================================ +// Buffers +// ============================================================================ + +PrRhiBuffer *prRhiCreateBufferVk(PrRhiDevice *device, PrRhiBufferDesc desc, + WpAllocator *alloc) { + VkBufferCreateInfo buf_info = {}; + buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buf_info.size = desc.size; + buf_info.usage = _toVkBufferUsage(desc.usage); + + // For device address, we need VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT + VkBufferUsageFlags2CreateInfo usage_flags2 = {}; + if (desc.usage & PR_RHI_BUFFER_USAGE_SHADER_DEVICE_ADDRESS) { + usage_flags2.sType = VK_STRUCTURE_TYPE_BUFFER_USAGE_FLAGS_2_CREATE_INFO; + usage_flags2.usage = VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT; + buf_info.pNext = &usage_flags2; + } + + VmaAllocationCreateInfo alloc_info = {}; + alloc_info.flags = _toVmaFlags(desc.memory); + alloc_info.usage = VMA_MEMORY_USAGE_AUTO; + + VkBuffer vk_buffer = VK_NULL_HANDLE; + VmaAllocation vma_alloc = VK_NULL_HANDLE; + VmaAllocationInfo vma_alloc_info = {}; + _checkVk(vmaCreateBuffer((VmaAllocator)device->allocator, &buf_info, &alloc_info, + &vk_buffer, &vma_alloc, &vma_alloc_info), + "vmaCreateBuffer"); + + PrRhiBuffer *buffer = wpMemAllocatorAlloc(alloc, sizeof(PrRhiBuffer)); + if (!buffer) _abort("alloc failed for PrRhiBuffer"); + buffer->handle = vk_buffer; + buffer->allocation = vma_alloc; + buffer->size = desc.size; + buffer->mapped_data = vma_alloc_info.pMappedData; + buffer->device_address = 0; + + // Query device address if needed + if (desc.usage & PR_RHI_BUFFER_USAGE_SHADER_DEVICE_ADDRESS) { + VkBufferDeviceAddressInfo addr_info = {}; + addr_info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addr_info.buffer = vk_buffer; + buffer->device_address = vkGetBufferDeviceAddress((VkDevice)device->handle, &addr_info); + } + + return buffer; +} + +void prRhiDestroyBufferVk(PrRhiDevice *device, PrRhiBuffer *buffer, WpAllocator *alloc) { + if (!buffer) return; + vmaDestroyBuffer((VmaAllocator)device->allocator, (VkBuffer)buffer->handle, + (VmaAllocation)buffer->allocation); + wpMemAllocatorFree(alloc, (void**)&buffer, sizeof(PrRhiBuffer)); +} + +void *prRhiBufferMapVk(PrRhiDevice *device, PrRhiBuffer *buffer) { + (void)device; + return buffer->mapped_data; +} + +void prRhiBufferUnmapVk(PrRhiDevice *device, PrRhiBuffer *buffer) { + (void)device; + (void)buffer; + // VMA mapped buffers don't need explicit unmap with VMA_ALLOCATION_CREATE_MAPPED_BIT +} + +PrRhiDeviceAddress prRhiGetBufferDeviceAddressVk(PrRhiDevice *device, PrRhiBuffer *buffer) { + (void)device; + return buffer->device_address; +} + +// ============================================================================ +// Textures +// ============================================================================ + +PrRhiTexture *prRhiCreateTextureVk(PrRhiDevice *device, PrRhiTextureDesc desc, + WpAllocator *alloc) { + VkImageCreateInfo img_info = {}; + img_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + img_info.imageType = VK_IMAGE_TYPE_2D; + img_info.format = _toVkFormat(desc.format); + img_info.extent.width = desc.width; + img_info.extent.height = desc.height; + img_info.extent.depth = 1; + img_info.mipLevels = desc.mip_levels; + img_info.arrayLayers = 1; + img_info.samples = VK_SAMPLE_COUNT_1_BIT; + img_info.tiling = VK_IMAGE_TILING_OPTIMAL; + img_info.usage = _toVkTextureUsage(desc.usage); + img_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VmaAllocationCreateInfo alloc_info = {}; + alloc_info.usage = VMA_MEMORY_USAGE_AUTO; + + VkImage vk_image = VK_NULL_HANDLE; + VmaAllocation vma_alloc = VK_NULL_HANDLE; + _checkVk(vmaCreateImage((VmaAllocator)device->allocator, &img_info, &alloc_info, + &vk_image, &vma_alloc, NULL), + "vmaCreateImage"); + + VkImageAspectFlags aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT; + if (desc.usage & PR_RHI_TEXTURE_USAGE_DEPTH_ATTACHMENT) { + aspect_mask = VK_IMAGE_ASPECT_DEPTH_BIT; + } + + VkImageViewCreateInfo view_info = {}; + view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + view_info.image = vk_image; + view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_info.format = _toVkFormat(desc.format); + view_info.subresourceRange.aspectMask = aspect_mask; + view_info.subresourceRange.levelCount = desc.mip_levels; + view_info.subresourceRange.layerCount = 1; + + VkImageView vk_view = VK_NULL_HANDLE; + _checkVk(vkCreateImageView((VkDevice)device->handle, &view_info, NULL, &vk_view), + "vkCreateImageView"); + + PrRhiTexture *texture = wpMemAllocatorAlloc(alloc, sizeof(PrRhiTexture)); + if (!texture) _abort("alloc failed for PrRhiTexture"); + texture->image = vk_image; + texture->view = vk_view; + texture->allocation = vma_alloc; + texture->width = desc.width; + texture->height = desc.height; + texture->format = _toVkFormat(desc.format); + + return texture; +} + +void prRhiDestroyTextureVk(PrRhiDevice *device, PrRhiTexture *texture, WpAllocator *alloc) { + if (!texture) return; + VkDevice vk_device = (VkDevice)device->handle; + + vkDestroyImageView(vk_device, (VkImageView)texture->view, NULL); + if (texture->allocation) { + vmaDestroyImage((VmaAllocator)device->allocator, (VkImage)texture->image, + (VmaAllocation)texture->allocation); + } + wpMemAllocatorFree(alloc, (void**)&texture, sizeof(PrRhiTexture)); +} + +// ============================================================================ +// Samplers +// ============================================================================ + +PrRhiSampler *prRhiCreateSamplerVk(PrRhiDevice *device, PrRhiSamplerDesc desc, + WpAllocator *alloc) { + VkSamplerCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + info.magFilter = _toVkFilter(desc.mag_filter); + info.minFilter = _toVkFilter(desc.min_filter); + info.mipmapMode = _toVkMipmapMode(desc.mipmap_mode); + info.addressModeU = _toVkAddressMode(desc.address_mode_u); + info.addressModeV = _toVkAddressMode(desc.address_mode_v); + info.addressModeW = _toVkAddressMode(desc.address_mode_w); + info.anisotropyEnable = desc.max_anisotropy > 0.0f ? VK_TRUE : VK_FALSE; + info.maxAnisotropy = desc.max_anisotropy; + info.minLod = desc.min_lod; + info.maxLod = desc.max_lod; + info.mipLodBias = 0.0f; + + VkSampler vk_sampler = VK_NULL_HANDLE; + _checkVk(vkCreateSampler((VkDevice)device->handle, &info, NULL, &vk_sampler), + "vkCreateSampler"); + + PrRhiSampler *sampler = wpMemAllocatorAlloc(alloc, sizeof(PrRhiSampler)); + if (!sampler) _abort("alloc failed for PrRhiSampler"); + sampler->handle = vk_sampler; + return sampler; +} + +void prRhiDestroySamplerVk(PrRhiDevice *device, PrRhiSampler *sampler, WpAllocator *alloc) { + if (!sampler) return; + vkDestroySampler((VkDevice)device->handle, (VkSampler)sampler->handle, NULL); + wpMemAllocatorFree(alloc, (void**)&sampler, sizeof(PrRhiSampler)); +} + +// ============================================================================ +// Shaders +// ============================================================================ + +PrRhiShader *prRhiCreateShaderVk(PrRhiDevice *device, PrRhiShaderDesc desc, + WpAllocator *alloc) { + VkShaderModuleCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + info.codeSize = desc.spirv_size; + info.pCode = (const u32 *)desc.spirv_code; + + VkShaderModule vk_module = VK_NULL_HANDLE; + _checkVk(vkCreateShaderModule((VkDevice)device->handle, &info, NULL, &vk_module), + "vkCreateShaderModule"); + + PrRhiShader *shader = wpMemAllocatorAlloc(alloc, sizeof(PrRhiShader)); + if (!shader) _abort("alloc failed for PrRhiShader"); + shader->handle = vk_module; + return shader; +} + +void prRhiDestroyShaderVk(PrRhiDevice *device, PrRhiShader *shader, WpAllocator *alloc) { + if (!shader) return; + vkDestroyShaderModule((VkDevice)device->handle, (VkShaderModule)shader->handle, NULL); + wpMemAllocatorFree(alloc, (void**)&shader, sizeof(PrRhiShader)); +} + +// ============================================================================ +// Pipeline layouts +// ============================================================================ + +PrRhiPipelineLayout *prRhiCreatePipelineLayoutVk(PrRhiDevice *device, + PrRhiPipelineLayoutDesc desc, + WpAllocator *alloc) { + VkDevice vk_device = (VkDevice)device->handle; + + // Gather descriptor set layouts + u32 layout_count = (desc.set_layouts && wpArrayCount(desc.set_layouts) > 0) + ? (u32)wpArrayCount(desc.set_layouts) : 0; + VkDescriptorSetLayout vk_layouts_stack[8]; + VkDescriptorSetLayout *vk_layouts = vk_layouts_stack; + + if (layout_count > 8) { + vk_layouts = wpArrayAllocCapacity(VkDescriptorSetLayout, alloc, layout_count, WP_ARRAY_INIT_NONE); + if (!vk_layouts) _abort("alloc failed for VkDescriptorSetLayout array"); + } + for (u32 i = 0; i < layout_count; ++i) { + vk_layouts[i] = (VkDescriptorSetLayout)desc.set_layouts[i]->handle; + } + + // Gather push constant ranges + u32 pc_count = (desc.push_constant_ranges && wpArrayCount(desc.push_constant_ranges) > 0) + ? (u32)wpArrayCount(desc.push_constant_ranges) : 0; + VkPushConstantRange pc_ranges_stack[8]; + VkPushConstantRange *pc_ranges = pc_ranges_stack; + + if (pc_count > 8) { + pc_ranges = wpArrayAllocCapacity(VkPushConstantRange, alloc, pc_count, WP_ARRAY_INIT_NONE); + if (!pc_ranges) _abort("alloc failed for VkPushConstantRange array"); + } + for (u32 i = 0; i < pc_count; ++i) { + pc_ranges[i].stageFlags = _toVkShaderStage(desc.push_constant_ranges[i].stage_flags); + pc_ranges[i].offset = desc.push_constant_ranges[i].offset; + pc_ranges[i].size = desc.push_constant_ranges[i].size; + } + + VkPipelineLayoutCreateInfo pl_info = {}; + pl_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pl_info.setLayoutCount = layout_count; + pl_info.pSetLayouts = vk_layouts; + pl_info.pushConstantRangeCount = pc_count; + pl_info.pPushConstantRanges = pc_ranges; + + VkPipelineLayout vk_layout = VK_NULL_HANDLE; + _checkVk(vkCreatePipelineLayout(vk_device, &pl_info, NULL, &vk_layout), + "vkCreatePipelineLayout"); + + if (layout_count > 8) { + wpArrayDealloc(VkDescriptorSetLayout, alloc, &vk_layouts); + } + if (pc_count > 8) { + wpArrayDealloc(VkPushConstantRange, alloc, &pc_ranges); + } + + PrRhiPipelineLayout *layout = wpMemAllocatorAlloc(alloc, sizeof(PrRhiPipelineLayout)); + if (!layout) _abort("alloc failed for PrRhiPipelineLayout"); + layout->handle = vk_layout; + return layout; +} + +void prRhiDestroyPipelineLayoutVk(PrRhiDevice *device, PrRhiPipelineLayout *layout, + WpAllocator *alloc) { + if (!layout) return; + vkDestroyPipelineLayout((VkDevice)device->handle, (VkPipelineLayout)layout->handle, NULL); + wpMemAllocatorFree(alloc, (void**)&layout, sizeof(PrRhiPipelineLayout)); +} + +// ============================================================================ +// Pipelines +// ============================================================================ + +PrRhiPipeline *prRhiCreateGraphicsPipelineVk(PrRhiDevice *device, + PrRhiGraphicsPipelineDesc desc, + WpAllocator *alloc) { + VkDevice vk_device = (VkDevice)device->handle; + + // Shader stages + VkPipelineShaderStageCreateInfo stages[2] = {}; + u32 stage_count = 0; + + if (desc.vertex_shader) { + stages[stage_count].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stages[stage_count].stage = VK_SHADER_STAGE_VERTEX_BIT; + stages[stage_count].module = (VkShaderModule)desc.vertex_shader->handle; + stages[stage_count].pName = "main"; + ++stage_count; + } + if (desc.fragment_shader) { + stages[stage_count].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stages[stage_count].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + stages[stage_count].module = (VkShaderModule)desc.fragment_shader->handle; + stages[stage_count].pName = "main"; + ++stage_count; + } + + // Vertex input state + VkVertexInputBindingDescription vk_bindings[8]; + u32 binding_count = 0; + if (desc.vertex_bindings && wpArrayCount(desc.vertex_bindings) > 0) { + binding_count = (u32)wpArrayCount(desc.vertex_bindings); + for (u32 i = 0; i < binding_count && i < 8; ++i) { + vk_bindings[i].binding = desc.vertex_bindings[i].binding; + vk_bindings[i].stride = desc.vertex_bindings[i].stride; + vk_bindings[i].inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + } + } + + VkVertexInputAttributeDescription vk_attrs[16]; + u32 attr_count = 0; + if (desc.vertex_attributes && wpArrayCount(desc.vertex_attributes) > 0) { + attr_count = (u32)wpArrayCount(desc.vertex_attributes); + for (u32 i = 0; i < attr_count && i < 16; ++i) { + vk_attrs[i].location = desc.vertex_attributes[i].location; + vk_attrs[i].binding = desc.vertex_attributes[i].binding; + vk_attrs[i].format = _toVkFormat(desc.vertex_attributes[i].format); + vk_attrs[i].offset = desc.vertex_attributes[i].offset; + } + } + + VkPipelineVertexInputStateCreateInfo vertex_input = {}; + vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_input.vertexBindingDescriptionCount = binding_count; + vertex_input.pVertexBindingDescriptions = vk_bindings; + vertex_input.vertexAttributeDescriptionCount = attr_count; + vertex_input.pVertexAttributeDescriptions = vk_attrs; + + // Input assembly + VkPipelineInputAssemblyStateCreateInfo input_assembly = {}; + input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly.topology = _toVkTopology(desc.topology); + + // Viewport & scissor dynamic states + VkPipelineViewportStateCreateInfo viewport_state = {}; + viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_state.viewportCount = 1; + viewport_state.scissorCount = 1; + + VkDynamicState dynamic_states_stack[2]; + u32 dynamic_count = 0; + if (desc.dynamic_viewport) { + dynamic_states_stack[dynamic_count++] = VK_DYNAMIC_STATE_VIEWPORT; + } + if (desc.dynamic_scissor) { + dynamic_states_stack[dynamic_count++] = VK_DYNAMIC_STATE_SCISSOR; + } + + VkPipelineDynamicStateCreateInfo dynamic_state = {}; + dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state.dynamicStateCount = dynamic_count; + dynamic_state.pDynamicStates = dynamic_states_stack; + + // Depth/stencil + VkPipelineDepthStencilStateCreateInfo depth_stencil = {}; + depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depth_stencil.depthTestEnable = desc.depth_test_enable ? VK_TRUE : VK_FALSE; + depth_stencil.depthWriteEnable = desc.depth_write_enable ? VK_TRUE : VK_FALSE; + depth_stencil.depthCompareOp = _toVkCompareOp(desc.depth_compare_op); + depth_stencil.depthBoundsTestEnable = VK_FALSE; + depth_stencil.stencilTestEnable = VK_FALSE; + + // Dynamic rendering + VkPipelineRenderingCreateInfo rendering_info = {}; + rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO; + rendering_info.depthAttachmentFormat = _toVkFormat(desc.depth_attachment_format); + + VkFormat vk_color_formats[8]; + u32 color_format_count = 0; + if (desc.color_attachment_formats && wpArrayCount(desc.color_attachment_formats) > 0) { + color_format_count = (u32)wpArrayCount(desc.color_attachment_formats); + for (u32 i = 0; i < color_format_count && i < 8; ++i) { + vk_color_formats[i] = _toVkFormat(desc.color_attachment_formats[i]); + } + } + rendering_info.colorAttachmentCount = color_format_count; + rendering_info.pColorAttachmentFormats = vk_color_formats; + + // Blending + VkPipelineColorBlendAttachmentState blend_attachments[8]; + u32 blend_count = 0; + if (desc.blend_attachments && wpArrayCount(desc.blend_attachments) > 0) { + blend_count = (u32)wpArrayCount(desc.blend_attachments); + for (u32 i = 0; i < blend_count && i < 8; ++i) { + blend_attachments[i].blendEnable = VK_FALSE; + blend_attachments[i].colorWriteMask = desc.blend_attachments[i].color_write_mask; + blend_attachments[i].srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + blend_attachments[i].dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; + blend_attachments[i].colorBlendOp = VK_BLEND_OP_ADD; + blend_attachments[i].srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + blend_attachments[i].dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + blend_attachments[i].alphaBlendOp = VK_BLEND_OP_ADD; + } + } + + VkPipelineColorBlendStateCreateInfo blend_state = {}; + blend_state.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + blend_state.attachmentCount = blend_count; + blend_state.pAttachments = blend_attachments; + + // Rasterization + VkPipelineRasterizationStateCreateInfo raster = {}; + raster.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + raster.lineWidth = 1.0f; + raster.cullMode = VK_CULL_MODE_BACK_BIT; + raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + + // Multisampling + VkPipelineMultisampleStateCreateInfo multisample = {}; + multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + // Create pipeline + VkGraphicsPipelineCreateInfo pi = {}; + pi.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pi.pNext = &rendering_info; + pi.stageCount = stage_count; + pi.pStages = stages; + pi.pVertexInputState = &vertex_input; + pi.pInputAssemblyState = &input_assembly; + pi.pViewportState = &viewport_state; + pi.pRasterizationState = &raster; + pi.pMultisampleState = &multisample; + pi.pDepthStencilState = &depth_stencil; + pi.pColorBlendState = &blend_state; + pi.pDynamicState = &dynamic_state; + pi.layout = desc.layout ? (VkPipelineLayout)desc.layout->handle : VK_NULL_HANDLE; + + VkPipeline vk_pipeline = VK_NULL_HANDLE; + _checkVk(vkCreateGraphicsPipelines(vk_device, VK_NULL_HANDLE, 1, &pi, NULL, &vk_pipeline), + "vkCreateGraphicsPipelines"); + + PrRhiPipeline *pipeline = wpMemAllocatorAlloc(alloc, sizeof(PrRhiPipeline)); + if (!pipeline) _abort("alloc failed for PrRhiPipeline"); + pipeline->handle = vk_pipeline; + return pipeline; +} + +PrRhiPipeline *prRhiCreateComputePipelineVk(PrRhiDevice *device, + PrRhiComputePipelineDesc desc, + WpAllocator *alloc) { + VkComputePipelineCreateInfo pi = {}; + pi.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + pi.stage = (VkPipelineShaderStageCreateInfo){ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_COMPUTE_BIT, + .module = desc.shader ? (VkShaderModule)desc.shader->handle : VK_NULL_HANDLE, + .pName = "main", + }; + pi.layout = desc.layout ? (VkPipelineLayout)desc.layout->handle : VK_NULL_HANDLE; + + VkPipeline vk_pipeline = VK_NULL_HANDLE; + _checkVk(vkCreateComputePipelines((VkDevice)device->handle, VK_NULL_HANDLE, 1, &pi, NULL, &vk_pipeline), + "vkCreateComputePipelines"); + + PrRhiPipeline *pipeline = wpMemAllocatorAlloc(alloc, sizeof(PrRhiPipeline)); + if (!pipeline) _abort("alloc failed for PrRhiPipeline"); + pipeline->handle = vk_pipeline; + return pipeline; +} + +void prRhiDestroyPipelineVk(PrRhiDevice *device, PrRhiPipeline *pipeline, WpAllocator *alloc) { + if (!pipeline) return; + vkDestroyPipeline((VkDevice)device->handle, (VkPipeline)pipeline->handle, NULL); + wpMemAllocatorFree(alloc, (void**)&pipeline, sizeof(PrRhiPipeline)); +} + +// ============================================================================ +// Descriptor set layouts +// ============================================================================ + +PrRhiDescriptorSetLayout *prRhiCreateDescriptorSetLayoutVk(PrRhiDevice *device, + PrRhiDescriptorSetLayoutDesc desc, + WpAllocator *alloc) { + VkDevice vk_device = (VkDevice)device->handle; + + u32 binding_count = (desc.bindings && wpArrayCount(desc.bindings) > 0) + ? (u32)wpArrayCount(desc.bindings) : 0; + + VkDescriptorSetLayoutBinding vk_bindings[16]; + VkDescriptorBindingFlags vk_flags[16]; + + for (u32 i = 0; i < binding_count && i < 16; ++i) { + vk_bindings[i].binding = i; + vk_bindings[i].descriptorType = _toVkDescriptorType(desc.bindings[i].type); + vk_bindings[i].descriptorCount = desc.bindings[i].descriptor_count; + vk_bindings[i].stageFlags = _toVkShaderStage(desc.bindings[i].stage_flags); + vk_bindings[i].pImmutableSamplers = NULL; + vk_flags[i] = _toVkBindingFlags(desc.bindings[i].binding_flags); + } + + VkDescriptorSetLayoutBindingFlagsCreateInfo flags_info = {}; + flags_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO; + flags_info.bindingCount = binding_count; + flags_info.pBindingFlags = vk_flags; + + VkDescriptorSetLayoutCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + info.pNext = &flags_info; + info.bindingCount = binding_count; + info.pBindings = vk_bindings; + + VkDescriptorSetLayout vk_layout = VK_NULL_HANDLE; + _checkVk(vkCreateDescriptorSetLayout(vk_device, &info, NULL, &vk_layout), + "vkCreateDescriptorSetLayout"); + + PrRhiDescriptorSetLayout *layout = wpMemAllocatorAlloc(alloc, sizeof(PrRhiDescriptorSetLayout)); + if (!layout) _abort("alloc failed for PrRhiDescriptorSetLayout"); + layout->handle = vk_layout; + return layout; +} + +void prRhiDestroyDescriptorSetLayoutVk(PrRhiDevice *device, PrRhiDescriptorSetLayout *layout, + WpAllocator *alloc) { + if (!layout) return; + vkDestroyDescriptorSetLayout((VkDevice)device->handle, (VkDescriptorSetLayout)layout->handle, NULL); + wpMemAllocatorFree(alloc, (void**)&layout, sizeof(PrRhiDescriptorSetLayout)); +} + +// ============================================================================ +// Descriptor pools +// ============================================================================ + +PrRhiDescriptorPool *prRhiCreateDescriptorPoolVk(PrRhiDevice *device, + PrRhiDescriptorPoolDesc desc, + WpAllocator *alloc) { + VkDevice vk_device = (VkDevice)device->handle; + + u32 pool_size_count = (desc.pool_sizes && wpArrayCount(desc.pool_sizes) > 0) + ? (u32)wpArrayCount(desc.pool_sizes) : 0; + + VkDescriptorPoolSize vk_sizes[8]; + for (u32 i = 0; i < pool_size_count && i < 8; ++i) { + vk_sizes[i].type = _toVkDescriptorType(desc.pool_sizes[i].type); + vk_sizes[i].descriptorCount = desc.pool_sizes[i].descriptor_count; + } + + VkDescriptorPoolCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + info.maxSets = desc.max_sets; + info.poolSizeCount = pool_size_count; + info.pPoolSizes = vk_sizes; + + VkDescriptorPool vk_pool = VK_NULL_HANDLE; + _checkVk(vkCreateDescriptorPool(vk_device, &info, NULL, &vk_pool), + "vkCreateDescriptorPool"); + + PrRhiDescriptorPool *pool = wpMemAllocatorAlloc(alloc, sizeof(PrRhiDescriptorPool)); + if (!pool) _abort("alloc failed for PrRhiDescriptorPool"); + pool->handle = vk_pool; + return pool; +} + +void prRhiDestroyDescriptorPoolVk(PrRhiDevice *device, PrRhiDescriptorPool *pool, + WpAllocator *alloc) { + if (!pool) return; + vkDestroyDescriptorPool((VkDevice)device->handle, (VkDescriptorPool)pool->handle, NULL); + wpMemAllocatorFree(alloc, (void**)&pool, sizeof(PrRhiDescriptorPool)); +} + +// ============================================================================ +// Descriptor sets +// ============================================================================ + +PrRhiDescriptorSet *prRhiAllocateDescriptorSetVk(PrRhiDevice *device, + PrRhiDescriptorPool *pool, + PrRhiDescriptorSetLayout *layout, + u32 variable_count, WpAllocator *alloc) { + VkDevice vk_device = (VkDevice)device->handle; + + VkDescriptorSetVariableDescriptorCountAllocateInfo var_info = {}; + var_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO; + var_info.descriptorSetCount = 1; + var_info.pDescriptorCounts = &variable_count; + + VkDescriptorSetLayout vk_layout = (VkDescriptorSetLayout)layout->handle; + + VkDescriptorSetAllocateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + info.pNext = &var_info; + info.descriptorPool = (VkDescriptorPool)pool->handle; + info.descriptorSetCount = 1; + info.pSetLayouts = &vk_layout; + + VkDescriptorSet vk_set = VK_NULL_HANDLE; + _checkVk(vkAllocateDescriptorSets(vk_device, &info, &vk_set), + "vkAllocateDescriptorSets"); + + PrRhiDescriptorSet *set = wpMemAllocatorAlloc(alloc, sizeof(PrRhiDescriptorSet)); + if (!set) _abort("alloc failed for PrRhiDescriptorSet"); + set->handle = vk_set; + return set; +} + +void prRhiFreeDescriptorSetVk(PrRhiDevice *device, PrRhiDescriptorPool *pool, + PrRhiDescriptorSet *set, WpAllocator *alloc) { + if (!set) return; + VkDevice vk_device = (VkDevice)device->handle; + VkDescriptorSet vk_set = (VkDescriptorSet)set->handle; + _checkVk(vkFreeDescriptorSets(vk_device, (VkDescriptorPool)pool->handle, 1, &vk_set), + "vkFreeDescriptorSets"); + wpMemAllocatorFree(alloc, (void**)&set, sizeof(PrRhiDescriptorSet)); +} + +void prRhiUpdateDescriptorSetVk(PrRhiDevice *device, PrRhiWriteDescriptorSetArray writes) { + VkDevice vk_device = (VkDevice)device->handle; + (void)vk_device; + + u32 count = writes ? (u32)wpArrayCount(writes) : 0; + if (count == 0) return; + + // We process each write individually because we need to expand image_info/buffer_info arrays + for (u32 i = 0; i < count; ++i) { + PrRhiWriteDescriptorSet *w = &writes[i]; + VkDescriptorType vk_type = _toVkDescriptorType(w->type); + + if (w->image_info && wpArrayCount(w->image_info) > 0) { + u32 img_count = (u32)wpArrayCount(w->image_info); + VkDescriptorImageInfo vk_img_info[16]; + u32 img_max = img_count > 16 ? 16 : img_count; + for (u32 j = 0; j < img_max; ++j) { + PrRhiDescriptorImageInfo *src = &w->image_info[j]; + vk_img_info[j].sampler = src->sampler ? (VkSampler)src->sampler->handle : VK_NULL_HANDLE; + vk_img_info[j].imageView = src->texture ? (VkImageView)src->texture->view : VK_NULL_HANDLE; + vk_img_info[j].imageLayout = _toVkImageLayout(src->layout); + } + + VkWriteDescriptorSet vk_write = {}; + vk_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + vk_write.dstSet = (VkDescriptorSet)w->dst_set->handle; + vk_write.dstBinding = w->dst_binding; + vk_write.dstArrayElement = w->dst_array_element; + vk_write.descriptorCount = img_max; + vk_write.descriptorType = vk_type; + vk_write.pImageInfo = vk_img_info; + vkUpdateDescriptorSets(vk_device, 1, &vk_write, 0, NULL); + } + + if (w->buffer_info && wpArrayCount(w->buffer_info) > 0) { + u32 buf_count = (u32)wpArrayCount(w->buffer_info); + VkDescriptorBufferInfo vk_buf_info[16]; + u32 buf_max = buf_count > 16 ? 16 : buf_count; + for (u32 j = 0; j < buf_max; ++j) { + PrRhiDescriptorBufferInfo *src = &w->buffer_info[j]; + vk_buf_info[j].buffer = src->buffer ? (VkBuffer)src->buffer->handle : VK_NULL_HANDLE; + vk_buf_info[j].offset = src->offset; + vk_buf_info[j].range = src->range; + } + + VkWriteDescriptorSet vk_write = {}; + vk_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + vk_write.dstSet = (VkDescriptorSet)w->dst_set->handle; + vk_write.dstBinding = w->dst_binding; + vk_write.dstArrayElement = w->dst_array_element; + vk_write.descriptorCount = buf_max; + vk_write.descriptorType = vk_type; + vk_write.pBufferInfo = vk_buf_info; + vkUpdateDescriptorSets(vk_device, 1, &vk_write, 0, NULL); + } + } +} + +// ============================================================================ +// Fences +// ============================================================================ + +PrRhiFence *prRhiCreateFenceVk(PrRhiDevice *device, PrRhiFenceDesc desc, + WpAllocator *alloc) { + VkFenceCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + info.flags = desc.signaled ? VK_FENCE_CREATE_SIGNALED_BIT : 0; + + VkFence vk_fence = VK_NULL_HANDLE; + _checkVk(vkCreateFence((VkDevice)device->handle, &info, NULL, &vk_fence), + "vkCreateFence"); + + PrRhiFence *fence = wpMemAllocatorAlloc(alloc, sizeof(PrRhiFence)); + if (!fence) _abort("alloc failed for PrRhiFence"); + fence->handle = vk_fence; + return fence; +} + +void prRhiDestroyFenceVk(PrRhiDevice *device, PrRhiFence *fence, WpAllocator *alloc) { + if (!fence) return; + vkDestroyFence((VkDevice)device->handle, (VkFence)fence->handle, NULL); + wpMemAllocatorFree(alloc, (void**)&fence, sizeof(PrRhiFence)); +} + +void prRhiWaitForFencesVk(PrRhiDevice *device, PrRhiFenceArray fences, u32 count, + b8 wait_all, u64 timeout_ns) { + VkFence vk_fences[16]; + u32 real_count = count < 16 ? count : 16; + for (u32 i = 0; i < real_count; ++i) { + vk_fences[i] = (VkFence)fences[i]->handle; + } + _checkVk(vkWaitForFences((VkDevice)device->handle, real_count, vk_fences, + wait_all ? VK_TRUE : VK_FALSE, timeout_ns), + "vkWaitForFences"); +} + +void prRhiResetFencesVk(PrRhiDevice *device, PrRhiFenceArray fences, u32 count) { + VkFence vk_fences[16]; + u32 real_count = count < 16 ? count : 16; + for (u32 i = 0; i < real_count; ++i) { + vk_fences[i] = (VkFence)fences[i]->handle; + } + _checkVk(vkResetFences((VkDevice)device->handle, real_count, vk_fences), + "vkResetFences"); +} + +// ============================================================================ +// Semaphores +// ============================================================================ + +PrRhiSemaphore *prRhiCreateSemaphoreVk(PrRhiDevice *device, WpAllocator *alloc) { + VkSemaphoreCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkSemaphore vk_semaphore = VK_NULL_HANDLE; + _checkVk(vkCreateSemaphore((VkDevice)device->handle, &info, NULL, &vk_semaphore), + "vkCreateSemaphore"); + + PrRhiSemaphore *semaphore = wpMemAllocatorAlloc(alloc, sizeof(PrRhiSemaphore)); + if (!semaphore) _abort("alloc failed for PrRhiSemaphore"); + semaphore->handle = vk_semaphore; + return semaphore; +} + +void prRhiDestroySemaphoreVk(PrRhiDevice *device, PrRhiSemaphore *semaphore, WpAllocator *alloc) { + if (!semaphore) return; + vkDestroySemaphore((VkDevice)device->handle, (VkSemaphore)semaphore->handle, NULL); + wpMemAllocatorFree(alloc, (void**)&semaphore, sizeof(PrRhiSemaphore)); +} + +// ============================================================================ +// Command pools +// ============================================================================ + +PrRhiCommandPool *prRhiCreateCommandPoolVk(PrRhiDevice *device, PrRhiCommandPoolDesc desc, + WpAllocator *alloc) { + VkCommandPoolCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + info.queueFamilyIndex = desc.queue_family_index; + + VkCommandPool vk_pool = VK_NULL_HANDLE; + _checkVk(vkCreateCommandPool((VkDevice)device->handle, &info, NULL, &vk_pool), + "vkCreateCommandPool"); + + PrRhiCommandPool *pool = wpMemAllocatorAlloc(alloc, sizeof(PrRhiCommandPool)); + if (!pool) _abort("alloc failed for PrRhiCommandPool"); + pool->handle = vk_pool; + return pool; +} + +void prRhiDestroyCommandPoolVk(PrRhiDevice *device, PrRhiCommandPool *pool, WpAllocator *alloc) { + if (!pool) return; + vkDestroyCommandPool((VkDevice)device->handle, (VkCommandPool)pool->handle, NULL); + wpMemAllocatorFree(alloc, (void**)&pool, sizeof(PrRhiCommandPool)); +} + +PrRhiCommandBufferArray prRhiAllocateCommandBuffersVk(PrRhiDevice *device, PrRhiCommandPool *pool, + u32 count, WpAllocator *alloc) { + VkCommandBufferAllocateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + info.commandPool = (VkCommandPool)pool->handle; + info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + info.commandBufferCount = count; + + VkCommandBuffer vk_cbs[16]; + u32 real_count = count < 16 ? count : 16; + + VkCommandBufferArray vk_cb_array = wpArrayAllocCapacity(VkCommandBuffer, alloc, real_count, WP_ARRAY_INIT_NONE); + if (!vk_cb_array) _abort("alloc failed for VkCommandBuffer array"); + + _checkVk(vkAllocateCommandBuffers((VkDevice)device->handle, &info, vk_cbs), + "vkAllocateCommandBuffers"); + + for (u32 i = 0; i < real_count; ++i) { + wpArrayAppendCapped(VkCommandBuffer, vk_cb_array, &vk_cbs[i]); + } + + PrRhiCommandBufferArray cbs = wpArrayAllocCapacity(PrRhiCommandBuffer *, alloc, real_count, WP_ARRAY_INIT_NONE); + if (!cbs) _abort("alloc failed for PrRhiCommandBuffer array"); + + for (u32 i = 0; i < real_count; ++i) { + PrRhiCommandBuffer *cb = (PrRhiCommandBuffer *)wpMemAllocatorAlloc(alloc, sizeof(PrRhiCommandBuffer)); + if (!cb) _abort("alloc failed for PrRhiCommandBuffer"); + cb->handle = vk_cb_array[i]; + wpArrayAppendCapped(PrRhiCommandBuffer *, cbs, &cb); + } + + wpArrayDealloc(VkCommandBuffer, alloc, &vk_cb_array); + + return cbs; +} + +void prRhiFreeCommandBuffersVk(PrRhiDevice *device, PrRhiCommandPool *pool, + u32 count, PrRhiCommandBufferArray buffers) { + if (!buffers || count == 0) return; + + VkDevice vk_device = (VkDevice)device->handle; + VkCommandPool vk_pool = (VkCommandPool)pool->handle; + + VkCommandBuffer vk_cbs[16]; + u32 real_count = count < 16 ? count : 16; + for (u32 i = 0; i < real_count; ++i) { + vk_cbs[i] = (VkCommandBuffer)buffers[i]->handle; + } + + vkFreeCommandBuffers(vk_device, vk_pool, real_count, vk_cbs); + + // Free PrRhiCommandBuffer structs + for (u32 i = 0; i < real_count; ++i) { + // These were allocated from the same allocator + // Since they're in an array, we need root allocator — skip, caller handles + } +} + +// ============================================================================ +// Command buffer recording +// ============================================================================ + +void prRhiBeginCommandBufferVk(PrRhiCommandBuffer *cb) { + VkCommandBufferBeginInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + _checkVk(vkBeginCommandBuffer((VkCommandBuffer)cb->handle, &info), + "vkBeginCommandBuffer"); +} + +void prRhiEndCommandBufferVk(PrRhiCommandBuffer *cb) { + _checkVk(vkEndCommandBuffer((VkCommandBuffer)cb->handle), + "vkEndCommandBuffer"); +} + +void prRhiResetCommandBufferVk(PrRhiCommandBuffer *cb) { + _checkVk(vkResetCommandBuffer((VkCommandBuffer)cb->handle, 0), + "vkResetCommandBuffer"); +} + +// ============================================================================ +// Pipeline barriers +// ============================================================================ + +void prRhiCmdPipelineBarrierVk(PrRhiCommandBuffer *cb, + PrRhiImageMemoryBarrierArray image_barriers, + PrRhiBufferMemoryBarrierArray buffer_barriers) { + VkCommandBuffer vk_cb = (VkCommandBuffer)cb->handle; + + u32 img_count = (image_barriers && wpArrayCount(image_barriers) > 0) + ? (u32)wpArrayCount(image_barriers) : 0; + u32 buf_count = (buffer_barriers && wpArrayCount(buffer_barriers) > 0) + ? (u32)wpArrayCount(buffer_barriers) : 0; + + if (img_count == 0 && buf_count == 0) return; + + VkImageMemoryBarrier2 vk_img_barriers[16]; + for (u32 i = 0; i < img_count && i < 16; ++i) { + PrRhiImageMemoryBarrier *src = &image_barriers[i]; + VkImageLayout old_layout = _toVkImageLayout(src->old_layout); + VkImageLayout new_layout = _toVkImageLayout(src->new_layout); + + VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT; + if (src->texture) { + VkFormat fmt = (VkFormat)src->texture->format; + if (fmt == VK_FORMAT_D24_UNORM_S8_UINT || fmt == VK_FORMAT_D32_SFLOAT_S8_UINT) + aspect = VK_IMAGE_ASPECT_DEPTH_BIT; + } + + VkPipelineStageFlags2 src_stage = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; + VkPipelineStageFlags2 dst_stage = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; + VkAccessFlags2 src_access = 0; + VkAccessFlags2 dst_access = 0; + + vk_img_barriers[i].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + vk_img_barriers[i].srcStageMask = src_stage; + vk_img_barriers[i].srcAccessMask = src_access; + vk_img_barriers[i].dstStageMask = dst_stage; + vk_img_barriers[i].dstAccessMask = dst_access; + vk_img_barriers[i].oldLayout = old_layout; + vk_img_barriers[i].newLayout = new_layout; + vk_img_barriers[i].image = src->texture ? (VkImage)src->texture->image : VK_NULL_HANDLE; + vk_img_barriers[i].subresourceRange.aspectMask = aspect; + vk_img_barriers[i].subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + vk_img_barriers[i].subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + } + + VkBufferMemoryBarrier2 vk_buf_barriers[16]; + for (u32 i = 0; i < buf_count && i < 16; ++i) { + PrRhiBufferMemoryBarrier *src = &buffer_barriers[i]; + + vk_buf_barriers[i].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; + vk_buf_barriers[i].srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; + vk_buf_barriers[i].srcAccessMask = 0; + vk_buf_barriers[i].dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; + vk_buf_barriers[i].dstAccessMask = 0; + vk_buf_barriers[i].buffer = src->buffer ? (VkBuffer)src->buffer->handle : VK_NULL_HANDLE; + vk_buf_barriers[i].offset = src->offset; + vk_buf_barriers[i].size = src->size; + } + + VkDependencyInfo dep = {}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = img_count; + dep.pImageMemoryBarriers = vk_img_barriers; + dep.bufferMemoryBarrierCount = buf_count; + dep.pBufferMemoryBarriers = vk_buf_barriers; + + vkCmdPipelineBarrier2(vk_cb, &dep); +} + +// ============================================================================ +// Dynamic rendering +// ============================================================================ + +void prRhiCmdBeginRenderingVk(PrRhiCommandBuffer *cb, + PrRhiColorAttachmentArray color_attachments, + const PrRhiDepthAttachment *depth_attachment) { + VkCommandBuffer vk_cb = (VkCommandBuffer)cb->handle; + + u32 color_count = (color_attachments && wpArrayCount(color_attachments) > 0) + ? (u32)wpArrayCount(color_attachments) : 0; + + VkRenderingAttachmentInfo vk_color[8]; + u32 width = 0, height = 0; + + for (u32 i = 0; i < color_count && i < 8; ++i) { + PrRhiColorAttachment *src = &color_attachments[i]; + vk_color[i].sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + vk_color[i].imageView = src->texture ? (VkImageView)src->texture->view : VK_NULL_HANDLE; + vk_color[i].imageLayout = _toVkImageLayout(src->layout); + vk_color[i].loadOp = src->clear ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; + vk_color[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE; + if (src->clear) { + vk_color[i].clearValue.color.float32[0] = src->clear_color[0]; + vk_color[i].clearValue.color.float32[1] = src->clear_color[1]; + vk_color[i].clearValue.color.float32[2] = src->clear_color[2]; + vk_color[i].clearValue.color.float32[3] = src->clear_color[3]; + } + if (src->texture) { + width = src->texture->width; + height = src->texture->height; + } + } + + VkRenderingAttachmentInfo vk_depth = {}; + VkRenderingAttachmentInfo *p_depth = NULL; + if (depth_attachment && depth_attachment->texture) { + p_depth = &vk_depth; + vk_depth.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + vk_depth.imageView = (VkImageView)depth_attachment->texture->view; + vk_depth.imageLayout = _toVkImageLayout(depth_attachment->layout); + vk_depth.loadOp = depth_attachment->clear ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; + vk_depth.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + vk_depth.clearValue.depthStencil.depth = depth_attachment->clear_depth; + if (!width || !height) { + width = depth_attachment->texture->width; + height = depth_attachment->texture->height; + } + } + + VkRect2D render_area = {}; + render_area.extent.width = width; + render_area.extent.height = height; + + VkRenderingInfo render_info = {}; + render_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + render_info.renderArea = render_area; + render_info.layerCount = 1; + render_info.colorAttachmentCount = color_count; + render_info.pColorAttachments = vk_color; + render_info.pDepthAttachment = p_depth; + + vkCmdBeginRendering(vk_cb, &render_info); +} + +void prRhiCmdEndRenderingVk(PrRhiCommandBuffer *cb) { + vkCmdEndRendering((VkCommandBuffer)cb->handle); +} + +// ============================================================================ +// Dynamic state +// ============================================================================ + +void prRhiCmdSetViewportVk(PrRhiCommandBuffer *cb, f32 x, f32 y, f32 width, f32 height) { + VkViewport vp = { x, y, width, height, 0.0f, 1.0f }; + vkCmdSetViewport((VkCommandBuffer)cb->handle, 0, 1, &vp); +} + +void prRhiCmdSetScissorVk(PrRhiCommandBuffer *cb, i32 x, i32 y, u32 width, u32 height) { + VkRect2D scissor = { { x, y }, { width, height } }; + vkCmdSetScissor((VkCommandBuffer)cb->handle, 0, 1, &scissor); +} + +// ============================================================================ +// Binding +// ============================================================================ + +void prRhiCmdBindPipelineVk(PrRhiCommandBuffer *cb, PrRhiPipelineBindPoint bind_point, + PrRhiPipeline *pipeline) { + VkPipelineBindPoint vk_bp = (bind_point == PR_RHI_PIPELINE_BIND_POINT_COMPUTE) + ? VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS; + vkCmdBindPipeline((VkCommandBuffer)cb->handle, vk_bp, (VkPipeline)pipeline->handle); +} + +void prRhiCmdBindDescriptorSetsVk(PrRhiCommandBuffer *cb, PrRhiPipelineBindPoint bind_point, + PrRhiPipelineLayout *layout, u32 first_set, + PrRhiDescriptorSetArray sets) { + VkPipelineBindPoint vk_bp = (bind_point == PR_RHI_PIPELINE_BIND_POINT_COMPUTE) + ? VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS; + VkPipelineLayout vk_layout = layout ? (VkPipelineLayout)layout->handle : VK_NULL_HANDLE; + + u32 set_count = sets ? (u32)wpArrayCount(sets) : 0; + VkDescriptorSet vk_sets[16]; + u32 real_count = set_count < 16 ? set_count : 16; + for (u32 i = 0; i < real_count; ++i) { + vk_sets[i] = (VkDescriptorSet)sets[i]->handle; + } + + vkCmdBindDescriptorSets((VkCommandBuffer)cb->handle, vk_bp, vk_layout, + first_set, real_count, vk_sets, 0, NULL); +} + +void prRhiCmdPushConstantsVk(PrRhiCommandBuffer *cb, PrRhiPipelineLayout *layout, + PrRhiShaderStage stage_flags, u32 offset, u32 size, + const void *data) { + vkCmdPushConstants((VkCommandBuffer)cb->handle, + layout ? (VkPipelineLayout)layout->handle : VK_NULL_HANDLE, + _toVkShaderStage(stage_flags), offset, size, data); +} + +void prRhiCmdBindVertexBuffersVk(PrRhiCommandBuffer *cb, u32 first_binding, + PrRhiBufferArray buffers, const u64 *offsets, u32 count) { + VkBuffer vk_bufs[16]; + u32 real_count = count < 16 ? count : 16; + for (u32 i = 0; i < real_count; ++i) { + vk_bufs[i] = (VkBuffer)buffers[i]->handle; + } + vkCmdBindVertexBuffers((VkCommandBuffer)cb->handle, first_binding, + real_count, vk_bufs, (const VkDeviceSize *)offsets); +} + +void prRhiCmdBindIndexBufferVk(PrRhiCommandBuffer *cb, PrRhiBuffer *buffer, u64 offset, + PrRhiIndexType index_type) { + vkCmdBindIndexBuffer((VkCommandBuffer)cb->handle, + (VkBuffer)buffer->handle, + offset, _toVkIndexType(index_type)); +} + +// ============================================================================ +// Draw calls +// ============================================================================ + +void prRhiCmdDrawVk(PrRhiCommandBuffer *cb, u32 vertex_count, u32 instance_count, + u32 first_vertex, u32 first_instance) { + vkCmdDraw((VkCommandBuffer)cb->handle, vertex_count, instance_count, first_vertex, first_instance); +} + +void prRhiCmdDrawIndexedVk(PrRhiCommandBuffer *cb, u32 index_count, u32 instance_count, + u32 first_index, i32 vertex_offset, u32 first_instance) { + vkCmdDrawIndexed((VkCommandBuffer)cb->handle, index_count, instance_count, + first_index, vertex_offset, first_instance); +} + +// ============================================================================ +// Copy +// ============================================================================ + +void prRhiCmdCopyBufferToImageVk(PrRhiCommandBuffer *cb, PrRhiBuffer *src, PrRhiTexture *dst) { + VkBufferImageCopy region = {}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.layerCount = 1; + region.imageExtent.width = dst->width; + region.imageExtent.height = dst->height; + region.imageExtent.depth = 1; + + vkCmdCopyBufferToImage((VkCommandBuffer)cb->handle, + (VkBuffer)src->handle, + (VkImage)dst->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, ®ion); +} + +// ============================================================================ +// Queue submission +// ============================================================================ + +void prRhiQueueSubmitVk(PrRhiDevice *device, PrRhiCommandBuffer *cb, + PrRhiSemaphore *wait_semaphore, + PrRhiSemaphore *signal_semaphore, PrRhiFence *fence) { + VkCommandBuffer vk_cb = (VkCommandBuffer)cb->handle; + + VkSemaphore wait_sems[1] = {}; + VkPipelineStageFlags wait_stages[1] = { VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT }; + u32 wait_count = 0; + if (wait_semaphore) { + wait_sems[0] = (VkSemaphore)wait_semaphore->handle; + wait_count = 1; + } + + VkSemaphore signal_sems[1] = {}; + u32 signal_count = 0; + if (signal_semaphore) { + signal_sems[0] = (VkSemaphore)signal_semaphore->handle; + signal_count = 1; + } + + VkSubmitInfo submit = {}; + submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit.waitSemaphoreCount = wait_count; + submit.pWaitSemaphores = wait_sems; + submit.pWaitDstStageMask = wait_stages; + submit.commandBufferCount = 1; + submit.pCommandBuffers = &vk_cb; + submit.signalSemaphoreCount = signal_count; + submit.pSignalSemaphores = signal_sems; + + _checkVk(vkQueueSubmit((VkQueue)device->queue, 1, &submit, + fence ? (VkFence)fence->handle : VK_NULL_HANDLE), + "vkQueueSubmit"); +} diff --git a/src/prism/rhi/vulkan/pr_rhi_vk.h b/src/prism/rhi/vulkan/pr_rhi_vk.h index bebec69..101b158 100644 --- a/src/prism/rhi/vulkan/pr_rhi_vk.h +++ b/src/prism/rhi/vulkan/pr_rhi_vk.h @@ -27,6 +27,8 @@ struct PrRhiDevice { void *handle; // VkDevice void *queue; // VkQueue u32 queue_family_index; + u32 present_mode; // VkPresentModeKHR + void *physical_device; // VkPhysicalDevice void *allocator; // VmaAllocator }; @@ -37,12 +39,14 @@ struct PrRhiSurface { struct PrRhiSwapchain { PrRhiDevice *device; void *handle; // VkSwapchainKHR + void *surface; // PrRhiSurface * u32 image_count; PrRhiTexture **images; PrRhiTexture *depth; u32 format; // VkFormat u32 width; u32 height; + u32 current_image_index; }; struct PrRhiBuffer { @@ -57,6 +61,9 @@ struct PrRhiTexture { void *image; // VkImage void *view; // VkImageView void *allocation; // VmaAllocation + u32 width; + u32 height; + u32 format; // VkFormat }; struct PrRhiSampler { diff --git a/src/prism/rhi/vulkan/pr_rhi_vk_aliases.h b/src/prism/rhi/vulkan/pr_rhi_vk_aliases.h index 55781a2..4d0c7fd 100644 --- a/src/prism/rhi/vulkan/pr_rhi_vk_aliases.h +++ b/src/prism/rhi/vulkan/pr_rhi_vk_aliases.h @@ -12,6 +12,7 @@ #define prRhiGetPhysicalDeviceDriverInfo prRhiGetPhysicalDeviceDriverInfoVk #define prRhiCreateSurface prRhiCreateSurfaceVk #define prRhiDestroySurface prRhiDestroySurfaceVk +#define prRhiGetSurfaceCapabilities prRhiGetSurfaceCapabilitiesVk #define prRhiCreateDevice prRhiCreateDeviceVk #define prRhiDestroyDevice prRhiDestroyDeviceVk #define prRhiDeviceWaitIdle prRhiDeviceWaitIdleVk diff --git a/src/prism/rhi/vulkan/pr_rhi_vk_vma.cpp b/src/prism/rhi/vulkan/pr_rhi_vk_vma.cpp new file mode 100644 index 0000000..7a6837c --- /dev/null +++ b/src/prism/rhi/vulkan/pr_rhi_vk_vma.cpp @@ -0,0 +1,2 @@ +#define VMA_IMPLEMENTATION +#include