device/kernel.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | /// @file kernel.cpp | ||
| 3 | /// @brief Creating compute pipelines and recording dispatches. | ||
| 4 | #include "ernie/device/kernel.h" | ||
| 5 | |||
| 6 | #include "ernie/util/on_failure.h" | ||
| 7 | |||
| 8 | #include <stdexcept> | ||
| 9 | |||
| 10 | namespace ernie { | ||
| 11 | |||
| 12 | 100 | ComputeKernel::ComputeKernel(DeviceContext& context, std::span<const std::uint32_t> spirv, | |
| 13 | std::string_view name, std::uint32_t parameterBytes, | ||
| 14 | 100 | const BindlessHeap* heap, std::string_view entryPoint) | |
| 15 | 100 | : device_(context.Device()), name_(name), parameterBytes_(parameterBytes), | |
| 16 | 100 | heap_(heap != nullptr ? heap->Set() : VK_NULL_HANDLE) { | |
| 17 | 100 | ERNIE_ZONE("Create compute pipeline"); | |
| 18 | 100 | ERNIE_ZONE_TEXT(name_); | |
| 19 | 100 | if (parameterBytes > context.Limits().maxPushConstantsSize) { | |
| 20 | 1 | throw DeviceError(DeviceErrorKind::Unsupported, | |
| 21 | 2 | name_ + " passes more bytes per dispatch than the device allows"); | |
| 22 | } | ||
| 23 | 99 | if (parameterBytes % 4 != 0) { | |
| 24 | 2 | throw std::invalid_argument(name_ + " passes bytes per dispatch in 4-byte words"); | |
| 25 | } | ||
| 26 | 98 | const VkPushConstantRange range{.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, | |
| 27 | 98 | .size = parameterBytes}; | |
| 28 | 98 | const VkDescriptorSetLayout heapLayout = heap != nullptr ? heap->Layout() : VK_NULL_HANDLE; | |
| 29 | 98 | const VkPipelineLayoutCreateInfo layout{.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, | |
| 30 | 98 | .setLayoutCount = heap != nullptr ? 1u : 0u, | |
| 31 | .pSetLayouts = &heapLayout, | ||
| 32 | 98 | .pushConstantRangeCount = parameterBytes > 0 ? 1u : 0u, | |
| 33 | 98 | .pPushConstantRanges = &range}; | |
| 34 | 98 | CheckVk(vkCreatePipelineLayout(device_, &layout, nullptr, &layout_), | |
| 35 | 294 | "Creating " + name_ + "'s layout"); | |
| 36 | 98 | const OnFailure freeLayout([&] { vkDestroyPipelineLayout(device_, layout_, nullptr); }); | |
| 37 | |||
| 38 | // maintenance5 would take the code inline; a module is portable to every Vulkan 1.3 driver. | ||
| 39 | 98 | const VkShaderModuleCreateInfo code{.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, | |
| 40 | 98 | .codeSize = spirv.size_bytes(), | |
| 41 | 98 | .pCode = spirv.data()}; | |
| 42 | 98 | VkShaderModule module = VK_NULL_HANDLE; | |
| 43 | 98 | CheckVk(vkCreateShaderModule(device_, &code, nullptr, &module), "Loading " + name_); | |
| 44 | 98 | const std::string entry(entryPoint); | |
| 45 | 98 | const VkComputePipelineCreateInfo pipeline{ | |
| 46 | .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, | ||
| 47 | .stage = {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, | ||
| 48 | .stage = VK_SHADER_STAGE_COMPUTE_BIT, | ||
| 49 | .module = module, | ||
| 50 | 98 | .pName = entry.c_str()}, | |
| 51 | 98 | .layout = layout_}; | |
| 52 | 98 | const VkResult result = vkCreateComputePipelines(device_, context.PipelineCache(), 1, &pipeline, | |
| 53 | nullptr, &pipeline_); | ||
| 54 | 98 | vkDestroyShaderModule(device_, module, nullptr); | |
| 55 | 98 | CheckVk(result, "Creating " + name_); | |
| 56 | 98 | context.Name(VK_OBJECT_TYPE_PIPELINE, reinterpret_cast<std::uint64_t>(pipeline_), name_); | |
| 57 | 102 | } | |
| 58 | |||
| 59 | 98 | ComputeKernel::~ComputeKernel() { | |
| 60 | 98 | vkDestroyPipeline(device_, pipeline_, nullptr); | |
| 61 | 98 | vkDestroyPipelineLayout(device_, layout_, nullptr); | |
| 62 | 98 | } | |
| 63 | |||
| 64 | 172 | void ComputeKernel::Dispatch(VkCommandBuffer commands, glm::uvec3 groups, | |
| 65 | std::span<const std::byte> parameters) const { | ||
| 66 | 172 | if (parameters.size() != parameterBytes_) { | |
| 67 | 2 | throw std::invalid_argument(name_ + " takes " + std::to_string(parameterBytes_) + | |
| 68 | 2 | " bytes per dispatch, not " + | |
| 69 | 3 | std::to_string(parameters.size())); | |
| 70 | } | ||
| 71 | 171 | vkCmdBindPipeline(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_); | |
| 72 | 171 | if (heap_ != VK_NULL_HANDLE) { | |
| 73 | 18 | vkCmdBindDescriptorSets(commands, VK_PIPELINE_BIND_POINT_COMPUTE, layout_, 0, 1, &heap_, 0, | |
| 74 | nullptr); | ||
| 75 | } | ||
| 76 | 171 | if (parameterBytes_ > 0) { | |
| 77 | 171 | vkCmdPushConstants(commands, layout_, VK_SHADER_STAGE_COMPUTE_BIT, 0, parameterBytes_, | |
| 78 | 171 | parameters.data()); | |
| 79 | } | ||
| 80 | 171 | vkCmdDispatch(commands, groups.x, groups.y, groups.z); | |
| 81 | 171 | } | |
| 82 | |||
| 83 | 73 | void ComputeBarrier(VkCommandBuffer commands) { | |
| 84 | 73 | Barrier(commands, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_WRITE_BIT, | |
| 85 | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, | ||
| 86 | VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT); | ||
| 87 | 73 | } | |
| 88 | |||
| 89 | } // namespace ernie | ||
| 90 |