blob: ceba428c0a1c66a1b69733751d99dd79a99e4ffa [file]
//
// Copyright 2021 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// CLKernelVk.cpp: Implements the class methods for CLKernelVk.
//
#include "libANGLE/renderer/vulkan/CLKernelVk.h"
#include "libANGLE/renderer/vulkan/CLContextVk.h"
#include "libANGLE/renderer/vulkan/CLDeviceVk.h"
#include "libANGLE/renderer/vulkan/CLMemoryVk.h"
#include "libANGLE/renderer/vulkan/CLProgramVk.h"
#include "libANGLE/renderer/vulkan/cl_types.h"
#include "libANGLE/renderer/vulkan/vk_wrapper.h"
#include "libANGLE/CLBuffer.h"
#include "libANGLE/CLContext.h"
#include "libANGLE/CLDevice.h"
#include "libANGLE/CLKernel.h"
#include "libANGLE/CLProgram.h"
#include "spirv/unified1/NonSemanticClspvReflection.h"
#include <algorithm>
namespace rx
{
// Setup the CLKernelArgument with info from ClspvKernelArgument
CLKernelArgument::CLKernelArgument(const CLContextVk *context, const ClspvKernelArgument &info)
: mCompiledInfo(info), mSize(0), mHandle(nullptr)
{
mArgInfo.typeQualifier = mCompiledInfo.info.typeQualifier;
mArgInfo.addressQualifier = mCompiledInfo.info.addressQualifier;
mArgInfo.accessQualifier = mCompiledInfo.info.accessQualifier;
mArgInfo.typeName = mCompiledInfo.info.typeName;
mArgInfo.name = mCompiledInfo.info.name;
}
cl::Memory *CLKernelArgument::getMemoryHandle() const
{
if (!mCompiledInfo.used)
{
return nullptr;
}
return cl::Memory::Cast(static_cast<cl_mem>(mHandle));
}
cl::Sampler *CLKernelArgument::getSamplerHandle() const
{
ASSERT(mCompiledInfo.type == NonSemanticClspvReflectionArgumentSampler);
return cl::Sampler::Cast(*static_cast<const cl_sampler *>(mHandle));
}
bool CLKernelArgument::isReadOnly() const
{
// if not used, can safely assume readonly
if (!mCompiledInfo.used)
{
return true;
}
switch (getReflectionType())
{
case NonSemanticClspvReflectionArgumentPodUniform:
case NonSemanticClspvReflectionArgumentUniform:
case NonSemanticClspvReflectionArgumentPointerUniform:
case NonSemanticClspvReflectionArgumentUniformTexelBuffer:
case NonSemanticClspvReflectionConstantDataStorageBuffer:
{
return true;
}
case NonSemanticClspvReflectionArgumentStorageBuffer:
case NonSemanticClspvReflectionArgumentStorageTexelBuffer:
case NonSemanticClspvReflectionArgumentStorageImage:
case NonSemanticClspvReflectionArgumentSampledImage:
{
return mArgInfo.accessQualifier == CL_KERNEL_ARG_ACCESS_READ_ONLY;
}
default:
{
return false;
}
}
}
CLKernelVk::CLKernelVk(const cl::Kernel &kernel, std::string &name, std::string &attributes)
: CLKernelImpl(kernel),
mProgram(&kernel.getProgram().getImpl<CLProgramVk>()),
mContext(&kernel.getProgram().getContext().getImpl<CLContextVk>()),
mName(name),
mAttributes(attributes),
mArgs(),
mPodBuffer(nullptr),
mLocalMemoryArgSizes(
mProgram->getDeviceProgramData(mName.c_str())->getKernelArguments(mName.c_str()).size(),
0)
{
mShaderProgramHelper.setShader(gl::ShaderType::Compute,
mKernel.getProgram().getImpl<CLProgramVk>().getShaderModule());
}
CLKernelVk::~CLKernelVk()
{
mComputePipelineCache.destroy(mContext);
mShaderProgramHelper.destroy(mContext->getRenderer());
}
angle::Result CLKernelVk::init()
{
const CLProgramVk::DeviceProgramData *deviceProgramData =
mProgram->getDeviceProgramData(mName.c_str());
// Literal sampler handling
for (const ClspvLiteralSampler &literalSampler :
deviceProgramData->reflectionData.literalSamplers)
{
mDescriptorSetLayoutDescs[DescriptorSetIndex::LiteralSampler].addBinding(
literalSampler.binding, VK_DESCRIPTOR_TYPE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT,
nullptr);
}
// Setup the arguments
for (auto &clspvArg : deviceProgramData->getKernelArguments(mName.c_str()))
{
mArgs.push_back(std::make_shared<CLKernelArgument>(mContext, clspvArg));
}
vk::DescriptorSetLayoutDesc &descriptorSetLayoutDesc =
mDescriptorSetLayoutDescs[DescriptorSetIndex::KernelArguments];
VkPushConstantRange pcRange = deviceProgramData->pushConstRange;
size_t podBufferSize = 0;
bool podFound = false;
for (const auto arg : getArgs())
{
VkDescriptorType descType = VK_DESCRIPTOR_TYPE_MAX_ENUM;
switch (arg->getReflectionType())
{
case NonSemanticClspvReflectionArgumentStorageBuffer:
descType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
break;
case NonSemanticClspvReflectionArgumentUniform:
descType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
break;
case NonSemanticClspvReflectionArgumentPodUniform:
case NonSemanticClspvReflectionArgumentPodStorageBuffer:
case NonSemanticClspvReflectionArgumentPointerUniform:
{
uint32_t newPodBufferSize =
arg->getPodStorageBufferOffset() + arg->getPodStorageBufferSize();
podBufferSize = newPodBufferSize > podBufferSize ? newPodBufferSize : podBufferSize;
if (podFound)
{
continue;
}
descType =
arg->getReflectionType() == NonSemanticClspvReflectionArgumentPodStorageBuffer
? VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
podFound = true;
break;
}
case NonSemanticClspvReflectionArgumentPodPushConstant:
case NonSemanticClspvReflectionArgumentPointerPushConstant:
// Get existing push constant range and see if we need to update
if (arg->getPushConstantOffset() + arg->getPushConstantSize() >
pcRange.offset + pcRange.size)
{
pcRange.size =
arg->getPushConstantOffset() + arg->getPushConstantSize() - pcRange.offset;
}
continue;
case NonSemanticClspvReflectionArgumentSampledImage:
descType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
break;
case NonSemanticClspvReflectionArgumentStorageImage:
descType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
break;
case NonSemanticClspvReflectionArgumentSampler:
descType = VK_DESCRIPTOR_TYPE_SAMPLER;
break;
case NonSemanticClspvReflectionArgumentStorageTexelBuffer:
descType = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
break;
case NonSemanticClspvReflectionArgumentUniformTexelBuffer:
descType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
break;
default:
continue;
}
if (descType != VK_DESCRIPTOR_TYPE_MAX_ENUM)
{
descriptorSetLayoutDesc.addBinding(arg->getDescriptorBinding(), descType, 1,
VK_SHADER_STAGE_COMPUTE_BIT, nullptr);
}
}
if (podBufferSize > 0)
{
mPodBuffer = cl::BufferPtr::Create(
const_cast<cl::Context &>(mKernel.getProgram().getContext()), cl::Memory::PropArray{},
cl::MemFlags(CL_MEM_READ_ONLY), podBufferSize, nullptr);
}
if (usesPrintf() && !usesPrintfBufferPointerPushConstant())
{
mDescriptorSetLayoutDescs[DescriptorSetIndex::Printf].addBinding(
deviceProgramData->reflectionData.printfBufferStorage.binding,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr);
}
// Get pipeline layout from cache (creates if missed)
// A given kernel need not have resulted in use of all the descriptor sets. Unless the
// graphicsPipelineLibrary extension is supported, the pipeline layout need all the descriptor
// set layouts to be valide. So set them up in the order of their occurrence.
mPipelineLayoutDesc = {};
for (DescriptorSetIndex index : angle::AllEnums<DescriptorSetIndex>())
{
if (!mDescriptorSetLayoutDescs[index].empty())
{
mPipelineLayoutDesc.updateDescriptorSetLayout(index, mDescriptorSetLayoutDescs[index]);
}
}
// push constant range size and offset both need to be a multiple of 4
pcRange.size = roundUpPow2(pcRange.size, 4u);
pcRange.offset = roundDownPow2(pcRange.offset, 4u);
mPipelineLayoutDesc.updatePushConstantRange(pcRange.stageFlags, pcRange.offset, pcRange.size);
mPodArgumentPushConstants.resize(pcRange.size);
angle::EnumIterator<DescriptorSetIndex> layoutIndex(DescriptorSetIndex::LiteralSampler);
for (DescriptorSetIndex index : angle::AllEnums<DescriptorSetIndex>())
{
if (!getDescriptorSetLayoutDesc(index).empty())
{
ANGLE_CL_IMPL_TRY_ERROR(mContext->getDescriptorSetLayoutCache()->getDescriptorSetLayout(
mContext, getDescriptorSetLayoutDesc(index),
&getDescriptorSetLayouts()[*layoutIndex]),
CL_INVALID_OPERATION);
ASSERT(getDescriptorSetLayouts()[*layoutIndex]->valid());
++layoutIndex;
}
}
ANGLE_CL_IMPL_TRY_ERROR(initPipelineLayout(), CL_INVALID_OPERATION);
// initialize the descriptor pools
// descriptor pools are setup as per their indices
return mContext->initializeDescriptorPools(this);
}
angle::Result CLKernelVk::setArg(cl_uint argIndex, size_t argSize, const void *argValue)
{
auto &arg = mArgs.at(argIndex);
if (arg->getUsed())
{
switch (arg->getReflectionType())
{
case NonSemanticClspvReflectionArgumentPodPushConstant:
{
ASSERT(mPodArgumentPushConstants.size() >=
arg->getPushConstantSize() + arg->getPushConstantOffset());
void *handle = &mPodArgumentPushConstants[arg->getPushConstantOffset()];
size_t handleSize =
(argSize > arg->getPushConstantSize() ? arg->getPushConstantSize() : argSize);
if (argSize > 0 && argValue != nullptr)
{
// Copy the contents since app is free to delete/reassign the contents after
ANGLE_UNSAFE_TODO(memcpy(handle, argValue, handleSize));
}
arg->set(handleSize, handle);
break;
}
case NonSemanticClspvReflectionArgumentPodUniform:
case NonSemanticClspvReflectionArgumentPodStorageBuffer:
{
ASSERT(mPodBuffer->getSize() >= argSize + arg->getPodUniformOffset());
if (argSize > 0 && argValue != nullptr)
{
ANGLE_TRY(mPodBuffer->getImpl<CLBufferVk>().copyFrom(
argValue, arg->getPodStorageBufferOffset(), argSize));
}
break;
}
case NonSemanticClspvReflectionArgumentUniform:
case NonSemanticClspvReflectionArgumentStorageBuffer:
{
ASSERT(argSize == sizeof(cl_mem *));
size_t handleSize = argSize;
void *handle = nullptr;
// Argument can be null ptr
if (argValue && *static_cast<const cl_mem *>(argValue))
{
handle = *static_cast<const cl_mem *>(argValue);
}
arg->set(handleSize, handle);
break;
}
case NonSemanticClspvReflectionArgumentStorageImage:
case NonSemanticClspvReflectionArgumentSampledImage:
case NonSemanticClspvReflectionArgumentUniformTexelBuffer:
case NonSemanticClspvReflectionArgumentStorageTexelBuffer:
case NonSemanticClspvReflectionArgumentPointerPushConstant:
case NonSemanticClspvReflectionArgumentPointerUniform:
{
ASSERT(argSize == sizeof(cl_mem *));
cl_mem memHandle = *static_cast<const cl_mem *>(argValue);
if (cl::Memory::Cast(memHandle)->getFlags().intersects(CL_MEM_READ_ONLY) &&
!arg->isReadOnly())
{
WARN() << "The cl_mem argument at index:" << argIndex
<< " is setup as read-only, and the kernel is writing to it -- This "
"could lead to undefined behavior";
}
arg->set(argSize, memHandle);
break;
}
case NonSemanticClspvReflectionArgumentWorkgroup:
ASSERT(arg->getWorkGroupElementSize() != 0);
mLocalMemoryArgSizes[argIndex] = argSize;
arg->set(argSize, const_cast<void *>(argValue));
break;
default:
// Just store ptr and size (if we end up here)
arg->set(argSize, const_cast<void *>(argValue));
break;
}
}
return angle::Result::Continue;
}
angle::Result CLKernelVk::createInfo(CLKernelImpl::Info *info) const
{
info->functionName = mName;
info->attributes = mAttributes;
info->numArgs = static_cast<cl_uint>(mArgs.size());
for (const auto arg : mArgs)
{
info->args.push_back(arg->getArgInfo());
}
auto &ctx = mKernel.getProgram().getContext();
info->workGroups.resize(ctx.getDevices().size());
const CLProgramVk::DeviceProgramData *deviceProgramData = nullptr;
for (auto i = 0u; i < ctx.getDevices().size(); ++i)
{
auto &workGroup = info->workGroups[i];
const auto device = &ctx.getDevices()[i];
const auto deviceVk = &device->get()->getImpl<CLDeviceVk>();
deviceProgramData = mProgram->getDeviceProgramData(ctx.getDevices()[i]->getNative());
if (deviceProgramData == nullptr)
{
continue;
}
// TODO: http://anglebug.com/42267005
ANGLE_TRY(
deviceVk->getInfoSizeT(cl::DeviceInfo::MaxWorkGroupSize, &workGroup.workGroupSize));
// TODO: http://anglebug.com/42267004
workGroup.privateMemSize = 0;
workGroup.localMemSize = deviceProgramData->reflectionData.workgroupVariableSize.size;
workGroup.prefWorkGroupSizeMultiple = deviceVk->getWorkGroupSizeMultiple();
workGroup.globalWorkSize = {0, 0, 0};
if (deviceProgramData->reflectionData.kernelCompileWorkgroupSize.contains(mName))
{
workGroup.compileWorkGroupSize = {
deviceProgramData->reflectionData.kernelCompileWorkgroupSize.at(mName)[0],
deviceProgramData->reflectionData.kernelCompileWorkgroupSize.at(mName)[1],
deviceProgramData->reflectionData.kernelCompileWorkgroupSize.at(mName)[2]};
}
else
{
workGroup.compileWorkGroupSize = {0, 0, 0};
}
if (device->get()->getInfo().khrSubgroups)
{
workGroup.subGroupSizeForNDRange =
mContext->getRenderer()->getPhysicalDeviceSubgroupProperties().subgroupSize;
}
else
{
workGroup.subGroupSizeForNDRange = 0;
}
}
return angle::Result::Continue;
}
angle::Result CLKernelVk::initPipelineLayout()
{
PipelineLayoutCache *pipelineLayoutCache = mContext->getPipelineLayoutCache();
return pipelineLayoutCache->getPipelineLayout(mContext, mPipelineLayoutDesc,
mDescriptorSetLayouts, &mPipelineLayout);
}
angle::Result CLKernelVk::getOrCreateComputePipeline(vk::PipelineCacheAccess *pipelineCache,
const cl::NDRange &ndrange,
const cl::Device &device,
vk::PipelineHelper **pipelineOut)
{
const CLProgramVk::DeviceProgramData *devProgramData =
getProgram()->getDeviceProgramData(device.getNative());
ASSERT(devProgramData != nullptr);
// Populate program specialization constants (if any)
uint32_t constantDataOffset = 0;
std::vector<uint32_t> specConstantData;
std::vector<VkSpecializationMapEntry> mapEntries;
for (const auto specConstantUsed : devProgramData->reflectionData.specConstantsUsed)
{
switch (specConstantUsed)
{
case SpecConstantType::WorkDimension:
specConstantData.push_back(ndrange.workDimensions);
break;
case SpecConstantType::WorkgroupSizeX:
specConstantData.push_back(ndrange.localWorkSize[0]);
break;
case SpecConstantType::WorkgroupSizeY:
specConstantData.push_back(ndrange.localWorkSize[1]);
break;
case SpecConstantType::WorkgroupSizeZ:
specConstantData.push_back(ndrange.localWorkSize[2]);
break;
case SpecConstantType::GlobalOffsetX:
specConstantData.push_back(ndrange.globalWorkOffset[0]);
break;
case SpecConstantType::GlobalOffsetY:
specConstantData.push_back(ndrange.globalWorkOffset[1]);
break;
case SpecConstantType::GlobalOffsetZ:
specConstantData.push_back(ndrange.globalWorkOffset[2]);
break;
case SpecConstantType::SubgroupMaxSize:
{
// We should only be here if cl_khr_subgroups is supported
ASSERT(device.getInfo().khrSubgroups);
specConstantData.push_back(
mContext->getRenderer()->getPhysicalDeviceSubgroupProperties().subgroupSize);
break;
}
default:
UNIMPLEMENTED();
continue;
}
mapEntries.push_back(VkSpecializationMapEntry{
.constantID = devProgramData->reflectionData.specConstantIDs[specConstantUsed],
.offset = constantDataOffset,
.size = sizeof(uint32_t)});
constantDataOffset += sizeof(uint32_t);
}
// Populate kernel specialization constants (if any)
for (const auto arg : mArgs)
{
if (arg->getUsed() &&
arg->getReflectionType() == NonSemanticClspvReflectionArgumentWorkgroup)
{
ASSERT(arg->getWorkGroupElementSize() != 0);
specConstantData.push_back(
static_cast<uint32_t>(arg->getSize() / arg->getWorkGroupElementSize()));
mapEntries.push_back(
VkSpecializationMapEntry{.constantID = arg->getWorkGroupBufferSpecId(),
.offset = constantDataOffset,
.size = sizeof(uint32_t)});
constantDataOffset += sizeof(uint32_t);
}
}
VkSpecializationInfo computeSpecializationInfo{
.mapEntryCount = static_cast<uint32_t>(mapEntries.size()),
.pMapEntries = mapEntries.data(),
.dataSize = specConstantData.size() * sizeof(uint32_t),
.pData = specConstantData.data(),
};
// Now get or create (on compute pipeline cache miss) compute pipeline and return it
vk::ComputePipelineOptions options = vk::GetComputePipelineOptions(
vk::PipelineRobustness::NonRobust, vk::PipelineProtectedAccess::Unprotected);
return mShaderProgramHelper.getOrCreateComputePipeline(
mContext, &mComputePipelineCache, pipelineCache, getPipelineLayout(), options,
PipelineSource::Draw, pipelineOut, mName.c_str(), &computeSpecializationInfo);
}
bool CLKernelVk::usesPrintf() const
{
return mProgram->getDeviceProgramData(mName.c_str())->getKernelFlags(mName) &
NonSemanticClspvReflectionMayUsePrintf;
}
bool CLKernelVk::usesPrintfBufferPointerPushConstant() const
{
return mProgram->getDeviceProgramData(mName.c_str())
->reflectionData.pushConstants.contains(
NonSemanticClspvReflectionPrintfBufferPointerPushConstant);
}
angle::Result CLKernelVk::initializeDescriptorPools()
{
for (DescriptorSetIndex index : angle::AllEnums<DescriptorSetIndex>())
{
if (!mDescriptorSetLayoutDescs[index].empty())
{
ANGLE_TRY(mContext->getMetaDescriptorPool().bindCachedDescriptorPool(
mContext, mDescriptorSetLayoutDescs[index], 1,
mContext->getDescriptorSetLayoutCache(), &mDynamicDescriptorPools[index]));
}
}
return angle::Result::Continue;
}
angle::Result CLKernelVk::allocateDescriptorSet(
DescriptorSetIndex index,
angle::EnumIterator<DescriptorSetIndex> layoutIndex,
vk::OutsideRenderPassCommandBufferHelper *computePassCommands)
{
if (mDescriptorSets[index] && mDescriptorSets[index]->valid())
{
// Safe to reuse: descriptor set is no longer in use by the GPU.
if (mContext->getRenderer()->hasResourceUseFinished(
mDescriptorSets[index]->getResourceUse()))
{
// Set DS serial to current CB serial upon reuse.
mDescriptorSets[index]->setQueueSerial(computePassCommands->getQueueSerial());
return angle::Result::Continue;
}
else
{
mDescriptorSets[index].reset();
}
}
if (mDynamicDescriptorPools[index]->valid())
{
ANGLE_TRY(mDynamicDescriptorPools[index]->allocateDescriptorSet(
mContext, *mDescriptorSetLayouts[*layoutIndex], &mDescriptorSets[index]));
computePassCommands->retainResource(mDescriptorSets[index].get());
}
return angle::Result::Continue;
}
cl_ulong CLKernelVk::getLocalMemSizeUsed(const cl::Device &device) const
{
return getAllArgLocalMemSize() + getCompiledLocalMemSize(device);
}
cl_ulong CLKernelVk::getAllArgLocalMemSize() const
{
return std::reduce(mLocalMemoryArgSizes.begin(), mLocalMemoryArgSizes.end(), 0,
std::plus<size_t>());
}
cl_ulong CLKernelVk::getCompiledLocalMemSize(const cl::Device &device) const
{
return mProgram->getDeviceProgramData(const_cast<cl_device_id>(device.getNative()))
->reflectionData.workgroupVariableSize.size;
}
} // namespace rx