blob: 0bab08c7fdbca47176c90b156510550eeb8ca1a9 [file]
/*
* 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