| /* |
| * Copyright 2026 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/gpu/graphite/vk/VulkanComputePipeline.h" |
| |
| #include "include/private/SkAssert.h" |
| #include "src/gpu/graphite/ComputePipelineDesc.h" |
| #include "src/gpu/graphite/ContextUtils.h" |
| #include "src/gpu/graphite/compute/ComputeStep.h" |
| #include "src/gpu/graphite/vk/VulkanGraphiteUtils.h" |
| #include "src/gpu/graphite/vk/VulkanSharedContext.h" |
| #include "src/gpu/vk/VulkanUtilsPriv.h" |
| #include "src/sksl/SkSLProgramKind.h" |
| #include "src/sksl/SkSLProgramSettings.h" |
| #include "src/sksl/ir/SkSLProgram.h" |
| |
| namespace skgpu::graphite { |
| |
| sk_sp<VulkanComputePipeline> VulkanComputePipeline::Make(VulkanSharedContext* sharedContext, |
| const ComputePipelineDesc& desc) { |
| const ComputeStep* step = desc.computeStep(); |
| // Should never have a compute step without resources. |
| SkASSERT(!step->resources().empty()); |
| |
| std::string sksl = BuildComputeSkSL(sharedContext->caps(), step, BackendApi::kVulkan); |
| SkSL::ProgramSettings settings; |
| SkSL::NativeShader spirv; |
| SkSL::ProgramInterface outInterface; |
| if (!skgpu::SkSLToSPIRV(sharedContext->caps()->shaderCaps(), |
| sksl, |
| SkSL::ProgramKind::kCompute, |
| settings, |
| &spirv, |
| &outInterface, |
| sharedContext->caps()->shaderErrorHandler())) { |
| return nullptr; |
| } |
| |
| VkShaderModule shaderModule = |
| CreateVulkanShaderModule(sharedContext, spirv, VK_SHADER_STAGE_COMPUTE_BIT); |
| if (shaderModule == VK_NULL_HANDLE) { |
| return nullptr; |
| } |
| |
| skia_private::TArray<DescriptorData> descriptorData; |
| descriptorData.reserve_exact(step->resources().size()); |
| skia_private::TArray<VkAccessFlags> bufferAccessFlags; |
| bufferAccessFlags.reserve_exact(step->resources().size()); |
| |
| int bindingIndex = 0; |
| for (const ComputeStep::ResourceDesc& r : step->resources()) { |
| DescriptorType descType; |
| VkAccessFlags accessFlags = 0; |
| switch (r.fType) { |
| case ComputeStep::ResourceType::kUniformBuffer: |
| descType = DescriptorType::kUniformBuffer; |
| accessFlags = VK_ACCESS_UNIFORM_READ_BIT; |
| break; |
| case ComputeStep::ResourceType::kStorageBuffer: |
| case ComputeStep::ResourceType::kIndirectBuffer: |
| descType = DescriptorType::kStorageBuffer; |
| accessFlags = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; |
| break; |
| case ComputeStep::ResourceType::kReadOnlyStorageBuffer: |
| descType = DescriptorType::kStorageBuffer; |
| accessFlags = VK_ACCESS_SHADER_READ_BIT; |
| break; |
| case ComputeStep::ResourceType::kWriteOnlyStorageTexture: |
| descType = DescriptorType::kStorageTexture; |
| break; |
| case ComputeStep::ResourceType::kReadOnlyTexture: |
| descType = DescriptorType::kTexture; |
| break; |
| case ComputeStep::ResourceType::kSampledTexture: |
| descType = DescriptorType::kCombinedTextureSampler; |
| break; |
| default: |
| SK_ABORT("Unsupported compute resource type"); |
| } |
| // TODO (thomsmit): If ComputeStep::ResourceDesc ever expands to support arrayed resources, |
| // this count will need to dynamically map to the resource's requested size. |
| descriptorData.push_back({descType, |
| /*count=*/1, |
| bindingIndex++, |
| PipelineStageFlags::kCompute}); |
| bufferAccessFlags.push_back(accessFlags); |
| } |
| |
| VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; |
| DescriptorDataToVkDescSetLayout(sharedContext, descriptorData, &descriptorSetLayout); |
| if (descriptorSetLayout == VK_NULL_HANDLE) { |
| VULKAN_CALL(sharedContext->interface(), |
| DestroyShaderModule(sharedContext->device(), shaderModule, nullptr)); |
| return nullptr; |
| } |
| |
| // TODO (thomsmit): If compute SkSL generation ever relies on push constants for intrinsics or |
| // uniforms (such as when VulkanCaps::fUsePushConstantsForIntrinsicConstants is true), |
| // pipelineLayoutInfo will need to declare the appropriate VkPushConstantRange. Currently, |
| // compute pipelines only rely on UBOs and SSBOs for uniforms and data. |
| VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; |
| pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; |
| pipelineLayoutInfo.setLayoutCount = 1; |
| pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; |
| |
| VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; |
| VkResult result; |
| VULKAN_CALL_RESULT( |
| sharedContext, |
| result, |
| CreatePipelineLayout( |
| sharedContext->device(), &pipelineLayoutInfo, nullptr, &pipelineLayout)); |
| |
| // DescriptorSetLayouts can be deleted after the pipeline layout is created. |
| VULKAN_CALL( |
| sharedContext->interface(), |
| DestroyDescriptorSetLayout(sharedContext->device(), descriptorSetLayout, nullptr)); |
| |
| if (result != VK_SUCCESS) { |
| VULKAN_CALL(sharedContext->interface(), |
| DestroyShaderModule(sharedContext->device(), shaderModule, nullptr)); |
| return nullptr; |
| } |
| |
| VkComputePipelineCreateInfo pipelineInfo = {}; |
| pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; |
| pipelineInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; |
| pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; |
| pipelineInfo.stage.module = shaderModule; |
| pipelineInfo.stage.pName = "main"; |
| pipelineInfo.layout = pipelineLayout; |
| |
| VkPipeline pipeline = VK_NULL_HANDLE; |
| VULKAN_CALL_RESULT(sharedContext, |
| result, |
| CreateComputePipelines(sharedContext->device(), |
| sharedContext->getPipelineCache(), |
| 1, |
| &pipelineInfo, |
| nullptr, |
| &pipeline)); |
| |
| VULKAN_CALL(sharedContext->interface(), |
| DestroyShaderModule(sharedContext->device(), shaderModule, nullptr)); |
| |
| if (result != VK_SUCCESS) { |
| VULKAN_CALL(sharedContext->interface(), |
| DestroyPipelineLayout(sharedContext->device(), pipelineLayout, nullptr)); |
| return nullptr; |
| } |
| |
| sharedContext->pipelineCompileWasRequired(); |
| |
| // Both pipeline and pipelineLayout are verified valid handles; ownership is transferred |
| // to VulkanComputePipeline, which cleans them up in freeGpuData(). |
| return sk_sp<VulkanComputePipeline>(new VulkanComputePipeline(sharedContext, |
| pipeline, |
| pipelineLayout, |
| std::move(descriptorData), |
| std::move(bufferAccessFlags))); |
| } |
| |
| VulkanComputePipeline::VulkanComputePipeline( |
| const SharedContext* sharedContext, |
| VkPipeline pipeline, |
| VkPipelineLayout pipelineLayout, |
| skia_private::TArray<DescriptorData> descriptorData, |
| skia_private::TArray<VkAccessFlags> bufferAccessFlags) |
| : ComputePipeline(sharedContext) |
| , fPipeline(pipeline) |
| , fPipelineLayout(pipelineLayout) |
| , fDescriptorData(std::move(descriptorData)) |
| , fBufferAccessFlags(std::move(bufferAccessFlags)) {} |
| |
| void VulkanComputePipeline::freeGpuData() { |
| auto sharedCtxt = static_cast<const VulkanSharedContext*>(this->sharedContext()); |
| if (fPipeline != VK_NULL_HANDLE) { |
| VULKAN_CALL(sharedCtxt->interface(), |
| DestroyPipeline(sharedCtxt->device(), fPipeline, nullptr)); |
| fPipeline = VK_NULL_HANDLE; |
| } |
| if (fPipelineLayout != VK_NULL_HANDLE) { |
| VULKAN_CALL(sharedCtxt->interface(), |
| DestroyPipelineLayout(sharedCtxt->device(), fPipelineLayout, nullptr)); |
| fPipelineLayout = VK_NULL_HANDLE; |
| } |
| } |
| |
| VkDescriptorType VulkanComputePipeline::bindingType(size_t index) const { |
| SkASSERT(index < SkToSizeT(fDescriptorData.size())); |
| return DsTypeEnumToVkDs(fDescriptorData[index].fType); |
| } |
| |
| VkAccessFlags VulkanComputePipeline::bufferAccessFlags(size_t index) const { |
| SkASSERT(index < SkToSizeT(fBufferAccessFlags.size())); |
| return fBufferAccessFlags[index]; |
| } |
| |
| } // namespace skgpu::graphite |