device/vulkan/device.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #ifndef _WIN32 | ||
| 3 | #include <unistd.h> | ||
| 4 | #endif | ||
| 5 | |||
| 6 | #include "chrono_batch_render/device/vulkan/device.h" | ||
| 7 | |||
| 8 | #include <algorithm> | ||
| 9 | #include <cstring> | ||
| 10 | #include <optional> | ||
| 11 | #include <string> | ||
| 12 | #include <string_view> | ||
| 13 | #include <utility> | ||
| 14 | #include <variant> | ||
| 15 | #include <vector> | ||
| 16 | |||
| 17 | #ifdef _WIN32 | ||
| 18 | #include <windows.h> | ||
| 19 | #endif | ||
| 20 | |||
| 21 | #include <fmt/format.h> | ||
| 22 | |||
| 23 | #include "chrono_batch_render/device/commands.h" | ||
| 24 | #include "chrono_batch_render/device/queue.h" | ||
| 25 | #include "chrono_batch_render/device/slang/compiler.h" | ||
| 26 | #include "chrono_batch_render/device/vulkan/interop.h" | ||
| 27 | #include "chrono_batch_render/device/vulkan/native.h" | ||
| 28 | #include "chrono_batch_render/util/disk_cache.h" | ||
| 29 | #include "chrono_batch_render/util/error.h" | ||
| 30 | #include "chrono_batch_render/util/log.h" | ||
| 31 | #include "chrono_batch_render/util/text.h" | ||
| 32 | |||
| 33 | namespace chrono::batch_render::device::vulkan { | ||
| 34 | |||
| 35 | 21 | std::array<uint8_t, 16> DeviceUuid(VkPhysicalDevice device) { | |
| 36 | 21 | VkPhysicalDeviceIDProperties id{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES}; | |
| 37 | 21 | VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &id}; | |
| 38 | 21 | vkGetPhysicalDeviceProperties2(device, &properties); | |
| 39 | 21 | std::array<uint8_t, 16> uuid{}; | |
| 40 | 21 | std::copy(std::begin(id.deviceUUID), std::end(id.deviceUUID), uuid.begin()); | |
| 41 | 21 | return uuid; | |
| 42 | } | ||
| 43 | |||
| 44 | namespace {} | ||
| 45 | |||
| 46 | 104 | std::optional<ComputeFamily> ComputeQueueFamily(VkPhysicalDevice device, VkQueueFlags flags) { | |
| 47 | 104 | const std::vector<VkQueueFamilyProperties> families = Enumerate<VkQueueFamilyProperties>( | |
| 48 | 208 | [device](uint32_t* count, VkQueueFamilyProperties* items) { | |
| 49 | 208 | vkGetPhysicalDeviceQueueFamilyProperties(device, count, items); | |
| 50 | 208 | }); | |
| 51 | 104 | for (uint32_t family = 0; family < uint32_t(families.size()); ++family) { | |
| 52 | 104 | if ((families[family].queueFlags & flags) == flags) { | |
| 53 | 104 | return ComputeFamily{family, families[family]}; | |
| 54 | } | ||
| 55 | } | ||
| 56 | ✗ | return std::nullopt; | |
| 57 | 104 | } | |
| 58 | |||
| 59 | namespace { | ||
| 60 | |||
| 61 | #if defined(_WIN32) | ||
| 62 | constexpr VkTimeDomainKHR kHostTimeDomain = VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_KHR; | ||
| 63 | |||
| 64 | uint64_t HostClockNs(uint64_t ticks) { | ||
| 65 | static const uint64_t frequency = [] { | ||
| 66 | LARGE_INTEGER value{}; | ||
| 67 | QueryPerformanceFrequency(&value); | ||
| 68 | return uint64_t(value.QuadPart); | ||
| 69 | }(); | ||
| 70 | return ticks / frequency * 1'000'000'000 + ticks % frequency * 1'000'000'000 / frequency; | ||
| 71 | } | ||
| 72 | #else | ||
| 73 | constexpr VkTimeDomainKHR kHostTimeDomain = VK_TIME_DOMAIN_CLOCK_MONOTONIC_KHR; | ||
| 74 | |||
| 75 | 11 | uint64_t HostClockNs(uint64_t ticks) { | |
| 76 | 11 | return ticks; | |
| 77 | } | ||
| 78 | #endif | ||
| 79 | |||
| 80 | constexpr VkBufferUsageFlags kStorageUsage = | ||
| 81 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | | ||
| 82 | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | | ||
| 83 | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; | ||
| 84 | |||
| 85 | 20 | VkPhysicalDevice FindPhysicalDevice(VkInstance instance, const AdapterInfo& adapter) { | |
| 86 | 20 | for (VkPhysicalDevice device : PhysicalDevices(instance)) { | |
| 87 | 20 | if (DeviceUuid(device) == adapter.uuid) { | |
| 88 | 20 | return device; | |
| 89 | } | ||
| 90 | 20 | } | |
| 91 | ✗ | throw Error("the Vulkan adapter {} is no longer present", adapter.name); | |
| 92 | } | ||
| 93 | |||
| 94 | #if !defined(_WIN32) | ||
| 95 | 20 | bool ExportsBuffers(VkPhysicalDevice device) { | |
| 96 | 20 | VkPhysicalDeviceExternalBufferInfo info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO}; | |
| 97 | 20 | info.usage = kStorageUsage; | |
| 98 | 20 | info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; | |
| 99 | 20 | VkExternalBufferProperties properties{VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES}; | |
| 100 | 20 | vkGetPhysicalDeviceExternalBufferProperties(device, &info, &properties); | |
| 101 | 20 | return (properties.externalMemoryProperties.externalMemoryFeatures & | |
| 102 | 20 | VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT) != 0; | |
| 103 | } | ||
| 104 | |||
| 105 | ✗ | bool ExportsTimelines(VkPhysicalDevice device) { | |
| 106 | ✗ | VkSemaphoreTypeCreateInfo timeline{VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO}; | |
| 107 | ✗ | timeline.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; | |
| 108 | ✗ | VkPhysicalDeviceExternalSemaphoreInfo info{ | |
| 109 | ✗ | VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO, &timeline}; | |
| 110 | ✗ | info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT; | |
| 111 | ✗ | VkExternalSemaphoreProperties properties{VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES}; | |
| 112 | ✗ | vkGetPhysicalDeviceExternalSemaphoreProperties(device, &info, &properties); | |
| 113 | ✗ | return (properties.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) != | |
| 114 | ✗ | 0; | |
| 115 | } | ||
| 116 | #endif | ||
| 117 | |||
| 118 | 20 | const char* CalibratedTimestampsExtension(VkPhysicalDevice device, const Extensions& extensions) { | |
| 119 | #if defined(__APPLE__) | ||
| 120 | return nullptr; | ||
| 121 | #endif | ||
| 122 | 20 | const bool khr = extensions.Has(VK_KHR_CALIBRATED_TIMESTAMPS_EXTENSION_NAME); | |
| 123 | 20 | if (!khr && !extensions.Has(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME)) { | |
| 124 | return nullptr; | ||
| 125 | } | ||
| 126 | |||
| 127 | 20 | const auto list = khr ? vkGetPhysicalDeviceCalibrateableTimeDomainsKHR | |
| 128 | : vkGetPhysicalDeviceCalibrateableTimeDomainsEXT; | ||
| 129 | 20 | const std::vector<VkTimeDomainKHR> domains = Enumerate<VkTimeDomainKHR>( | |
| 130 | 60 | [&](uint32_t* count, VkTimeDomainKHR* items) { list(device, count, items); }); | |
| 131 | 60 | const auto has = [&](VkTimeDomainKHR domain) { | |
| 132 | 40 | return std::ranges::find(domains, domain) != domains.end(); | |
| 133 | 20 | }; | |
| 134 | 20 | if (!has(VK_TIME_DOMAIN_DEVICE_KHR) || !has(kHostTimeDomain)) { | |
| 135 | return nullptr; | ||
| 136 | } | ||
| 137 | 20 | return khr ? VK_KHR_CALIBRATED_TIMESTAMPS_EXTENSION_NAME | |
| 138 | 20 | : VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME; | |
| 139 | 20 | } | |
| 140 | |||
| 141 | ✗ | struct DeviceExtensions { | |
| 142 | std::vector<const char*> names; | ||
| 143 | const char* calibrated = nullptr; | ||
| 144 | bool sharesSemaphores = false; | ||
| 145 | bool sharesMemory = false; | ||
| 146 | bool capturesStatistics = false; | ||
| 147 | bool presentation = false; | ||
| 148 | }; | ||
| 149 | |||
| 150 | 20 | DeviceExtensions ChooseExtensions(VkPhysicalDevice device, const AdapterInfo& adapter, | |
| 151 | const DeviceDesc& desc, bool hardware) { | ||
| 152 | 20 | DeviceExtensions chosen; | |
| 153 | 20 | if (hardware) { | |
| 154 | ✗ | chosen.names.assign(std::begin(kRayQueryExtensions), std::end(kRayQueryExtensions)); | |
| 155 | } | ||
| 156 | 20 | const Extensions available(device); | |
| 157 | 20 | chosen.calibrated = desc.profiling ? CalibratedTimestampsExtension(device, available) : nullptr; | |
| 158 | 20 | if (chosen.calibrated != nullptr) { | |
| 159 | 20 | chosen.names.push_back(chosen.calibrated); | |
| 160 | } | ||
| 161 | 20 | if (available.Has("VK_KHR_portability_subset")) { | |
| 162 | ✗ | chosen.names.push_back("VK_KHR_portability_subset"); | |
| 163 | } | ||
| 164 | #if !defined(_WIN32) | ||
| 165 | 40 | chosen.sharesSemaphores = | |
| 166 | 20 | available.Has(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME) && ExportsTimelines(device); | |
| 167 | 20 | if (chosen.sharesSemaphores) { | |
| 168 | ✗ | chosen.names.push_back(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME); | |
| 169 | } | ||
| 170 | 40 | chosen.sharesMemory = | |
| 171 | 20 | available.Has(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME) && ExportsBuffers(device); | |
| 172 | 20 | if (chosen.sharesMemory) { | |
| 173 | 20 | chosen.names.push_back(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME); | |
| 174 | } | ||
| 175 | #endif | ||
| 176 | 20 | chosen.presentation = desc.presentation; | |
| 177 | 20 | if (chosen.presentation) { | |
| 178 | ✗ | chosen.names.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); | |
| 179 | } | ||
| 180 | 20 | chosen.capturesStatistics = desc.profiling && adapter.features.kernelStatistics; | |
| 181 | 20 | if (chosen.capturesStatistics) { | |
| 182 | ✗ | chosen.names.push_back(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); | |
| 183 | } | ||
| 184 | 20 | return chosen; | |
| 185 | 20 | } | |
| 186 | |||
| 187 | 20 | VkDevice CreateLogicalDevice(VkPhysicalDevice physicalDevice, uint32_t queueFamily, | |
| 188 | uint32_t queueCount, const DeviceExtensions& extensions, bool hardware, | ||
| 189 | bool textures, bool blockCompression, bool anisotropy, | ||
| 190 | const Creator* creator) { | ||
| 191 | 20 | const std::vector<const char*>& names = extensions.names; | |
| 192 | 20 | FeatureChain enabled; | |
| 193 | 20 | enabled.Link([&](const char* name) { | |
| 194 | 80 | return std::ranges::any_of( | |
| 195 | 160 | names, [name](const char* each) { return std::strcmp(each, name) == 0; }); | |
| 196 | }); | ||
| 197 | 20 | enabled.executables.pipelineExecutableInfo = extensions.capturesStatistics ? VK_TRUE : VK_FALSE; | |
| 198 | 20 | enabled.base.features.shaderInt64 = VK_TRUE; | |
| 199 | 20 | enabled.vulkan12.bufferDeviceAddress = VK_TRUE; | |
| 200 | 20 | enabled.vulkan12.timelineSemaphore = VK_TRUE; | |
| 201 | 20 | enabled.vulkan13.synchronization2 = VK_TRUE; | |
| 202 | 20 | enabled.vulkan13.subgroupSizeControl = VK_TRUE; | |
| 203 | 20 | enabled.vulkan13.computeFullSubgroups = VK_TRUE; | |
| 204 | 20 | enabled.vulkan13.dynamicRendering = extensions.presentation ? VK_TRUE : VK_FALSE; | |
| 205 | 20 | enabled.accelerationStructure.accelerationStructure = hardware ? VK_TRUE : VK_FALSE; | |
| 206 | 20 | enabled.rayQuery.rayQuery = hardware ? VK_TRUE : VK_FALSE; | |
| 207 | |||
| 208 | 20 | const VkBool32 bindless = textures ? VK_TRUE : VK_FALSE; | |
| 209 | 20 | enabled.vulkan12.runtimeDescriptorArray = bindless; | |
| 210 | 20 | enabled.vulkan12.descriptorBindingPartiallyBound = bindless; | |
| 211 | 20 | enabled.vulkan12.descriptorBindingSampledImageUpdateAfterBind = bindless; | |
| 212 | 20 | enabled.vulkan12.descriptorBindingUpdateUnusedWhilePending = bindless; | |
| 213 | 20 | enabled.vulkan12.shaderSampledImageArrayNonUniformIndexing = bindless; | |
| 214 | 20 | enabled.base.features.textureCompressionBC = textures && blockCompression ? VK_TRUE : VK_FALSE; | |
| 215 | 20 | enabled.base.features.samplerAnisotropy = textures && anisotropy ? VK_TRUE : VK_FALSE; | |
| 216 | |||
| 217 | 20 | const float priorities[2] = {queueCount > 1 ? 0.5f : 1.0f, 1.0f}; | |
| 218 | 20 | VkDeviceQueueCreateInfo queue{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; | |
| 219 | 20 | queue.queueFamilyIndex = queueFamily; | |
| 220 | 20 | queue.queueCount = queueCount; | |
| 221 | 20 | queue.pQueuePriorities = priorities; | |
| 222 | 20 | VkDeviceCreateInfo info{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, &enabled.base}; | |
| 223 | 20 | info.queueCreateInfoCount = 1; | |
| 224 | 20 | info.pQueueCreateInfos = &queue; | |
| 225 | 20 | info.enabledExtensionCount = uint32_t(names.size()); | |
| 226 | 20 | info.ppEnabledExtensionNames = names.data(); | |
| 227 | 20 | VkDevice device = VK_NULL_HANDLE; | |
| 228 | 20 | if (creator != nullptr) { | |
| 229 | 2 | device = creator->createDevice(vkGetInstanceProcAddr, physicalDevice, info); | |
| 230 | 2 | if (device == VK_NULL_HANDLE) { | |
| 231 | ✗ | throw Error("the Vulkan creator made no device"); | |
| 232 | } | ||
| 233 | return device; | ||
| 234 | } | ||
| 235 | 18 | Check(vkCreateDevice(physicalDevice, &info, nullptr, &device), "vkCreateDevice"); | |
| 236 | 18 | return device; | |
| 237 | } | ||
| 238 | |||
| 239 | 20 | VmaAllocator CreateAllocator(VkInstance instance, VkPhysicalDevice physicalDevice, | |
| 240 | VkDevice device) { | ||
| 241 | 20 | VmaVulkanFunctions functions{}; | |
| 242 | 20 | functions.vkGetInstanceProcAddr = vkGetInstanceProcAddr; | |
| 243 | 20 | functions.vkGetDeviceProcAddr = vkGetDeviceProcAddr; | |
| 244 | 20 | VmaAllocatorCreateInfo info{}; | |
| 245 | 20 | info.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; | |
| 246 | 20 | info.vulkanApiVersion = VK_API_VERSION_1_3; | |
| 247 | 20 | info.physicalDevice = physicalDevice; | |
| 248 | 20 | info.device = device; | |
| 249 | 20 | info.instance = instance; | |
| 250 | 20 | info.pVulkanFunctions = &functions; | |
| 251 | 20 | VmaAllocator allocator = VK_NULL_HANDLE; | |
| 252 | 20 | Check(vmaCreateAllocator(&info, &allocator), "vmaCreateAllocator"); | |
| 253 | 20 | return allocator; | |
| 254 | } | ||
| 255 | |||
| 256 | 40 | std::string PipelineCacheKey(const AdapterInfo& adapter) { | |
| 257 | 40 | const std::string_view uuid(reinterpret_cast<const char*>(adapter.uuid.data()), | |
| 258 | 40 | adapter.uuid.size()); | |
| 259 | 80 | return fmt::format("vulkan pipelines {} {} driver {}", adapter.name, Digest(uuid), | |
| 260 | 120 | adapter.driverVersion.ToString()); | |
| 261 | } | ||
| 262 | |||
| 263 | } | ||
| 264 | |||
| 265 | 20 | VulkanDevice::VulkanDevice(const AdapterInfo& adapter, const DeviceDesc& desc, Traversal traversal, | |
| 266 | 20 | OwnedInstance instance) | |
| 267 | 20 | : Device(adapter, desc, traversal), m_ownedInstance(std::move(instance)) { | |
| 268 | 20 | GetApi(); | |
| 269 | 20 | try { | |
| 270 | 20 | m_labels = Extensions().Has(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); | |
| 271 | 20 | m_instance = m_ownedInstance.Get() != VK_NULL_HANDLE ? m_ownedInstance.Get() | |
| 272 | 18 | : vulkan::Instance(desc.validation); | |
| 273 | 20 | m_physicalDevice = FindPhysicalDevice(m_instance, adapter); | |
| 274 | 40 | const VkQueueFlags flags = | |
| 275 | 20 | VK_QUEUE_COMPUTE_BIT | (desc.presentation ? VK_QUEUE_GRAPHICS_BIT : 0); | |
| 276 | 20 | const std::optional<ComputeFamily> family = ComputeQueueFamily(m_physicalDevice, flags); | |
| 277 | 20 | if (!family) { | |
| 278 | ✗ | throw Error("the Vulkan adapter {} has no {} queue", adapter.name, | |
| 279 | ✗ | desc.presentation ? "graphics and compute" : "compute"); | |
| 280 | } | ||
| 281 | |||
| 282 | 20 | m_queueFamily = family->index; | |
| 283 | 20 | VkPhysicalDeviceProperties properties{}; | |
| 284 | 20 | vkGetPhysicalDeviceProperties(m_physicalDevice, &properties); | |
| 285 | 20 | m_vendorId = properties.vendorID; | |
| 286 | 20 | m_timestampPeriod = properties.limits.timestampPeriod; | |
| 287 | 20 | const uint32_t bits = family->properties.timestampValidBits; | |
| 288 | 20 | m_timestampMask = bits >= 64 ? ~uint64_t(0) : (uint64_t(1) << bits) - 1; | |
| 289 | |||
| 290 | 20 | const bool hardware = traversal == Traversal::Hardware; | |
| 291 | 20 | const DeviceExtensions extensions = | |
| 292 | 20 | ChooseExtensions(m_physicalDevice, adapter, desc, hardware); | |
| 293 | 20 | m_sharesSemaphores = extensions.sharesSemaphores; | |
| 294 | 20 | m_sharesMemory = extensions.sharesMemory; | |
| 295 | 20 | m_capturesStatistics = extensions.capturesStatistics; | |
| 296 | |||
| 297 | 20 | const bool textures = GetSampling() == Sampling::Hardware; | |
| 298 | 20 | const uint32_t queueCount = desc.presentation && family->properties.queueCount > 1 ? 2 : 1; | |
| 299 | 60 | m_device = CreateLogicalDevice( | |
| 300 | m_physicalDevice, m_queueFamily, queueCount, extensions, hardware, textures, | ||
| 301 | 20 | adapter.features.blockCompression, adapter.features.anisotropicFiltering, | |
| 302 | 20 | m_ownedInstance.Get() != VK_NULL_HANDLE ? desc.creator.get() : nullptr); | |
| 303 | 20 | LoadDeviceFunctions(m_functions, m_device); | |
| 304 | 20 | if (extensions.calibrated != nullptr) { | |
| 305 | 40 | m_calibratedTimestamps = std::string_view(extensions.calibrated).starts_with("VK_KHR") | |
| 306 | 20 | ? m_functions.vkGetCalibratedTimestampsKHR | |
| 307 | : m_functions.vkGetCalibratedTimestampsEXT; | ||
| 308 | } | ||
| 309 | 20 | m_functions.vkGetDeviceQueue(m_device, m_queueFamily, 0, &m_queueHandle); | |
| 310 | 20 | m_allocator = CreateAllocator(m_instance, m_physicalDevice, m_device); | |
| 311 | |||
| 312 | 20 | const std::optional<std::string> saved = | |
| 313 | 100 | DiskCache(KernelCacheDirectory() / "vulkan").Load(PipelineCacheKey(Adapter())); | |
| 314 | 20 | VkPipelineCacheCreateInfo pipelines{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO}; | |
| 315 | 20 | pipelines.initialDataSize = saved ? saved->size() : 0; | |
| 316 | 20 | pipelines.pInitialData = saved ? saved->data() : nullptr; | |
| 317 | 20 | Check(m_functions.vkCreatePipelineCache(m_device, &pipelines, nullptr, &m_pipelineCache), | |
| 318 | "vkCreatePipelineCache"); | ||
| 319 | |||
| 320 | 20 | CreateDescriptors(textures); | |
| 321 | |||
| 322 | 20 | Log(LogLevel::Debug, "created a Vulkan device on queue family {}{}{}", m_queueFamily, | |
| 323 | 40 | extensions.names.empty() ? "" : ", with ", | |
| 324 | 40 | Join({extensions.names.begin(), extensions.names.end()})); | |
| 325 | 20 | m_queue = CreateQueue(*this, m_queueHandle, true); | |
| 326 | 20 | if (queueCount > 1) { | |
| 327 | ✗ | VkQueue display = VK_NULL_HANDLE; | |
| 328 | ✗ | m_functions.vkGetDeviceQueue(m_device, m_queueFamily, 1, &display); | |
| 329 | ✗ | m_displayQueue = CreateQueue(*this, display, false); | |
| 330 | } | ||
| 331 | 20 | } catch (...) { | |
| 332 | ✗ | Destroy(); | |
| 333 | ✗ | throw; | |
| 334 | ✗ | } | |
| 335 | 20 | } | |
| 336 | |||
| 337 | 20 | void VulkanDevice::CreateDescriptors(bool textures) { | |
| 338 | 20 | const uint32_t textureSlots = textures ? Adapter().limits.maxTextures : 0; | |
| 339 | 19 | if (textures) { | |
| 340 | 19 | CreateSamplers(); | |
| 341 | } | ||
| 342 | |||
| 343 | 20 | VkDescriptorSetLayoutBinding bindings[3] = {}; | |
| 344 | 20 | bindings[0].binding = 0; | |
| 345 | 20 | bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; | |
| 346 | 20 | bindings[0].descriptorCount = 1; | |
| 347 | 20 | bindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; | |
| 348 | 20 | bindings[1].binding = kTextureBinding; | |
| 349 | 20 | bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; | |
| 350 | 20 | bindings[1].descriptorCount = textureSlots; | |
| 351 | 20 | bindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; | |
| 352 | 20 | bindings[2].binding = kSamplerBinding; | |
| 353 | 20 | bindings[2].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; | |
| 354 | 20 | bindings[2].descriptorCount = uint32_t(m_samplers.size()); | |
| 355 | 20 | bindings[2].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; | |
| 356 | 20 | bindings[2].pImmutableSamplers = m_samplers.data(); | |
| 357 | 20 | const VkDescriptorBindingFlags flags[3] = { | |
| 358 | 0, | ||
| 359 | VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT | | ||
| 360 | VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT, | ||
| 361 | 0}; | ||
| 362 | 20 | VkDescriptorSetLayoutBindingFlagsCreateInfo bindingFlags{ | |
| 363 | VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO}; | ||
| 364 | 20 | bindingFlags.bindingCount = 3; | |
| 365 | 20 | bindingFlags.pBindingFlags = flags; | |
| 366 | 20 | VkDescriptorSetLayoutCreateInfo set{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; | |
| 367 | 20 | set.bindingCount = textures ? 3 : 1; | |
| 368 | 20 | set.pBindings = bindings; | |
| 369 | 20 | if (textures) { | |
| 370 | 19 | set.pNext = &bindingFlags; | |
| 371 | 19 | set.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT; | |
| 372 | } | ||
| 373 | 20 | Check(m_functions.vkCreateDescriptorSetLayout(m_device, &set, nullptr, &m_globalsLayout), | |
| 374 | "vkCreateDescriptorSetLayout"); | ||
| 375 | |||
| 376 | 20 | const VkPushConstantRange params{VK_SHADER_STAGE_COMPUTE_BIT, 0, | |
| 377 | 20 | Adapter().limits.maxParamBytes}; | |
| 378 | 20 | VkPipelineLayoutCreateInfo layout{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; | |
| 379 | 20 | layout.setLayoutCount = 1; | |
| 380 | 20 | layout.pSetLayouts = &m_globalsLayout; | |
| 381 | 20 | layout.pushConstantRangeCount = 1; | |
| 382 | 20 | layout.pPushConstantRanges = ¶ms; | |
| 383 | 20 | Check(m_functions.vkCreatePipelineLayout(m_device, &layout, nullptr, &m_pipelineLayout), | |
| 384 | "vkCreatePipelineLayout"); | ||
| 385 | |||
| 386 | 20 | const VkDescriptorPoolSize sizes[3] = {{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1}, | |
| 387 | {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, textureSlots}, | ||
| 388 | 20 | {VK_DESCRIPTOR_TYPE_SAMPLER, kTextureSamplers}}; | |
| 389 | 20 | VkDescriptorPoolCreateInfo pool{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; | |
| 390 | 20 | pool.maxSets = 1; | |
| 391 | 20 | pool.poolSizeCount = textures ? 3 : 1; | |
| 392 | 20 | pool.pPoolSizes = sizes; | |
| 393 | 20 | if (textures) { | |
| 394 | 19 | pool.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT; | |
| 395 | } | ||
| 396 | 20 | Check(m_functions.vkCreateDescriptorPool(m_device, &pool, nullptr, &m_descriptorPool), | |
| 397 | "vkCreateDescriptorPool"); | ||
| 398 | |||
| 399 | 20 | VkDescriptorSetAllocateInfo allocate{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; | |
| 400 | 20 | allocate.descriptorPool = m_descriptorPool; | |
| 401 | 20 | allocate.descriptorSetCount = 1; | |
| 402 | 20 | allocate.pSetLayouts = &m_globalsLayout; | |
| 403 | 20 | Check(m_functions.vkAllocateDescriptorSets(m_device, &allocate, &m_globals), | |
| 404 | "vkAllocateDescriptorSets"); | ||
| 405 | 20 | } | |
| 406 | |||
| 407 | 121128 | void VulkanDevice::Name(VkObjectType type, uint64_t object, const std::string& name) const { | |
| 408 | 121128 | if (!m_labels || name.empty()) { | |
| 409 | ✗ | return; | |
| 410 | } | ||
| 411 | 121128 | VkDebugUtilsObjectNameInfoEXT info{VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT}; | |
| 412 | 121128 | info.objectType = type; | |
| 413 | 121128 | info.objectHandle = object; | |
| 414 | 121128 | info.pObjectName = name.c_str(); | |
| 415 | 121128 | Check(vkSetDebugUtilsObjectNameEXT(m_device, &info), "vkSetDebugUtilsObjectNameEXT"); | |
| 416 | } | ||
| 417 | |||
| 418 | 11 | bool VulkanDevice::ToNanoseconds(std::span<uint64_t> timestamps) const { | |
| 419 | 11 | const auto calibrated = CalibratedTimestamps(); | |
| 420 | 47 | for (uint64_t& time : timestamps) { | |
| 421 | 36 | const auto ticks = int64_t(time & m_timestampMask); | |
| 422 | 72 | time = calibrated | |
| 423 | 36 | ? calibrated->second + | |
| 424 | 36 | uint64_t(double(ticks - int64_t(calibrated->first & m_timestampMask)) * | |
| 425 | 36 | m_timestampPeriod) | |
| 426 | ✗ | : uint64_t(double(ticks) * m_timestampPeriod); | |
| 427 | } | ||
| 428 | 11 | return calibrated.has_value(); | |
| 429 | } | ||
| 430 | |||
| 431 | 11 | std::optional<std::pair<uint64_t, uint64_t>> VulkanDevice::CalibratedTimestamps() const { | |
| 432 | 11 | if (m_calibratedTimestamps == nullptr) { | |
| 433 | ✗ | return std::nullopt; | |
| 434 | } | ||
| 435 | 11 | const VkCalibratedTimestampInfoKHR domains[2] = { | |
| 436 | {VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_KHR, nullptr, VK_TIME_DOMAIN_DEVICE_KHR}, | ||
| 437 | {VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_KHR, nullptr, kHostTimeDomain}}; | ||
| 438 | 11 | uint64_t times[2] = {}; | |
| 439 | 11 | uint64_t deviation = 0; | |
| 440 | 11 | Check(m_calibratedTimestamps(m_device, 2, domains, times, &deviation), | |
| 441 | "vkGetCalibratedTimestampsKHR"); | ||
| 442 | 11 | return std::pair{times[0], HostClockNs(times[1])}; | |
| 443 | } | ||
| 444 | |||
| 445 | 40 | VulkanDevice::~VulkanDevice() { | |
| 446 | 20 | Destroy(); | |
| 447 | 40 | } | |
| 448 | |||
| 449 | 20 | void VulkanDevice::Destroy() { | |
| 450 | 20 | if (m_displayQueue) { | |
| 451 | ✗ | CloseQueue(*m_displayQueue); | |
| 452 | ✗ | m_displayQueue.reset(); | |
| 453 | } | ||
| 454 | 20 | if (m_device != VK_NULL_HANDLE) { | |
| 455 | 20 | m_functions.vkDestroyDescriptorPool(m_device, m_descriptorPool, nullptr); | |
| 456 | 20 | m_functions.vkDestroyPipelineLayout(m_device, m_pipelineLayout, nullptr); | |
| 457 | 20 | m_functions.vkDestroyDescriptorSetLayout(m_device, m_globalsLayout, nullptr); | |
| 458 | 20 | m_descriptorPool = VK_NULL_HANDLE; | |
| 459 | 20 | m_pipelineLayout = VK_NULL_HANDLE; | |
| 460 | 20 | m_globalsLayout = VK_NULL_HANDLE; | |
| 461 | 340 | for (VkSampler& sampler : m_samplers) { | |
| 462 | 320 | m_functions.vkDestroySampler(m_device, sampler, nullptr); | |
| 463 | 320 | sampler = VK_NULL_HANDLE; | |
| 464 | } | ||
| 465 | } | ||
| 466 | 20 | if (m_pipelineCache != VK_NULL_HANDLE) { | |
| 467 | 60 | const std::vector<char> data = Enumerate<char, size_t>([this](size_t* bytes, char* items) { | |
| 468 | 40 | m_functions.vkGetPipelineCacheData(m_device, m_pipelineCache, bytes, items); | |
| 469 | 20 | }); | |
| 470 | 20 | if (!data.empty()) { | |
| 471 | 100 | DiskCache(KernelCacheDirectory() / "vulkan") | |
| 472 | 40 | .Store(PipelineCacheKey(Adapter()), {data.data(), data.size()}); | |
| 473 | } | ||
| 474 | 20 | m_functions.vkDestroyPipelineCache(m_device, m_pipelineCache, nullptr); | |
| 475 | 20 | m_pipelineCache = VK_NULL_HANDLE; | |
| 476 | 20 | } | |
| 477 | 20 | ReleaseSpares(); | |
| 478 | 20 | m_memoryInterop.reset(); | |
| 479 | 20 | if (m_interopPool != VK_NULL_HANDLE) { | |
| 480 | 11 | vmaDestroyPool(m_allocator, m_interopPool); | |
| 481 | 11 | m_interopPool = VK_NULL_HANDLE; | |
| 482 | } | ||
| 483 | 20 | if (m_allocator != VK_NULL_HANDLE) { | |
| 484 | 20 | vmaDestroyAllocator(m_allocator); | |
| 485 | 20 | m_allocator = VK_NULL_HANDLE; | |
| 486 | } | ||
| 487 | 20 | if (m_device != VK_NULL_HANDLE) { | |
| 488 | 20 | m_functions.vkDestroyDevice(m_device, nullptr); | |
| 489 | 20 | m_device = VK_NULL_HANDLE; | |
| 490 | } | ||
| 491 | 20 | m_ownedInstance = {}; | |
| 492 | 20 | } | |
| 493 | |||
| 494 | 119994 | VkBufferUsageFlags VulkanDevice::UsageFlags(BufferUsage usage) const { | |
| 495 | // NOLINTNEXTLINE(bugprone-signed-bitwise) | ||
| 496 | 119994 | VkBufferUsageFlags flags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | | |
| 497 | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | | ||
| 498 | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | | ||
| 499 | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; | ||
| 500 | 119994 | if (Has(usage, BufferUsage::Indirect)) { | |
| 501 | 842 | flags |= VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT; | |
| 502 | } | ||
| 503 | 119994 | if (GetTraversal() == Traversal::Hardware) { | |
| 504 | ✗ | if (Has(usage, BufferUsage::AccelBuildInput)) { | |
| 505 | ✗ | flags |= VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR; | |
| 506 | } | ||
| 507 | ✗ | if (Has(usage, BufferUsage::AccelStorage)) { | |
| 508 | ✗ | flags |= VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR; | |
| 509 | } | ||
| 510 | } | ||
| 511 | 119994 | return flags; | |
| 512 | } | ||
| 513 | |||
| 514 | namespace { | ||
| 515 | |||
| 516 | ✗ | VkAccelerationStructureTypeKHR VulkanType(AccelLevel level) { | |
| 517 | ✗ | return level == AccelLevel::Top ? VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR | |
| 518 | ✗ | : VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; | |
| 519 | } | ||
| 520 | |||
| 521 | } | ||
| 522 | |||
| 523 | ✗ | void VulkanBuild::Describe(const AccelInput& input) { | |
| 524 | ✗ | info.type = VulkanType(GetLevel(input)); | |
| 525 | ✗ | info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; | |
| 526 | ✗ | if (Refittable(input)) { | |
| 527 | ✗ | info.flags |= VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR; | |
| 528 | } | ||
| 529 | ✗ | info.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; | |
| 530 | ✗ | info.geometryCount = 1; | |
| 531 | ✗ | info.pGeometries = &geometry; | |
| 532 | ✗ | geometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR; | |
| 533 | ✗ | std::visit(Overloaded{ | |
| 534 | ✗ | [&](const AccelTriangles& t) { | |
| 535 | ✗ | geometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR; | |
| 536 | ✗ | VkAccelerationStructureGeometryTrianglesDataKHR& triangles = | |
| 537 | ✗ | geometry.geometry.triangles; | |
| 538 | ✗ | triangles.sType = | |
| 539 | VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR; | ||
| 540 | ✗ | triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; | |
| 541 | ✗ | triangles.vertexData.deviceAddress = t.vertices.Address(); | |
| 542 | ✗ | triangles.vertexStride = t.stride; | |
| 543 | ✗ | triangles.maxVertex = t.vertexCount > 0 ? t.vertexCount - 1 : 0; | |
| 544 | ✗ | triangles.indexType = | |
| 545 | ✗ | t.indices ? VK_INDEX_TYPE_UINT32 : VK_INDEX_TYPE_NONE_KHR; | |
| 546 | ✗ | triangles.indexData.deviceAddress = t.indices ? t.indices->Address() : 0; | |
| 547 | ✗ | primitives = t.count; | |
| 548 | ✗ | }, | |
| 549 | ✗ | [&](const AccelBoxes& b) { | |
| 550 | ✗ | geometry.geometryType = VK_GEOMETRY_TYPE_AABBS_KHR; | |
| 551 | ✗ | geometry.geometry.aabbs.sType = | |
| 552 | VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR; | ||
| 553 | ✗ | geometry.geometry.aabbs.data.deviceAddress = b.boxes.Address(); | |
| 554 | ✗ | geometry.geometry.aabbs.stride = 6 * sizeof(float); | |
| 555 | ✗ | primitives = b.count; | |
| 556 | ✗ | }, | |
| 557 | ✗ | [&](const AccelInstances& i) { | |
| 558 | ✗ | geometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR; | |
| 559 | ✗ | geometry.geometry.instances.sType = | |
| 560 | VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR; | ||
| 561 | ✗ | geometry.geometry.instances.arrayOfPointers = VK_FALSE; | |
| 562 | ✗ | geometry.geometry.instances.data.deviceAddress = i.instances.Address(); | |
| 563 | ✗ | primitives = i.count; | |
| 564 | ✗ | }, | |
| 565 | }, | ||
| 566 | input); | ||
| 567 | ✗ | } | |
| 568 | |||
| 569 | ✗ | AccelSizes VulkanDevice::HardwareBuildSizes(const AccelInput& input) const { | |
| 570 | ✗ | VulkanBuild build; | |
| 571 | ✗ | build.Describe(input); | |
| 572 | ✗ | VkAccelerationStructureBuildSizesInfoKHR sizes{ | |
| 573 | VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR}; | ||
| 574 | ✗ | m_functions.vkGetAccelerationStructureBuildSizesKHR( | |
| 575 | ✗ | m_device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &build.info, &build.primitives, | |
| 576 | &sizes); | ||
| 577 | ✗ | return {.storage = sizes.accelerationStructureSize, | |
| 578 | ✗ | .scratch = sizes.buildScratchSize, | |
| 579 | ✗ | .updateScratch = Refittable(input) ? sizes.updateScratchSize : 0}; | |
| 580 | } | ||
| 581 | |||
| 582 | ✗ | Ref<Accel> VulkanDevice::NewAccel(Ref<Buffer> storage, AccelLevel level, AccelSizes sizes, | |
| 583 | std::string label) { | ||
| 584 | ✗ | return Ref<Accel>(new VulkanAccel(*this, std::move(storage), level, sizes, std::move(label))); | |
| 585 | } | ||
| 586 | |||
| 587 | ✗ | VulkanAccel::VulkanAccel(VulkanDevice& owner, Ref<Buffer> storage, AccelLevel level, | |
| 588 | ✗ | AccelSizes sizes, std::string label) | |
| 589 | ✗ | : Accel(owner, std::move(storage), level, sizes, std::move(label)), m_owner(owner) { | |
| 590 | ✗ | VkAccelerationStructureCreateInfoKHR info{ | |
| 591 | VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR}; | ||
| 592 | ✗ | info.buffer = static_cast<VulkanBuffer&>(Storage()).Handle(); | |
| 593 | ✗ | info.size = Storage().Bytes(); | |
| 594 | ✗ | info.type = VulkanType(level); | |
| 595 | ✗ | const VolkDeviceTable& vk = owner.Functions(); | |
| 596 | ✗ | Check(vk.vkCreateAccelerationStructureKHR(owner.Handle(), &info, nullptr, &m_handle), | |
| 597 | "vkCreateAccelerationStructureKHR"); | ||
| 598 | ✗ | VkAccelerationStructureDeviceAddressInfoKHR address{ | |
| 599 | VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR}; | ||
| 600 | ✗ | address.accelerationStructure = m_handle; | |
| 601 | ✗ | m_address = vk.vkGetAccelerationStructureDeviceAddressKHR(owner.Handle(), &address); | |
| 602 | ✗ | owner.Name(VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR, uint64_t(m_handle), Label()); | |
| 603 | ✗ | } | |
| 604 | |||
| 605 | ✗ | VulkanAccel::~VulkanAccel() { | |
| 606 | ✗ | m_owner.Functions().vkDestroyAccelerationStructureKHR(m_owner.Handle(), m_handle, nullptr); | |
| 607 | ✗ | } | |
| 608 | |||
| 609 | 119983 | uint64_t VulkanDevice::BufferAddress(VkBuffer buffer) const { | |
| 610 | 119983 | VkBufferDeviceAddressInfo info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO}; | |
| 611 | 119983 | info.buffer = buffer; | |
| 612 | 119983 | return m_functions.vkGetBufferDeviceAddress(m_device, &info); | |
| 613 | } | ||
| 614 | |||
| 615 | 889 | VmaPool VulkanDevice::InteropPool() { | |
| 616 | 889 | const std::lock_guard lock(m_interopMutex); | |
| 617 | 889 | if (m_interopPool != VK_NULL_HANDLE) { | |
| 618 | return m_interopPool; | ||
| 619 | } | ||
| 620 | |||
| 621 | 11 | VkExternalMemoryBufferCreateInfo external{VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO}; | |
| 622 | 11 | external.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; | |
| 623 | 11 | VkBufferCreateInfo sample{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, &external}; | |
| 624 | 11 | sample.size = 65536; | |
| 625 | 11 | sample.usage = UsageFlags(BufferUsage::None); | |
| 626 | 11 | VmaAllocationCreateInfo wanted{}; | |
| 627 | 11 | wanted.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; | |
| 628 | 11 | uint32_t type = 0; | |
| 629 | 11 | Check(vmaFindMemoryTypeIndexForBufferInfo(m_allocator, &sample, &wanted, &type), | |
| 630 | "vmaFindMemoryTypeIndexForBufferInfo"); | ||
| 631 | |||
| 632 | 11 | m_exportInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; | |
| 633 | 11 | VmaPoolCreateInfo pool{}; | |
| 634 | 11 | pool.memoryTypeIndex = type; | |
| 635 | 11 | pool.pMemoryAllocateNext = &m_exportInfo; | |
| 636 | 11 | Check(vmaCreatePool(m_allocator, &pool, &m_interopPool), "vmaCreatePool"); | |
| 637 | 11 | return m_interopPool; | |
| 638 | 889 | } | |
| 639 | |||
| 640 | 2971 | MemoryInterop* VulkanDevice::GetMemoryInterop() { | |
| 641 | 2971 | const std::lock_guard lock(m_interopMutex); | |
| 642 | 2971 | if (!m_interopTried) { | |
| 643 | 13 | m_interopTried = true; | |
| 644 | 13 | if (m_sharesMemory) { | |
| 645 | 13 | try { | |
| 646 | 13 | m_memoryInterop = CreateMemoryInterop(*this); | |
| 647 | 13 | } catch (const UnsupportedError& error) { | |
| 648 | 13 | Log(LogLevel::Debug, "{} shares no memory: {}", Adapter().alias, error.what()); | |
| 649 | 13 | } | |
| 650 | } | ||
| 651 | } | ||
| 652 | 2971 | return m_memoryInterop.get(); | |
| 653 | 2971 | } | |
| 654 | |||
| 655 | 1193 | std::optional<DLDevice> VulkanDevice::SharedDevice() { | |
| 656 | 1193 | const MemoryInterop* interop = GetMemoryInterop(); | |
| 657 | 1193 | return interop != nullptr ? std::optional(interop->Device()) : std::nullopt; | |
| 658 | } | ||
| 659 | |||
| 660 | ✗ | Stream VulkanDevice::CopyStream() { | |
| 661 | ✗ | MemoryInterop* interop = GetMemoryInterop(); | |
| 662 | ✗ | return interop != nullptr ? Stream::Compute(interop->Stream()) : Stream::None(); | |
| 663 | } | ||
| 664 | |||
| 665 | ✗ | void VulkanDevice::ExternalCopy(BufferRange dst, uint64_t source, Stream producer) { | |
| 666 | ✗ | MemoryInterop& interop = *GetMemoryInterop(); | |
| 667 | ✗ | const ExternalAddress external = dst.GetBuffer().External(interop.Device().device_type); | |
| 668 | ✗ | interop.Copy(external.address + dst.Offset(), source, dst.Bytes(), | |
| 669 | ✗ | producer.IsCompute() ? producer.Handle() : nullptr); | |
| 670 | ✗ | } | |
| 671 | |||
| 672 | 239966 | VulkanBuffer::~VulkanBuffer() { | |
| 673 | 119983 | const bool reusable = | |
| 674 | 119983 | Has(Usage(), BufferUsage::Interop) && m_owner.GetMemoryInterop() != nullptr; | |
| 675 | ✗ | if (reusable && m_owner.KeepSpare({m_buffer, m_allocation, m_external, m_externalAddress, | |
| 676 | ✗ | Bytes(), Usage()})) { | |
| 677 | ✗ | return; | |
| 678 | } | ||
| 679 | 119983 | if (m_external != nullptr) { | |
| 680 | ✗ | ImportedMemory imported{m_external, m_externalAddress}; | |
| 681 | ✗ | m_owner.GetMemoryInterop()->Release(imported); | |
| 682 | } | ||
| 683 | 119983 | vmaDestroyBuffer(m_owner.Allocator(), m_buffer, m_allocation); | |
| 684 | 239966 | } | |
| 685 | |||
| 686 | namespace { | ||
| 687 | |||
| 688 | ✗ | ExportedMemory ExportMemory(const VulkanDevice& owner, VkBuffer buffer, VmaAllocation allocation, | |
| 689 | uint64_t bytes) { | ||
| 690 | ✗ | VmaAllocationInfo info{}; | |
| 691 | ✗ | vmaGetAllocationInfo(owner.Allocator(), allocation, &info); | |
| 692 | ✗ | VkMemoryGetFdInfoKHR get{VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR}; | |
| 693 | ✗ | get.memory = info.deviceMemory; | |
| 694 | ✗ | get.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; | |
| 695 | ✗ | ExportedMemory exported; | |
| 696 | ✗ | Check(owner.Functions().vkGetMemoryFdKHR(owner.Handle(), &get, &exported.fd), | |
| 697 | "vkGetMemoryFdKHR"); | ||
| 698 | |||
| 699 | ✗ | VkMemoryRequirements requirements{}; | |
| 700 | ✗ | owner.Functions().vkGetBufferMemoryRequirements(owner.Handle(), buffer, &requirements); | |
| 701 | ✗ | exported.memoryBytes = std::max<uint64_t>(info.size, requirements.size); | |
| 702 | ✗ | exported.offset = info.offset; | |
| 703 | ✗ | exported.bytes = bytes; | |
| 704 | ✗ | return exported; | |
| 705 | } | ||
| 706 | |||
| 707 | } | ||
| 708 | |||
| 709 | ✗ | ExternalAddress VulkanBuffer::External(DLDeviceType api) { | |
| 710 | ✗ | MemoryInterop* interop = m_owner.GetMemoryInterop(); | |
| 711 | ✗ | if (interop == nullptr || interop->Device().device_type != api || | |
| 712 | ✗ | !Has(Usage(), BufferUsage::Interop)) { | |
| 713 | ✗ | throw UnsupportedError("Buffer \"{}\" on {} isn't shared with device type {}", Label(), | |
| 714 | ✗ | m_owner.Adapter().alias, int(api)); | |
| 715 | } | ||
| 716 | |||
| 717 | ✗ | const std::lock_guard lock(m_externalMutex); | |
| 718 | ✗ | if (m_external == nullptr) { | |
| 719 | ✗ | const ImportedMemory imported = | |
| 720 | ✗ | interop->Import(ExportMemory(m_owner, m_buffer, m_allocation, Bytes())); | |
| 721 | ✗ | m_external = imported.external; | |
| 722 | ✗ | m_externalAddress = imported.address; | |
| 723 | } | ||
| 724 | ✗ | return {interop->Device(), m_externalAddress}; | |
| 725 | ✗ | } | |
| 726 | |||
| 727 | namespace { | ||
| 728 | |||
| 729 | 119983 | VmaAllocationCreateInfo MemoryAllocation(Memory memory) { | |
| 730 | 119983 | constexpr VkMemoryPropertyFlags kCoherent = | |
| 731 | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; | ||
| 732 | 119983 | VmaAllocationCreateInfo allocation{}; | |
| 733 | 119983 | switch (memory) { | |
| 734 | 119808 | case Memory::Device: | |
| 735 | 119808 | allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; | |
| 736 | 119808 | break; | |
| 737 | 91 | case Memory::Upload: | |
| 738 | 91 | allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; | |
| 739 | 91 | allocation.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | | |
| 740 | VMA_ALLOCATION_CREATE_MAPPED_BIT; | ||
| 741 | 91 | allocation.requiredFlags = kCoherent; | |
| 742 | 91 | break; | |
| 743 | 84 | case Memory::Readback: | |
| 744 | 84 | allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; | |
| 745 | 84 | allocation.flags = | |
| 746 | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; | ||
| 747 | 84 | allocation.requiredFlags = kCoherent; | |
| 748 | 84 | allocation.preferredFlags = VK_MEMORY_PROPERTY_HOST_CACHED_BIT; | |
| 749 | 84 | break; | |
| 750 | ✗ | case Memory::Shared: | |
| 751 | ✗ | allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; | |
| 752 | ✗ | allocation.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | | |
| 753 | VMA_ALLOCATION_CREATE_MAPPED_BIT; | ||
| 754 | ✗ | allocation.requiredFlags = kCoherent | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; | |
| 755 | ✗ | break; | |
| 756 | case Memory::Virtual: | ||
| 757 | break; | ||
| 758 | } | ||
| 759 | 119983 | return allocation; | |
| 760 | } | ||
| 761 | |||
| 762 | } | ||
| 763 | |||
| 764 | ✗ | bool VulkanDevice::KeepSpare(const SpareBuffer& spare) { | |
| 765 | ✗ | constexpr size_t kMostSpares = 32; | |
| 766 | ✗ | constexpr uint64_t kMostSpareBytes = 256ull << 20; | |
| 767 | ✗ | const std::lock_guard lock(m_spareMutex); | |
| 768 | ✗ | if (m_sparesClosed || m_spares.size() >= kMostSpares || | |
| 769 | ✗ | m_spareBytes + spare.bytes > kMostSpareBytes) { | |
| 770 | return false; | ||
| 771 | } | ||
| 772 | ✗ | m_spares.push_back(spare); | |
| 773 | ✗ | m_spareBytes += spare.bytes; | |
| 774 | ✗ | return true; | |
| 775 | ✗ | } | |
| 776 | |||
| 777 | 889 | std::optional<VulkanDevice::SpareBuffer> VulkanDevice::TakeSpare(uint64_t bytes, | |
| 778 | BufferUsage usage) { | ||
| 779 | 889 | const std::lock_guard lock(m_spareMutex); | |
| 780 | 889 | const auto found = std::ranges::find_if(m_spares, [&](const SpareBuffer& spare) { | |
| 781 | ✗ | return spare.bytes == bytes && spare.usage == usage; | |
| 782 | }); | ||
| 783 | 889 | if (found == m_spares.end()) { | |
| 784 | 889 | return std::nullopt; | |
| 785 | } | ||
| 786 | ✗ | const SpareBuffer spare = *found; | |
| 787 | ✗ | m_spares.erase(found); | |
| 788 | ✗ | m_spareBytes -= spare.bytes; | |
| 789 | ✗ | return spare; | |
| 790 | 889 | } | |
| 791 | |||
| 792 | 20 | void VulkanDevice::ReleaseSpares() { | |
| 793 | 20 | std::vector<SpareBuffer> spares; | |
| 794 | 20 | { | |
| 795 | 20 | const std::lock_guard lock(m_spareMutex); | |
| 796 | 20 | m_sparesClosed = true; | |
| 797 | 20 | spares.swap(m_spares); | |
| 798 | 20 | m_spareBytes = 0; | |
| 799 | 20 | } | |
| 800 | 20 | for (const SpareBuffer& spare : spares) { | |
| 801 | ✗ | if (spare.external != nullptr) { | |
| 802 | ✗ | ImportedMemory imported{spare.external, spare.externalAddress}; | |
| 803 | ✗ | m_memoryInterop->Release(imported); | |
| 804 | } | ||
| 805 | ✗ | vmaDestroyBuffer(m_allocator, spare.buffer, spare.allocation); | |
| 806 | } | ||
| 807 | 20 | } | |
| 808 | |||
| 809 | 10 | std::vector<MemoryHeap> VulkanDevice::Heaps() const { | |
| 810 | 10 | const VkPhysicalDeviceMemoryProperties* properties = nullptr; | |
| 811 | 10 | vmaGetMemoryProperties(m_allocator, &properties); | |
| 812 | 10 | std::vector<VmaBudget> budgets(properties->memoryHeapCount); | |
| 813 | 10 | vmaGetHeapBudgets(m_allocator, budgets.data()); | |
| 814 | 10 | std::vector<MemoryHeap> heaps; | |
| 815 | 20 | for (uint32_t i = 0; i < properties->memoryHeapCount; ++i) { | |
| 816 | 10 | const VkMemoryHeap& heap = properties->memoryHeaps[i]; | |
| 817 | 10 | heaps.push_back({.usage = budgets[i].usage, | |
| 818 | 10 | .budget = budgets[i].budget, | |
| 819 | 10 | .size = heap.size, | |
| 820 | 10 | .deviceLocal = (heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0}); | |
| 821 | } | ||
| 822 | 10 | return heaps; | |
| 823 | 10 | } | |
| 824 | |||
| 825 | 119983 | Ref<Buffer> VulkanDevice::NewBuffer(const BufferDesc& desc) { | |
| 826 | 119983 | VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; | |
| 827 | 119983 | info.size = desc.bytes; | |
| 828 | 119983 | info.usage = UsageFlags(desc.usage); | |
| 829 | 119983 | info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; | |
| 830 | 119983 | VkExternalMemoryBufferCreateInfo external{VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO}; | |
| 831 | 119983 | const bool interop = | |
| 832 | 119983 | Has(desc.usage, BufferUsage::Interop) && (GetMemoryInterop() != nullptr || m_sharesMemory); | |
| 833 | if (interop) { | ||
| 834 | 889 | if (desc.memory != Memory::Device) { | |
| 835 | ✗ | throw UnsupportedError("Buffer \"{}\" asks for interop, which only device memory has", | |
| 836 | ✗ | desc.label); | |
| 837 | } | ||
| 838 | 889 | external.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; | |
| 839 | 889 | info.pNext = &external; | |
| 840 | } | ||
| 841 | |||
| 842 | 889 | if (interop) { | |
| 843 | 889 | if (const std::optional<SpareBuffer> spare = TakeSpare(desc.bytes, desc.usage)) { | |
| 844 | ✗ | vmaSetAllocationName(m_allocator, spare->allocation, desc.label.c_str()); | |
| 845 | ✗ | Name(VK_OBJECT_TYPE_BUFFER, uint64_t(spare->buffer), desc.label); | |
| 846 | ✗ | auto* kept = new VulkanBuffer(*this, desc, spare->buffer, spare->allocation, nullptr, | |
| 847 | ✗ | spare->external, spare->externalAddress); | |
| 848 | ✗ | kept->exportable = true; | |
| 849 | ✗ | return Ref<Buffer>(kept); | |
| 850 | } | ||
| 851 | } | ||
| 852 | |||
| 853 | 119983 | VmaAllocationCreateInfo allocation = MemoryAllocation(desc.memory); | |
| 854 | 119983 | if (interop) { | |
| 855 | 889 | allocation.pool = InteropPool(); | |
| 856 | 889 | allocation.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; | |
| 857 | } | ||
| 858 | |||
| 859 | 119983 | VkBuffer buffer = VK_NULL_HANDLE; | |
| 860 | 119983 | VmaAllocation allocated = VK_NULL_HANDLE; | |
| 861 | 119983 | VmaAllocationInfo allocatedInfo{}; | |
| 862 | 119983 | Check(vmaCreateBufferWithAlignment(m_allocator, &info, &allocation, kBufferAlignment, &buffer, | |
| 863 | &allocated, &allocatedInfo), | ||
| 864 | "vmaCreateBufferWithAlignment"); | ||
| 865 | 119983 | vmaSetAllocationName(m_allocator, allocated, desc.label.c_str()); | |
| 866 | 119983 | Name(VK_OBJECT_TYPE_BUFFER, uint64_t(buffer), desc.label); | |
| 867 | 119983 | auto* created = new VulkanBuffer(*this, desc, buffer, allocated, allocatedInfo.pMappedData); | |
| 868 | 119983 | created->exportable = interop; | |
| 869 | 119983 | return Ref<Buffer>(created); | |
| 870 | } | ||
| 871 | |||
| 872 | ✗ | Ref<Buffer> VulkanDevice::Import(Buffer& foreign) { | |
| 873 | ✗ | auto* buffer = dynamic_cast<VulkanBuffer*>(&foreign); | |
| 874 | ✗ | if (buffer == nullptr || &buffer->Owner() == this || !buffer->exportable || !m_sharesMemory || | |
| 875 | ✗ | !buffer->Owner().SharesExportedMemory() || | |
| 876 | ✗ | buffer->Owner().Adapter().uuid != Adapter().uuid) { | |
| 877 | ✗ | return {}; | |
| 878 | } | ||
| 879 | ✗ | return buffer->SharedWith(*this); | |
| 880 | } | ||
| 881 | |||
| 882 | ✗ | Ref<Buffer> VulkanBuffer::SharedWith(VulkanDevice& reader) { | |
| 883 | ✗ | const std::lock_guard lock(m_externalMutex); | |
| 884 | ✗ | for (const auto& [device, view] : m_shared) { | |
| 885 | ✗ | if (device == &reader) { | |
| 886 | ✗ | return view; | |
| 887 | } | ||
| 888 | } | ||
| 889 | ✗ | Ref<Buffer> view = reader.ImportMemory(m_owner, m_buffer, m_allocation, | |
| 890 | ✗ | {.bytes = Bytes(), .label = Label() + " (shared)"}); | |
| 891 | ✗ | if (view) { | |
| 892 | ✗ | m_shared.emplace_back(&reader, view); | |
| 893 | } | ||
| 894 | ✗ | return view; | |
| 895 | ✗ | } | |
| 896 | |||
| 897 | ✗ | ImportedVulkanBuffer::~ImportedVulkanBuffer() { | |
| 898 | ✗ | m_owner.Functions().vkFreeMemory(m_owner.Handle(), m_memory, nullptr); | |
| 899 | ✗ | } | |
| 900 | |||
| 901 | ✗ | Ref<Buffer> VulkanDevice::ImportMemory(const VulkanDevice& owner, VkBuffer buffer, | |
| 902 | VmaAllocation allocation, const BufferDesc& desc) { | ||
| 903 | #ifdef _WIN32 | ||
| 904 | (void)owner; | ||
| 905 | (void)buffer; | ||
| 906 | (void)allocation; | ||
| 907 | (void)desc; | ||
| 908 | return {}; | ||
| 909 | #else | ||
| 910 | ✗ | const ExportedMemory exported = ExportMemory(owner, buffer, allocation, desc.bytes); | |
| 911 | ✗ | VmaAllocationInfo2 info{}; | |
| 912 | ✗ | vmaGetAllocationInfo2(owner.Allocator(), allocation, &info); | |
| 913 | |||
| 914 | ✗ | VkExternalMemoryBufferCreateInfo external{VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO}; | |
| 915 | ✗ | external.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; | |
| 916 | ✗ | VkBufferCreateInfo create{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, &external}; | |
| 917 | ✗ | create.size = desc.bytes; | |
| 918 | ✗ | create.usage = UsageFlags(desc.usage); | |
| 919 | ✗ | create.sharingMode = VK_SHARING_MODE_EXCLUSIVE; | |
| 920 | ✗ | VkBuffer view = VK_NULL_HANDLE; | |
| 921 | ✗ | if (m_functions.vkCreateBuffer(m_device, &create, nullptr, &view) != VK_SUCCESS) { | |
| 922 | ✗ | close(exported.fd); | |
| 923 | ✗ | return {}; | |
| 924 | } | ||
| 925 | ✗ | VkMemoryDedicatedAllocateInfo dedicated{VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO}; | |
| 926 | ✗ | dedicated.buffer = view; | |
| 927 | ✗ | VkImportMemoryFdInfoKHR import{VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, | |
| 928 | ✗ | info.dedicatedMemory ? &dedicated : nullptr}; | |
| 929 | ✗ | import.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; | |
| 930 | ✗ | import.fd = exported.fd; | |
| 931 | ✗ | VkMemoryAllocateFlagsInfo flags{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO, &import}; | |
| 932 | ✗ | flags.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT; | |
| 933 | ✗ | VkMemoryAllocateInfo allocate{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &flags}; | |
| 934 | ✗ | allocate.allocationSize = info.blockSize; | |
| 935 | ✗ | allocate.memoryTypeIndex = info.allocationInfo.memoryType; | |
| 936 | ✗ | VkDeviceMemory memory = VK_NULL_HANDLE; | |
| 937 | ✗ | if (m_functions.vkAllocateMemory(m_device, &allocate, nullptr, &memory) != VK_SUCCESS) { | |
| 938 | ✗ | close(exported.fd); | |
| 939 | ✗ | m_functions.vkDestroyBuffer(m_device, view, nullptr); | |
| 940 | ✗ | return {}; | |
| 941 | } | ||
| 942 | ✗ | if (m_functions.vkBindBufferMemory(m_device, view, memory, info.allocationInfo.offset) != | |
| 943 | VK_SUCCESS) { | ||
| 944 | ✗ | m_functions.vkFreeMemory(m_device, memory, nullptr); | |
| 945 | ✗ | m_functions.vkDestroyBuffer(m_device, view, nullptr); | |
| 946 | ✗ | return {}; | |
| 947 | } | ||
| 948 | ✗ | Name(VK_OBJECT_TYPE_BUFFER, uint64_t(view), desc.label); | |
| 949 | ✗ | return Ref<Buffer>(new ImportedVulkanBuffer(*this, desc, view, memory)); | |
| 950 | #endif | ||
| 951 | } | ||
| 952 | |||
| 953 | namespace { | ||
| 954 | |||
| 955 | ✗ | std::string Value(const VkPipelineExecutableStatisticKHR& statistic) { | |
| 956 | ✗ | switch (statistic.format) { | |
| 957 | ✗ | case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_BOOL32_KHR: | |
| 958 | ✗ | return statistic.value.b32 == VK_TRUE ? "true" : "false"; | |
| 959 | ✗ | case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_INT64_KHR: | |
| 960 | ✗ | return std::to_string(statistic.value.i64); | |
| 961 | ✗ | case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_UINT64_KHR: | |
| 962 | ✗ | return std::to_string(statistic.value.u64); | |
| 963 | ✗ | case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_FLOAT64_KHR: | |
| 964 | ✗ | return fmt::format("{}", statistic.value.f64); | |
| 965 | ✗ | default: | |
| 966 | ✗ | return ""; | |
| 967 | } | ||
| 968 | } | ||
| 969 | |||
| 970 | struct KnownStatistic { | ||
| 971 | const char* name; | ||
| 972 | std::optional<uint32_t> KernelStats::* field; | ||
| 973 | }; | ||
| 974 | |||
| 975 | constexpr KnownStatistic kKnownStatistics[] = { | ||
| 976 | {"Register Count", &KernelStats::registers}, | ||
| 977 | {"VGPRs", &KernelStats::registers}, | ||
| 978 | {"Scratch Memory Size", &KernelStats::spillBytes}, | ||
| 979 | {"Shared Memory Size", &KernelStats::sharedBytes}, | ||
| 980 | {"Workgroup Memory Size", &KernelStats::sharedBytes}, | ||
| 981 | }; | ||
| 982 | |||
| 983 | 1117 | VkShaderModule CreateShaderModule(const VolkDeviceTable& functions, VkDevice device, | |
| 984 | const KernelCode& code) { | ||
| 985 | 1117 | std::vector<uint32_t> words(code.code.size() / 4); | |
| 986 | 1117 | std::memcpy(words.data(), code.code.data(), words.size() * 4); | |
| 987 | 1117 | VkShaderModuleCreateInfo shader{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; | |
| 988 | 1117 | shader.codeSize = words.size() * 4; | |
| 989 | 1117 | shader.pCode = words.data(); | |
| 990 | 1117 | VkShaderModule module = VK_NULL_HANDLE; | |
| 991 | 1117 | Check(functions.vkCreateShaderModule(device, &shader, nullptr, &module), | |
| 992 | "vkCreateShaderModule"); | ||
| 993 | 1117 | return module; | |
| 994 | 1117 | } | |
| 995 | |||
| 996 | ✗ | KernelStats ReadStatistics(const VolkDeviceTable& functions, VkDevice device, VkPipeline pipeline) { | |
| 997 | ✗ | const VkPipelineExecutableInfoKHR executable{VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INFO_KHR, | |
| 998 | ✗ | nullptr, pipeline, 0}; | |
| 999 | ✗ | uint32_t count = 0; | |
| 1000 | ✗ | Check(functions.vkGetPipelineExecutableStatisticsKHR(device, &executable, &count, nullptr), | |
| 1001 | "vkGetPipelineExecutableStatisticsKHR"); | ||
| 1002 | ✗ | std::vector<VkPipelineExecutableStatisticKHR> statistics( | |
| 1003 | ✗ | count, {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_STATISTIC_KHR}); | |
| 1004 | ✗ | Check(functions.vkGetPipelineExecutableStatisticsKHR(device, &executable, &count, | |
| 1005 | statistics.data()), | ||
| 1006 | "vkGetPipelineExecutableStatisticsKHR"); | ||
| 1007 | |||
| 1008 | ✗ | KernelStats stats{.cached = true}; | |
| 1009 | ✗ | for (const VkPipelineExecutableStatisticKHR& statistic : statistics) { | |
| 1010 | ✗ | stats.driver.push_back({statistic.name, Value(statistic)}); | |
| 1011 | ✗ | for (const KnownStatistic& known : kKnownStatistics) { | |
| 1012 | ✗ | if (std::string_view(statistic.name) == known.name && | |
| 1013 | ✗ | statistic.format == VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_UINT64_KHR) { | |
| 1014 | ✗ | stats.*known.field = uint32_t(statistic.value.u64); | |
| 1015 | } | ||
| 1016 | } | ||
| 1017 | } | ||
| 1018 | ✗ | return stats; | |
| 1019 | ✗ | } | |
| 1020 | |||
| 1021 | } | ||
| 1022 | |||
| 1023 | 1117 | KernelStats VulkanKernel::Load(const KernelCode& code, const DiskCache&, const DeviceGlobals&) { | |
| 1024 | 1117 | const VolkDeviceTable& functions = m_owner.Functions(); | |
| 1025 | 1117 | const VkDevice device = m_owner.Handle(); | |
| 1026 | 1117 | m_owner.Globals(); | |
| 1027 | 1117 | const VkShaderModule module = CreateShaderModule(functions, device, code); | |
| 1028 | |||
| 1029 | 1117 | VkComputePipelineCreateInfo pipeline{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; | |
| 1030 | 1117 | pipeline.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; | |
| 1031 | 1117 | pipeline.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; | |
| 1032 | 1117 | pipeline.stage.module = module; | |
| 1033 | 1117 | pipeline.stage.pName = Name().c_str(); | |
| 1034 | 1117 | pipeline.layout = m_owner.PipelineLayout(); | |
| 1035 | 1117 | if (code.shape.groupSize.x % m_owner.Adapter().features.waveSizeMax == 0) { | |
| 1036 | 1102 | pipeline.stage.flags |= VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT; | |
| 1037 | } | ||
| 1038 | 1117 | if (m_owner.CapturesStatistics()) { | |
| 1039 | ✗ | pipeline.flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; | |
| 1040 | } | ||
| 1041 | 1117 | const VkResult created = functions.vkCreateComputePipelines(device, m_owner.PipelineCache(), 1, | |
| 1042 | &pipeline, nullptr, &m_pipeline); | ||
| 1043 | 1117 | functions.vkDestroyShaderModule(device, module, nullptr); | |
| 1044 | 1117 | Check(created, "vkCreateComputePipelines"); | |
| 1045 | 1117 | m_owner.Name(VK_OBJECT_TYPE_PIPELINE, uint64_t(m_pipeline), | |
| 1046 | 1117 | fmt::format("{} of program \"{}\"", Name(), GetProgram().Label())); | |
| 1047 | |||
| 1048 | 1117 | if (!m_owner.CapturesStatistics()) { | |
| 1049 | 1117 | return {.cached = true}; | |
| 1050 | } | ||
| 1051 | ✗ | return ReadStatistics(functions, device, m_pipeline); | |
| 1052 | } | ||
| 1053 | |||
| 1054 | 2238 | VulkanKernel::~VulkanKernel() { | |
| 1055 | 1119 | m_owner.Functions().vkDestroyPipeline(m_owner.Handle(), m_pipeline, nullptr); | |
| 1056 | 2238 | } | |
| 1057 | |||
| 1058 | 233727 | VkDescriptorSet VulkanDevice::Globals() { | |
| 1059 | 233727 | std::call_once(m_globalsWritten, [this] { | |
| 1060 | 17 | VkDescriptorBufferInfo buffer{}; | |
| 1061 | 17 | buffer.buffer = static_cast<const VulkanBuffer&>(GlobalsBuffer()).Handle(); | |
| 1062 | 17 | buffer.range = sizeof(DeviceGlobals); | |
| 1063 | 17 | VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; | |
| 1064 | 17 | write.dstSet = m_globals; | |
| 1065 | 17 | write.descriptorCount = 1; | |
| 1066 | 17 | write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; | |
| 1067 | 17 | write.pBufferInfo = &buffer; | |
| 1068 | 17 | m_functions.vkUpdateDescriptorSets(m_device, 1, &write, 0, nullptr); | |
| 1069 | 17 | }); | |
| 1070 | 233727 | return m_globals; | |
| 1071 | } | ||
| 1072 | |||
| 1073 | namespace { | ||
| 1074 | |||
| 1075 | 608 | VkSamplerAddressMode VulkanAddressMode(uint32_t wrap) { | |
| 1076 | 608 | switch (TextureWrap(wrap)) { | |
| 1077 | case TextureWrap::Repeat: | ||
| 1078 | return VK_SAMPLER_ADDRESS_MODE_REPEAT; | ||
| 1079 | 152 | case TextureWrap::Mirror: | |
| 1080 | 152 | return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; | |
| 1081 | 152 | case TextureWrap::Clamp: | |
| 1082 | 152 | return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; | |
| 1083 | 152 | default: | |
| 1084 | 152 | return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; | |
| 1085 | } | ||
| 1086 | } | ||
| 1087 | |||
| 1088 | 28 | VkFormat VulkanFormat(const TextureDesc& desc) { | |
| 1089 | 28 | const bool encoded = desc.colorSpace == TexelColorSpace::Srgb; | |
| 1090 | 56 | const auto pick = [&](VkFormat linear, VkFormat srgb) { | |
| 1091 | 3 | if (encoded && srgb == VK_FORMAT_UNDEFINED) { | |
| 1092 | ✗ | throw Error("texture \"{}\" is sRGB in a format with no sRGB variant", desc.label); | |
| 1093 | } | ||
| 1094 | 6 | return encoded ? srgb : linear; | |
| 1095 | 28 | }; | |
| 1096 | 28 | switch (desc.format) { | |
| 1097 | ✗ | case TexelFormat::R8: | |
| 1098 | ✗ | return pick(VK_FORMAT_R8_UNORM, VK_FORMAT_R8_SRGB); | |
| 1099 | ✗ | case TexelFormat::Rg8: | |
| 1100 | ✗ | return pick(VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8_SRGB); | |
| 1101 | 25 | case TexelFormat::Rgba8: | |
| 1102 | 25 | return pick(VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SRGB); | |
| 1103 | ✗ | case TexelFormat::R16: | |
| 1104 | ✗ | return pick(VK_FORMAT_R16_UNORM, VK_FORMAT_UNDEFINED); | |
| 1105 | ✗ | case TexelFormat::Rg16: | |
| 1106 | ✗ | return pick(VK_FORMAT_R16G16_UNORM, VK_FORMAT_UNDEFINED); | |
| 1107 | ✗ | case TexelFormat::Rgba16: | |
| 1108 | ✗ | return pick(VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_UNDEFINED); | |
| 1109 | ✗ | case TexelFormat::R16f: | |
| 1110 | ✗ | return pick(VK_FORMAT_R16_SFLOAT, VK_FORMAT_UNDEFINED); | |
| 1111 | ✗ | case TexelFormat::Rg16f: | |
| 1112 | ✗ | return pick(VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_UNDEFINED); | |
| 1113 | 3 | case TexelFormat::Rgba16f: | |
| 1114 | 3 | return pick(VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_UNDEFINED); | |
| 1115 | ✗ | case TexelFormat::R32f: | |
| 1116 | ✗ | return pick(VK_FORMAT_R32_SFLOAT, VK_FORMAT_UNDEFINED); | |
| 1117 | ✗ | case TexelFormat::Rg32f: | |
| 1118 | ✗ | return pick(VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_UNDEFINED); | |
| 1119 | ✗ | case TexelFormat::Rgba32f: | |
| 1120 | ✗ | return pick(VK_FORMAT_R32G32B32A32_SFLOAT, VK_FORMAT_UNDEFINED); | |
| 1121 | ✗ | case TexelFormat::Bc1: | |
| 1122 | ✗ | return pick(VK_FORMAT_BC1_RGBA_UNORM_BLOCK, VK_FORMAT_BC1_RGBA_SRGB_BLOCK); | |
| 1123 | ✗ | case TexelFormat::Bc3: | |
| 1124 | ✗ | return pick(VK_FORMAT_BC3_UNORM_BLOCK, VK_FORMAT_BC3_SRGB_BLOCK); | |
| 1125 | ✗ | case TexelFormat::Bc4: | |
| 1126 | ✗ | return pick(VK_FORMAT_BC4_UNORM_BLOCK, VK_FORMAT_UNDEFINED); | |
| 1127 | ✗ | case TexelFormat::Bc5: | |
| 1128 | ✗ | return pick(VK_FORMAT_BC5_UNORM_BLOCK, VK_FORMAT_UNDEFINED); | |
| 1129 | } | ||
| 1130 | return VK_FORMAT_UNDEFINED; | ||
| 1131 | } | ||
| 1132 | |||
| 1133 | } | ||
| 1134 | |||
| 1135 | 19 | void VulkanDevice::CreateSamplers() { | |
| 1136 | 19 | const bool anisotropic = Adapter().features.anisotropicFiltering; | |
| 1137 | 323 | for (uint32_t wrap = 0; wrap < m_samplers.size(); ++wrap) { | |
| 1138 | 304 | VkSamplerCreateInfo info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; | |
| 1139 | 304 | info.magFilter = VK_FILTER_LINEAR; | |
| 1140 | 304 | info.minFilter = VK_FILTER_LINEAR; | |
| 1141 | 304 | info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; | |
| 1142 | 304 | info.anisotropyEnable = anisotropic ? VK_TRUE : VK_FALSE; | |
| 1143 | 304 | info.maxAnisotropy = anisotropic ? std::min(16.0f, Adapter().limits.maxAnisotropy) : 1.0f; | |
| 1144 | 304 | info.addressModeU = VulkanAddressMode(wrap >> kTextureWrapBits); | |
| 1145 | 304 | info.addressModeV = VulkanAddressMode(wrap & ((1u << kTextureWrapBits) - 1)); | |
| 1146 | 304 | info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; | |
| 1147 | 304 | info.maxLod = VK_LOD_CLAMP_NONE; | |
| 1148 | 304 | info.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; | |
| 1149 | 304 | Check(m_functions.vkCreateSampler(m_device, &info, nullptr, &m_samplers[wrap]), | |
| 1150 | "vkCreateSampler"); | ||
| 1151 | } | ||
| 1152 | 19 | } | |
| 1153 | |||
| 1154 | 28 | uint32_t VulkanDevice::AcquireTextureSlot(VkImageView view) { | |
| 1155 | 28 | const std::lock_guard lock(m_textureMutex); | |
| 1156 | 28 | uint32_t slot = 0; | |
| 1157 | 28 | if (!m_freeTextureSlots.empty()) { | |
| 1158 | 19 | slot = m_freeTextureSlots.back(); | |
| 1159 | 19 | m_freeTextureSlots.pop_back(); | |
| 1160 | 9 | } else if (m_textureSlots < Adapter().limits.maxTextures) { | |
| 1161 | 9 | slot = m_textureSlots++; | |
| 1162 | } else { | ||
| 1163 | ✗ | throw Error("{} holds at most {} textures at once", Adapter().alias, | |
| 1164 | ✗ | Adapter().limits.maxTextures); | |
| 1165 | } | ||
| 1166 | |||
| 1167 | 28 | VkDescriptorImageInfo image{}; | |
| 1168 | 28 | image.imageView = view; | |
| 1169 | 28 | image.imageLayout = VK_IMAGE_LAYOUT_GENERAL; | |
| 1170 | 28 | VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; | |
| 1171 | 28 | write.dstSet = Globals(); | |
| 1172 | 28 | write.dstBinding = kTextureBinding; | |
| 1173 | 28 | write.dstArrayElement = slot; | |
| 1174 | 28 | write.descriptorCount = 1; | |
| 1175 | 28 | write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; | |
| 1176 | 28 | write.pImageInfo = ℑ | |
| 1177 | 28 | m_functions.vkUpdateDescriptorSets(m_device, 1, &write, 0, nullptr); | |
| 1178 | 28 | return slot; | |
| 1179 | 28 | } | |
| 1180 | |||
| 1181 | 28 | void VulkanDevice::ReleaseTextureSlot(uint32_t slot) { | |
| 1182 | 28 | const std::lock_guard lock(m_textureMutex); | |
| 1183 | 28 | m_freeTextureSlots.push_back(slot); | |
| 1184 | 28 | } | |
| 1185 | |||
| 1186 | 28 | Ref<Texture> VulkanDevice::NewTexture(const TextureDesc& desc) { | |
| 1187 | 28 | return Ref<Texture>(new VulkanTexture(*this, desc)); | |
| 1188 | } | ||
| 1189 | |||
| 1190 | 28 | VulkanTexture::VulkanTexture(VulkanDevice& owner, const TextureDesc& desc) | |
| 1191 | 28 | : Texture(owner, desc), m_owner(owner) { | |
| 1192 | 28 | const VkFormat format = VulkanFormat(desc); | |
| 1193 | 28 | VkFormatProperties properties{}; | |
| 1194 | 28 | vkGetPhysicalDeviceFormatProperties(owner.PhysicalDevice(), format, &properties); | |
| 1195 | 28 | constexpr VkFormatFeatureFlags kNeeded = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | | |
| 1196 | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | | ||
| 1197 | VK_FORMAT_FEATURE_TRANSFER_DST_BIT; | ||
| 1198 | 28 | if ((properties.optimalTilingFeatures & kNeeded) != kNeeded) { | |
| 1199 | ✗ | throw UnsupportedError("{} can't filter the format of texture \"{}\"", | |
| 1200 | ✗ | owner.Adapter().alias, desc.label); | |
| 1201 | } | ||
| 1202 | |||
| 1203 | 28 | VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; | |
| 1204 | 28 | info.imageType = VK_IMAGE_TYPE_2D; | |
| 1205 | 28 | info.format = format; | |
| 1206 | 28 | info.extent = {desc.width, desc.height, 1}; | |
| 1207 | 28 | info.mipLevels = desc.levels; | |
| 1208 | 28 | info.arrayLayers = 1; | |
| 1209 | 28 | info.samples = VK_SAMPLE_COUNT_1_BIT; | |
| 1210 | 28 | info.tiling = VK_IMAGE_TILING_OPTIMAL; | |
| 1211 | 28 | info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; | |
| 1212 | 28 | info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; | |
| 1213 | 28 | info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
| 1214 | 28 | VmaAllocationCreateInfo allocation{}; | |
| 1215 | 28 | allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; | |
| 1216 | 28 | Check(vmaCreateImage(owner.Allocator(), &info, &allocation, &m_image, &m_allocation, nullptr), | |
| 1217 | "vmaCreateImage"); | ||
| 1218 | |||
| 1219 | 28 | try { | |
| 1220 | 28 | vmaSetAllocationName(owner.Allocator(), m_allocation, desc.label.c_str()); | |
| 1221 | 28 | owner.Name(VK_OBJECT_TYPE_IMAGE, uint64_t(m_image), desc.label); | |
| 1222 | 28 | VkImageViewCreateInfo view{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; | |
| 1223 | 28 | view.image = m_image; | |
| 1224 | 28 | view.viewType = VK_IMAGE_VIEW_TYPE_2D; | |
| 1225 | 28 | view.format = format; | |
| 1226 | 28 | view.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, desc.levels, 0, 1}; | |
| 1227 | 28 | Check(owner.Functions().vkCreateImageView(owner.Handle(), &view, nullptr, &m_view), | |
| 1228 | "vkCreateImageView"); | ||
| 1229 | 28 | m_slot = owner.AcquireTextureSlot(m_view); | |
| 1230 | ✗ | } catch (...) { | |
| 1231 | ✗ | Destroy(); | |
| 1232 | ✗ | throw; | |
| 1233 | ✗ | } | |
| 1234 | 28 | m_handle = TextureHandle(*m_slot, desc.wrapS, desc.wrapT); | |
| 1235 | 28 | } | |
| 1236 | |||
| 1237 | 56 | VulkanTexture::~VulkanTexture() { | |
| 1238 | 28 | Destroy(); | |
| 1239 | 56 | } | |
| 1240 | |||
| 1241 | 28 | void VulkanTexture::Destroy() noexcept { | |
| 1242 | 28 | if (m_slot) { | |
| 1243 | 28 | m_owner.ReleaseTextureSlot(*m_slot); | |
| 1244 | 28 | m_slot.reset(); | |
| 1245 | } | ||
| 1246 | 28 | m_owner.Functions().vkDestroyImageView(m_owner.Handle(), m_view, nullptr); | |
| 1247 | 28 | vmaDestroyImage(m_owner.Allocator(), m_image, m_allocation); | |
| 1248 | 28 | m_view = VK_NULL_HANDLE; | |
| 1249 | 28 | m_image = VK_NULL_HANDLE; | |
| 1250 | 28 | m_allocation = VK_NULL_HANDLE; | |
| 1251 | 28 | } | |
| 1252 | |||
| 1253 | 1119 | std::unique_ptr<Kernel> VulkanDevice::NewKernel() { | |
| 1254 | 1119 | return std::make_unique<VulkanKernel>(*this); | |
| 1255 | } | ||
| 1256 | |||
| 1257 | } | ||
| 1258 |