| // |
| // 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. |
| // |
| // CLProgramVk.cpp: Implements the class methods for CLProgramVk. |
| // |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| # pragma allow_unsafe_buffers |
| #endif |
| |
| #include "libANGLE/renderer/vulkan/CLProgramVk.h" |
| #include "libANGLE/renderer/vulkan/CLContextVk.h" |
| #include "libANGLE/renderer/vulkan/cl_types.h" |
| #include "libANGLE/renderer/vulkan/clspv_utils.h" |
| |
| #include "libANGLE/CLBuffer.h" |
| #include "libANGLE/CLContext.h" |
| #include "libANGLE/CLKernel.h" |
| #include "libANGLE/CLProgram.h" |
| #include "libANGLE/cl_utils.h" |
| |
| #include "clspv/Compiler.h" |
| |
| namespace rx |
| { |
| |
| namespace |
| { |
| #if defined(ANGLE_ENABLE_ASSERTS) |
| constexpr bool kAngleDebug = true; |
| #else |
| constexpr bool kAngleDebug = false; |
| #endif |
| |
| std::string ProcessBuildOptions(const std::vector<std::string> &optionTokens, |
| CLProgramVk::BuildType buildType) |
| { |
| std::string processedOptions; |
| |
| // Need to remove/replace options that are not 1-1 mapped to clspv |
| for (const std::string &optionToken : optionTokens) |
| { |
| if (optionToken == "-create-library" && buildType == CLProgramVk::BuildType::LINK) |
| { |
| processedOptions += " --output-format=bc"; |
| continue; |
| } |
| else if (optionToken == "-cl-std=CL2.0") |
| { |
| processedOptions += " -cl-std=CL2.0 -inline-entry-points"; |
| continue; |
| } |
| processedOptions += " " + optionToken; |
| } |
| |
| switch (buildType) |
| { |
| case CLProgramVk::BuildType::COMPILE: |
| processedOptions += " --output-format=bc"; |
| break; |
| case CLProgramVk::BuildType::LINK: |
| processedOptions += " -x ir"; |
| break; |
| default: |
| break; |
| } |
| |
| return processedOptions; |
| } |
| |
| } // namespace |
| |
| void CLAsyncBuildTask::operator()() |
| { |
| ANGLE_TRACE_EVENT0("gpu.angle", "CLProgramVk::buildInternal (async)"); |
| CLProgramVk::ScopedProgramCallback spc(mNotify); |
| if (!mProgramVk->buildInternal(mDevices, mOptions, mInternalOptions, mBuildType, |
| mLinkProgramsList)) |
| { |
| ERR() << "Async build failed for program (" << mProgramVk |
| << ")! Check the build status or build log for details."; |
| } |
| } |
| |
| CLProgramVk::CLProgramVk(const cl::Program &program) |
| : CLProgramImpl(program), |
| mContext(&program.getContext().getImpl<CLContextVk>()), |
| mAsyncBuildEvent(std::make_shared<angle::WaitableEventDone>()), |
| mModuleConstantDataBuffer(nullptr) |
| {} |
| |
| angle::Result CLProgramVk::init() |
| { |
| cl::DevicePtrs devices; |
| ANGLE_TRY(mContext->getDevices(&devices)); |
| |
| // The devices associated with the program object are the devices associated with context |
| for (const cl::DevicePtr &device : devices) |
| { |
| DeviceProgramData deviceProgramData{}; |
| mAssociatedDevicePrograms[device->getNative()] = std::move(deviceProgramData); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result CLProgramVk::init(const size_t *lengths, |
| const unsigned char **binaries, |
| cl_int *binaryStatus) |
| { |
| // The devices associated with program come from device_list param from |
| // clCreateProgramWithBinary |
| for (const cl::DevicePtr &device : mProgram.getDevices()) |
| { |
| const unsigned char *binaryHandle = *binaries++; |
| size_t binarySize = *lengths++; |
| |
| // Check for header |
| if (binarySize < sizeof(ProgramBinaryOutputHeader)) |
| { |
| if (binaryStatus) |
| { |
| *binaryStatus++ = CL_INVALID_BINARY; |
| } |
| ANGLE_CL_RETURN_ERROR(CL_INVALID_BINARY); |
| } |
| binarySize -= sizeof(ProgramBinaryOutputHeader); |
| |
| // Check for valid binary version from header |
| const ProgramBinaryOutputHeader *binaryHeader = |
| reinterpret_cast<const ProgramBinaryOutputHeader *>(binaryHandle); |
| if (binaryHeader == nullptr) |
| { |
| ERR() << "NULL binary header!"; |
| if (binaryStatus) |
| { |
| *binaryStatus++ = CL_INVALID_BINARY; |
| } |
| ANGLE_CL_RETURN_ERROR(CL_INVALID_BINARY); |
| } |
| else if (binaryHeader->headerVersion < kBinaryVersion) |
| { |
| ERR() << "Binary version not compatible with runtime!"; |
| if (binaryStatus) |
| { |
| *binaryStatus++ = CL_INVALID_BINARY; |
| } |
| ANGLE_CL_RETURN_ERROR(CL_INVALID_BINARY); |
| } |
| binaryHandle += sizeof(ProgramBinaryOutputHeader); |
| |
| // See what kind of binary we have (i.e. SPIR-V or LLVM Bitcode) |
| // https://llvm.org/docs/BitCodeFormat.html#llvm-ir-magic-number |
| // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_magic_number |
| constexpr uint32_t LLVM_BC_MAGIC = 0xDEC04342; |
| constexpr uint32_t SPIRV_MAGIC = 0x07230203; |
| const uint32_t &firstWord = reinterpret_cast<const uint32_t *>(binaryHandle)[0]; |
| bool isBC = firstWord == LLVM_BC_MAGIC; |
| bool isSPV = firstWord == SPIRV_MAGIC; |
| if (!isBC && !isSPV) |
| { |
| ERR() << "Binary is neither SPIR-V nor LLVM Bitcode!"; |
| if (binaryStatus) |
| { |
| *binaryStatus++ = CL_INVALID_BINARY; |
| } |
| ANGLE_CL_RETURN_ERROR(CL_INVALID_BINARY); |
| } |
| |
| // Add device binary to program |
| DeviceProgramData deviceBinary; |
| deviceBinary.spirvVersion = device->getImpl<CLDeviceVk>().getSpirvVersion(); |
| deviceBinary.binaryType = binaryHeader->binaryType; |
| deviceBinary.buildStatus = binaryHeader->buildStatus; |
| switch (deviceBinary.binaryType) |
| { |
| case CL_PROGRAM_BINARY_TYPE_EXECUTABLE: |
| deviceBinary.binary.assign(binarySize / sizeof(uint32_t), 0); |
| std::memcpy(deviceBinary.binary.data(), binaryHandle, binarySize); |
| break; |
| case CL_PROGRAM_BINARY_TYPE_LIBRARY: |
| case CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT: |
| deviceBinary.IR.assign(binarySize, 0); |
| std::memcpy(deviceBinary.IR.data(), binaryHandle, binarySize); |
| break; |
| default: |
| UNREACHABLE(); |
| ERR() << "Invalid binary type!"; |
| if (binaryStatus) |
| { |
| *binaryStatus++ = CL_INVALID_BINARY; |
| } |
| ANGLE_CL_RETURN_ERROR(CL_INVALID_BINARY); |
| } |
| mAssociatedDevicePrograms[device->getNative()] = std::move(deviceBinary); |
| if (binaryStatus) |
| { |
| *binaryStatus++ = CL_SUCCESS; |
| } |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| CLProgramVk::~CLProgramVk() {} |
| |
| angle::Result CLProgramVk::build(const cl::DevicePtrs &devices, |
| const char *options, |
| cl::Program *notify) |
| { |
| BuildType buildType = !mProgram.getSource().empty() ? BuildType::BUILD : BuildType::BINARY; |
| const cl::DevicePtrs &devicePtrs = !devices.empty() ? devices : mProgram.getDevices(); |
| |
| setBuildStatus(devicePtrs, CL_BUILD_IN_PROGRESS); |
| |
| if (notify) |
| { |
| mAsyncBuildEvent = |
| getPlatform()->postMultiThreadWorkerTask(std::make_shared<CLAsyncBuildTask>( |
| this, devicePtrs, std::string(options ? options : ""), "", buildType, |
| LinkProgramsList{}, notify)); |
| ASSERT(mAsyncBuildEvent != nullptr); |
| } |
| else |
| { |
| if (!buildInternal(devicePtrs, std::string(options ? options : ""), "", buildType, |
| LinkProgramsList{})) |
| { |
| ANGLE_CL_RETURN_ERROR(CL_BUILD_PROGRAM_FAILURE); |
| } |
| } |
| return angle::Result::Continue; |
| } |
| |
| angle::Result CLProgramVk::compile(const cl::DevicePtrs &devices, |
| const char *options, |
| const cl::ProgramPtrs &inputHeaders, |
| const char **headerIncludeNames, |
| cl::Program *notify) |
| { |
| const cl::DevicePtrs &devicePtrs = !devices.empty() ? devices : mProgram.getDevices(); |
| |
| // Ensure OS temp dir is available |
| std::string internalCompileOpts; |
| Optional<std::string> tmpDir = angle::GetTempDirectory(); |
| if (!tmpDir.valid()) |
| { |
| ERR() << "Failed to open OS temp dir"; |
| ANGLE_CL_RETURN_ERROR(CL_INVALID_OPERATION); |
| } |
| internalCompileOpts += inputHeaders.empty() ? "" : " -I" + tmpDir.value(); |
| |
| // Dump input headers to OS temp directory |
| for (size_t i = 0; i < inputHeaders.size(); ++i) |
| { |
| const std::string &inputHeaderSrc = |
| inputHeaders.at(i)->getImpl<CLProgramVk>().mProgram.getSource(); |
| std::string headerFilePath(angle::ConcatenatePath(tmpDir.value(), headerIncludeNames[i])); |
| |
| // Sanitize path so we can use "/" as universal path separator |
| angle::MakeForwardSlashThePathSeparator(headerFilePath); |
| size_t baseDirPos = headerFilePath.find_last_of("/"); |
| |
| // Ensure parent dir(s) exists |
| if (!angle::CreateDirectories(headerFilePath.substr(0, baseDirPos))) |
| { |
| ERR() << "Failed to create output path(s) for header(s)!"; |
| ANGLE_CL_RETURN_ERROR(CL_INVALID_OPERATION); |
| } |
| writeFile(headerFilePath.c_str(), inputHeaderSrc); |
| } |
| |
| setBuildStatus(devicePtrs, CL_BUILD_IN_PROGRESS); |
| |
| // Perform compile |
| if (notify) |
| { |
| mAsyncBuildEvent = mProgram.getContext().getPlatform().getMultiThreadPool()->postWorkerTask( |
| std::make_shared<CLAsyncBuildTask>( |
| this, devicePtrs, std::string(options ? options : ""), internalCompileOpts, |
| BuildType::COMPILE, LinkProgramsList{}, notify)); |
| ASSERT(mAsyncBuildEvent != nullptr); |
| } |
| else |
| { |
| mAsyncBuildEvent = std::make_shared<angle::WaitableEventDone>(); |
| if (!buildInternal(devicePtrs, std::string(options ? options : ""), internalCompileOpts, |
| BuildType::COMPILE, LinkProgramsList{})) |
| { |
| ANGLE_CL_RETURN_ERROR(CL_COMPILE_PROGRAM_FAILURE); |
| } |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result CLProgramVk::getInfo(cl::ProgramInfo name, |
| size_t valueSize, |
| void *value, |
| size_t *valueSizeRet) const |
| { |
| cl_uint valUInt = 0u; |
| cl_bool valBool = CL_FALSE; |
| void *valPointer = nullptr; |
| const void *copyValue = nullptr; |
| size_t copySize = 0u; |
| unsigned char **outputBins = reinterpret_cast<unsigned char **>(value); |
| std::string kernelNamesList; |
| std::vector<size_t> vBinarySizes; |
| |
| switch (name) |
| { |
| case cl::ProgramInfo::NumKernels: |
| for (const auto &deviceProgram : mAssociatedDevicePrograms) |
| { |
| valUInt += static_cast<decltype(valUInt)>(deviceProgram.second.numKernels()); |
| } |
| copyValue = &valUInt; |
| copySize = sizeof(valUInt); |
| break; |
| case cl::ProgramInfo::BinarySizes: |
| { |
| for (const auto &deviceProgram : mAssociatedDevicePrograms) |
| { |
| vBinarySizes.push_back( |
| sizeof(ProgramBinaryOutputHeader) + |
| (deviceProgram.second.binaryType == CL_PROGRAM_BINARY_TYPE_EXECUTABLE |
| ? deviceProgram.second.binary.size() * sizeof(uint32_t) |
| : deviceProgram.second.IR.size())); |
| } |
| valPointer = vBinarySizes.data(); |
| copyValue = valPointer; |
| copySize = vBinarySizes.size() * sizeof(size_t); |
| break; |
| } |
| case cl::ProgramInfo::Binaries: |
| for (const auto &deviceProgram : mAssociatedDevicePrograms) |
| { |
| const void *bin = |
| deviceProgram.second.binaryType == CL_PROGRAM_BINARY_TYPE_EXECUTABLE |
| ? reinterpret_cast<const void *>(deviceProgram.second.binary.data()) |
| : reinterpret_cast<const void *>(deviceProgram.second.IR.data()); |
| size_t binSize = |
| deviceProgram.second.binaryType == CL_PROGRAM_BINARY_TYPE_EXECUTABLE |
| ? deviceProgram.second.binary.size() * sizeof(uint32_t) |
| : deviceProgram.second.IR.size(); |
| ProgramBinaryOutputHeader header{.headerVersion = kBinaryVersion, |
| .binaryType = deviceProgram.second.binaryType, |
| .buildStatus = deviceProgram.second.buildStatus}; |
| |
| if (outputBins != nullptr) |
| { |
| if (*outputBins != nullptr) |
| { |
| std::memcpy(*outputBins, &header, sizeof(ProgramBinaryOutputHeader)); |
| std::memcpy((*outputBins) + sizeof(ProgramBinaryOutputHeader), bin, |
| binSize); |
| } |
| outputBins++; |
| } |
| |
| // Spec just wants pointer size here |
| copySize += sizeof(unsigned char *); |
| } |
| // We already copied the (headers + binaries) over - nothing else left to copy |
| copyValue = nullptr; |
| break; |
| case cl::ProgramInfo::KernelNames: |
| for (const auto &deviceProgram : mAssociatedDevicePrograms) |
| { |
| kernelNamesList = deviceProgram.second.getKernelNames(); |
| } |
| valPointer = kernelNamesList.data(); |
| copyValue = valPointer; |
| copySize = kernelNamesList.size() + 1; |
| break; |
| case cl::ProgramInfo::ScopeGlobalCtorsPresent: |
| case cl::ProgramInfo::ScopeGlobalDtorsPresent: |
| // These are deprecated by version 3.0 and are currently not supported |
| copyValue = &valBool; |
| copySize = sizeof(cl_bool); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| |
| if ((value != nullptr) && (copyValue != nullptr)) |
| { |
| std::memcpy(value, copyValue, copySize); |
| } |
| |
| if (valueSizeRet != nullptr) |
| { |
| *valueSizeRet = copySize; |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result CLProgramVk::getBuildInfo(const cl::Device &device, |
| cl::ProgramBuildInfo name, |
| size_t valueSize, |
| void *value, |
| size_t *valueSizeRet) const |
| { |
| cl_uint valUInt = 0; |
| cl_build_status valStatus = 0; |
| const void *copyValue = nullptr; |
| size_t copySize = 0; |
| const DeviceProgramData *deviceProgramData = getDeviceProgramData(device.getNative()); |
| |
| switch (name) |
| { |
| case cl::ProgramBuildInfo::Status: |
| valStatus = deviceProgramData->buildStatus; |
| copyValue = &valStatus; |
| copySize = sizeof(valStatus); |
| break; |
| case cl::ProgramBuildInfo::Log: |
| copyValue = deviceProgramData->buildLog.c_str(); |
| copySize = deviceProgramData->buildLog.size() + 1; |
| break; |
| case cl::ProgramBuildInfo::Options: |
| copyValue = mProgramOpts.c_str(); |
| copySize = mProgramOpts.size() + 1; |
| break; |
| case cl::ProgramBuildInfo::BinaryType: |
| valUInt = deviceProgramData->binaryType; |
| copyValue = &valUInt; |
| copySize = sizeof(valUInt); |
| break; |
| case cl::ProgramBuildInfo::GlobalVariableTotalSize: |
| // Returns 0 if device does not support program scope global variables. |
| valUInt = 0; |
| copyValue = &valUInt; |
| copySize = sizeof(valUInt); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| |
| if ((value != nullptr) && (copyValue != nullptr)) |
| { |
| memcpy(value, copyValue, std::min(valueSize, copySize)); |
| } |
| |
| if (valueSizeRet != nullptr) |
| { |
| *valueSizeRet = copySize; |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result CLProgramVk::createKernel(const cl::Kernel &kernel, |
| const char *name, |
| CLKernelImpl::Ptr *kernelOut) |
| { |
| // Wait for the compile to finish |
| mAsyncBuildEvent->wait(); |
| |
| std::scoped_lock<angle::SimpleMutex> sl(mProgramMutex); |
| const auto devProgram = getDeviceProgramData(name); |
| ASSERT(devProgram != nullptr); |
| |
| // Create kernel |
| std::string kernelAttributes = devProgram->getKernelAttributes(name); |
| std::string kernelName = std::string(name ? name : ""); |
| CLKernelVk::Ptr kernelImpl = |
| CLKernelVk::Ptr(new (std::nothrow) CLKernelVk(kernel, kernelName, kernelAttributes)); |
| if (kernelImpl == nullptr) |
| { |
| ERR() << "Could not create kernel obj!"; |
| ANGLE_CL_RETURN_ERROR(CL_OUT_OF_HOST_MEMORY); |
| } |
| |
| ANGLE_TRY(kernelImpl->init()); |
| *kernelOut = std::move(kernelImpl); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result CLProgramVk::createKernels(cl_uint numKernels, |
| CLKernelImpl::CreateFuncs &createFuncs, |
| cl_uint *numKernelsRet) |
| { |
| size_t numDevKernels = 0; |
| for (const auto &dev : mAssociatedDevicePrograms) |
| { |
| numDevKernels += dev.second.numKernels(); |
| } |
| if (numKernelsRet != nullptr) |
| { |
| *numKernelsRet = static_cast<cl_uint>(numDevKernels); |
| } |
| |
| if (numKernels != 0) |
| { |
| for (const auto &dev : mAssociatedDevicePrograms) |
| { |
| for (const auto &kernArgMap : dev.second.getKernelArgsMap()) |
| { |
| createFuncs.emplace_back([this, &kernArgMap](const cl::Kernel &kern) { |
| CLKernelImpl::Ptr implPtr = nullptr; |
| ANGLE_CL_IMPL_TRY(this->createKernel(kern, kernArgMap.first.c_str(), &implPtr)); |
| return CLKernelImpl::Ptr(std::move(implPtr)); |
| }); |
| } |
| } |
| } |
| return angle::Result::Continue; |
| } |
| |
| const CLProgramVk::DeviceProgramData *CLProgramVk::getDeviceProgramData( |
| const _cl_device_id *device) const |
| { |
| if (!mAssociatedDevicePrograms.contains(device)) |
| { |
| WARN() << "Device (" << device << ") is not associated with program (" << this << ") !"; |
| return nullptr; |
| } |
| return &mAssociatedDevicePrograms.at(device); |
| } |
| |
| const CLProgramVk::DeviceProgramData *CLProgramVk::getDeviceProgramData( |
| const char *kernelName) const |
| { |
| for (const auto &deviceProgram : mAssociatedDevicePrograms) |
| { |
| if (deviceProgram.second.containsKernel(kernelName)) |
| { |
| return &deviceProgram.second; |
| } |
| } |
| WARN() << "Kernel name (" << kernelName << ") is not associated with program (" << this |
| << ") !"; |
| return nullptr; |
| } |
| |
| cl::BufferPtr CLProgramVk::getOrCreateModuleConstantDataBuffer(const std::string &kernelName) |
| { |
| const DeviceProgramData *devProgram = getDeviceProgramData(kernelName.c_str()); |
| ASSERT(devProgram); |
| const std::vector<uint8_t> &initData = |
| devProgram->reflectionData.constantDataBufferInfo.bufferData; |
| void *initDataPtr = reinterpret_cast<void *>(const_cast<uint8_t *>(initData.data())); |
| |
| if (!mModuleConstantDataBuffer) |
| { |
| mModuleConstantDataBuffer = cl::BufferPtr::Create( |
| const_cast<cl::Context &>(mProgram.getContext()), cl::Memory::PropArray{}, |
| cl::MemFlags(CL_MEM_USE_HOST_PTR), initData.size(), initDataPtr); |
| } |
| else |
| { |
| // reading that this buffer is already present and is sufficient to hold initData |
| ASSERT(mModuleConstantDataBuffer->getSize() == initData.size()); |
| } |
| |
| return mModuleConstantDataBuffer; |
| } |
| |
| bool CLProgramVk::buildInternal(const cl::DevicePtrs &devices, |
| std::string options, |
| std::string internalOptions, |
| BuildType buildType, |
| const LinkProgramsList &LinkProgramsList) |
| { |
| std::scoped_lock<angle::SimpleMutex> sl(mProgramMutex); |
| |
| // Cache original options string |
| mProgramOpts = options; |
| |
| // Build for each associated device |
| for (size_t i = 0; i < devices.size(); ++i) |
| { |
| const cl::RefPointer<cl::Device> &device = devices.at(i); |
| DeviceProgramData &deviceProgramData = mAssociatedDevicePrograms[device->getNative()]; |
| deviceProgramData.spirvVersion = device->getImpl<CLDeviceVk>().getSpirvVersion(); |
| if (buildType != BuildType::BINARY) |
| { |
| // Process options and append any other internal (required) options for clspv |
| std::vector<std::string> optionTokens; |
| angle::SplitStringAlongWhitespace(options + " " + internalOptions, &optionTokens); |
| const bool createLibrary = std::find(optionTokens.begin(), optionTokens.end(), |
| "-create-library") != optionTokens.end(); |
| std::string processedOptions = ProcessBuildOptions(optionTokens, buildType); |
| // add clspv compiler options based on device features |
| processedOptions += ClspvGetCompilerOptions(&device->getImpl<CLDeviceVk>()); |
| |
| switch (buildType) |
| { |
| case BuildType::BUILD: |
| case BuildType::COMPILE: |
| { |
| ScopedClspvContext clspvCtx; |
| const char *clSrc = mProgram.getSource().c_str(); |
| |
| ClspvError clspvRet = ClspvCompileSource( |
| 1, NULL, static_cast<const char **>(&clSrc), processedOptions.c_str(), |
| &clspvCtx.mOutputBin, &clspvCtx.mOutputBinSize, &clspvCtx.mOutputBuildLog); |
| deviceProgramData.buildLog = |
| clspvCtx.mOutputBuildLog != nullptr ? clspvCtx.mOutputBuildLog : ""; |
| if (clspvRet != CLSPV_SUCCESS) |
| { |
| ERR() << "OpenCL build failed with: ClspvError(" << clspvRet << ")!"; |
| ERR() << "Clspv option: " << processedOptions; |
| ERR() << "Build log: " << std::endl << deviceProgramData.buildLog; |
| deviceProgramData.buildStatus = CL_BUILD_ERROR; |
| return false; |
| } |
| |
| if (buildType == BuildType::COMPILE) |
| { |
| deviceProgramData.IR.assign(clspvCtx.mOutputBinSize, 0); |
| std::memcpy(deviceProgramData.IR.data(), clspvCtx.mOutputBin, |
| clspvCtx.mOutputBinSize); |
| deviceProgramData.binaryType = CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT; |
| } |
| else |
| { |
| deviceProgramData.binary.assign(clspvCtx.mOutputBinSize / sizeof(uint32_t), |
| 0); |
| std::memcpy(deviceProgramData.binary.data(), clspvCtx.mOutputBin, |
| clspvCtx.mOutputBinSize); |
| deviceProgramData.binaryType = CL_PROGRAM_BINARY_TYPE_EXECUTABLE; |
| } |
| break; |
| } |
| case BuildType::LINK: |
| { |
| ScopedClspvContext clspvCtx; |
| std::vector<size_t> vSizes; |
| std::vector<const char *> vBins; |
| const LinkPrograms &linkPrograms = LinkProgramsList.at(i); |
| for (const CLProgramVk::DeviceProgramData *linkProgramData : linkPrograms) |
| { |
| vSizes.push_back(linkProgramData->IR.size()); |
| vBins.push_back(linkProgramData->IR.data()); |
| } |
| |
| ClspvError clspvRet = ClspvCompileSource( |
| linkPrograms.size(), vSizes.data(), vBins.data(), processedOptions.c_str(), |
| &clspvCtx.mOutputBin, &clspvCtx.mOutputBinSize, &clspvCtx.mOutputBuildLog); |
| deviceProgramData.buildLog = |
| clspvCtx.mOutputBuildLog != nullptr ? clspvCtx.mOutputBuildLog : ""; |
| if (clspvRet != CLSPV_SUCCESS) |
| { |
| ERR() << "OpenCL build failed with: ClspvError(" << clspvRet << ")!"; |
| ERR() << "Clspv option: " << processedOptions; |
| ERR() << "Build log: " << std::endl << deviceProgramData.buildLog; |
| deviceProgramData.buildStatus = CL_BUILD_ERROR; |
| return false; |
| } |
| |
| if (createLibrary) |
| { |
| deviceProgramData.IR.assign(clspvCtx.mOutputBinSize, 0); |
| std::memcpy(deviceProgramData.IR.data(), clspvCtx.mOutputBin, |
| clspvCtx.mOutputBinSize); |
| deviceProgramData.binaryType = CL_PROGRAM_BINARY_TYPE_LIBRARY; |
| } |
| else |
| { |
| deviceProgramData.binary.assign(clspvCtx.mOutputBinSize / sizeof(uint32_t), |
| 0); |
| std::memcpy(deviceProgramData.binary.data(), |
| reinterpret_cast<char *>(clspvCtx.mOutputBin), |
| clspvCtx.mOutputBinSize); |
| deviceProgramData.binaryType = CL_PROGRAM_BINARY_TYPE_EXECUTABLE; |
| } |
| break; |
| } |
| default: |
| UNREACHABLE(); |
| return false; |
| } |
| } |
| |
| // Extract reflection info from spv binary and populate reflection data, as well as create |
| // the shader module |
| if (deviceProgramData.binaryType == CL_PROGRAM_BINARY_TYPE_EXECUTABLE) |
| { |
| // Report SPIR-V validation failure as a build failure |
| if (!ClspvValidate(mContext->getRenderer(), deviceProgramData.binary)) |
| { |
| ERR() << "Failed to validate SPIR-V binary!"; |
| deviceProgramData.buildStatus = CL_BUILD_ERROR; |
| return false; |
| } |
| if (!ClspvParseReflection(mContext->getRenderer(), deviceProgramData.binary, |
| deviceProgramData.reflectionData)) |
| { |
| ERR() << "Failed to parse reflection info from SPIR-V!"; |
| deviceProgramData.buildStatus = CL_BUILD_ERROR; |
| return false; |
| } |
| |
| angle::spirv::Blob strippedSpvBlob; |
| angle::spirv::Blob *spvBlobPtr = &deviceProgramData.binary; |
| if (!mContext->getFeatures().supportsShaderNonSemanticInfo.enabled) |
| { |
| // If VK driver does not support non-semantic instructions, attempt to use a |
| // "stripped" version of the spir-v. |
| if (ClspvStripReflection(mContext->getRenderer(), deviceProgramData.binary, |
| strippedSpvBlob)) |
| { |
| spvBlobPtr = &strippedSpvBlob; |
| } |
| } |
| if (mShader) |
| { |
| // Reset shader module if we built program prior to this current build |
| mShader.reset(); |
| } |
| if (IsError(vk::InitShaderModule(mContext, &mShader, spvBlobPtr->data(), |
| spvBlobPtr->size() * sizeof(uint32_t)))) |
| { |
| ERR() << "Failed to init Vulkan Shader Module!"; |
| deviceProgramData.buildStatus = CL_BUILD_ERROR; |
| return false; |
| } |
| |
| // Setup inital push constant range |
| uint32_t pushConstantMinOffet = UINT32_MAX, pushConstantMaxOffset = 0, |
| pushConstantMaxSize = 0; |
| for (const auto &pushConstant : deviceProgramData.reflectionData.pushConstants) |
| { |
| pushConstantMinOffet = pushConstant.second.offset < pushConstantMinOffet |
| ? pushConstant.second.offset |
| : pushConstantMinOffet; |
| if (pushConstant.second.offset >= pushConstantMaxOffset) |
| { |
| pushConstantMaxOffset = pushConstant.second.offset; |
| pushConstantMaxSize = pushConstant.second.size; |
| } |
| } |
| for (const auto &pushConstant : deviceProgramData.reflectionData.imagePushConstants) |
| { |
| for (const auto imageConstant : pushConstant.second) |
| { |
| pushConstantMinOffet = imageConstant.pcRange.offset < pushConstantMinOffet |
| ? imageConstant.pcRange.offset |
| : pushConstantMinOffet; |
| if (imageConstant.pcRange.offset >= pushConstantMaxOffset) |
| { |
| pushConstantMaxOffset = imageConstant.pcRange.offset; |
| pushConstantMaxSize = imageConstant.pcRange.size; |
| } |
| } |
| } |
| deviceProgramData.pushConstRange.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; |
| deviceProgramData.pushConstRange.offset = |
| pushConstantMinOffet == UINT32_MAX ? 0 : pushConstantMinOffet; |
| deviceProgramData.pushConstRange.size = pushConstantMaxOffset + pushConstantMaxSize; |
| |
| if (kAngleDebug) |
| { |
| if (mContext->getFeatures().clDumpVkSpirv.enabled) |
| { |
| angle::spirv::Print(deviceProgramData.binary); |
| } |
| } |
| } |
| deviceProgramData.buildStatus = CL_BUILD_SUCCESS; |
| } |
| return true; |
| } |
| |
| void CLProgramVk::setBuildStatus(const cl::DevicePtrs &devices, cl_build_status status) |
| { |
| std::scoped_lock<angle::SimpleMutex> sl(mProgramMutex); |
| |
| for (const auto &device : devices) |
| { |
| ASSERT(mAssociatedDevicePrograms.contains(device->getNative())); |
| DeviceProgramData &deviceProgram = mAssociatedDevicePrograms.at(device->getNative()); |
| deviceProgram.buildStatus = status; |
| } |
| } |
| |
| const angle::HashMap<uint32_t, ClspvPrintfInfo> *CLProgramVk::getPrintfDescriptors( |
| const std::string &kernelName) const |
| { |
| const DeviceProgramData *deviceProgram = getDeviceProgramData(kernelName.c_str()); |
| if (deviceProgram) |
| { |
| return &deviceProgram->reflectionData.printfInfoMap; |
| } |
| return nullptr; |
| } |
| |
| } // namespace rx |