blob: b29c8c61f116b46932af8f7445dc12e61b825ec2 [file] [log] [blame]
/*
* Copyright 2022 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/VulkanSharedContext.h"
#include "include/gpu/GpuTypes.h"
#include "include/gpu/graphite/ContextOptions.h"
#include "include/gpu/graphite/PersistentPipelineStorage.h"
#include "include/gpu/vk/VulkanBackendContext.h"
#include "include/gpu/vk/VulkanExtensions.h"
#include "include/private/base/SkMutex.h"
#include "src/gpu/GpuTypesPriv.h"
#include "src/gpu/graphite/Log.h"
#include "src/gpu/graphite/ResourceTypes.h"
#include "src/gpu/graphite/vk/VulkanBuffer.h"
#include "src/gpu/graphite/vk/VulkanCaps.h"
#include "src/gpu/graphite/vk/VulkanResourceProvider.h"
#include "src/gpu/vk/VulkanInterface.h"
#include "src/gpu/vk/VulkanUtilsPriv.h"
#if defined(SK_USE_VMA)
#include "src/gpu/vk/vulkanmemoryallocator/VulkanMemoryAllocatorPriv.h"
#endif
namespace skgpu::graphite {
sk_sp<SharedContext> VulkanSharedContext::Make(const VulkanBackendContext& context,
const ContextOptions& options) {
if (context.fInstance == VK_NULL_HANDLE ||
context.fPhysicalDevice == VK_NULL_HANDLE ||
context.fDevice == VK_NULL_HANDLE ||
context.fQueue == VK_NULL_HANDLE) {
SKGPU_LOG_E("Failed to create VulkanSharedContext because either fInstance,"
"fPhysicalDevice, fDevice, or fQueue in the VulkanBackendContext is"
"VK_NULL_HANDLE.");
return nullptr;
}
if (!context.fGetProc) {
SKGPU_LOG_E("Failed to create VulkanSharedContext because there is no valid VulkanGetProc"
"on the VulkanBackendContext");
return nullptr;
}
// If no extensions are provided, make sure we don't have a null dereference downstream.
skgpu::VulkanExtensions noExtensions;
const skgpu::VulkanExtensions* extensions = &noExtensions;
if (context.fVkExtensions) {
extensions = context.fVkExtensions;
}
uint32_t physDevVersion = 0;
sk_sp<const skgpu::VulkanInterface> interface =
skgpu::MakeInterface(context, extensions, &physDevVersion, nullptr);
if (!interface) {
SKGPU_LOG_E("Failed to create VulkanInterface.");
return nullptr;
}
VkPhysicalDeviceFeatures2 features;
const VkPhysicalDeviceFeatures2* featuresPtr;
// If fDeviceFeatures2 is not null, then we ignore fDeviceFeatures. If both are null, we assume
// no features are enabled.
if (!context.fDeviceFeatures2 && context.fDeviceFeatures) {
features.pNext = nullptr;
features.features = *context.fDeviceFeatures;
featuresPtr = &features;
} else {
featuresPtr = context.fDeviceFeatures2;
}
std::unique_ptr<const VulkanCaps> caps(new VulkanCaps(options,
interface.get(),
context.fPhysicalDevice,
physDevVersion,
featuresPtr,
extensions,
context.fProtectedContext));
sk_sp<skgpu::VulkanMemoryAllocator> memoryAllocator = context.fMemoryAllocator;
#if defined(SK_USE_VMA)
if (!memoryAllocator) {
// We were not given a memory allocator at creation
skgpu::ThreadSafe threadSafe = options.fClientWillExternallySynchronizeAllThreads
? skgpu::ThreadSafe::kNo
: skgpu::ThreadSafe::kYes;
memoryAllocator = skgpu::VulkanMemoryAllocators::Make(context,
threadSafe,
options.fVulkanVMALargeHeapBlockSize);
}
#endif
if (!memoryAllocator) {
SKGPU_LOG_E("No supplied vulkan memory allocator and unable to create one internally.");
return nullptr;
}
return sk_sp<SharedContext>(new VulkanSharedContext(context,
std::move(interface),
std::move(memoryAllocator),
std::move(caps),
options.fExecutor,
options.fPersistentPipelineStorage,
options.fUserDefinedKnownRuntimeEffects));
}
VulkanSharedContext::VulkanSharedContext(
const VulkanBackendContext& backendContext,
sk_sp<const skgpu::VulkanInterface> interface,
sk_sp<skgpu::VulkanMemoryAllocator> memoryAllocator,
std::unique_ptr<const VulkanCaps> caps,
SkExecutor* executor,
PersistentPipelineStorage* persistentPipelineStorage,
SkSpan<sk_sp<SkRuntimeEffect>> userDefinedKnownRuntimeEffects)
: SharedContext(std::move(caps),
BackendApi::kVulkan,
executor,
userDefinedKnownRuntimeEffects)
, fInterface(std::move(interface))
, fMemoryAllocator(std::move(memoryAllocator))
, fPhysDevice(backendContext.fPhysicalDevice)
, fDevice(backendContext.fDevice)
, fQueueIndex(backendContext.fGraphicsQueueIndex)
, fDeviceLostContext(backendContext.fDeviceLostContext)
, fDeviceLostProc(backendContext.fDeviceLostProc) {
fPipelineCache = this->createPipelineCache(backendContext.fPhysicalDevice,
persistentPipelineStorage);
fThreadSafeResourceProvider = std::make_unique<VulkanThreadSafeResourceProvider>(
this->makeResourceProvider(&fSingleOwner,
SK_InvalidGenID,
kThreadedSafeResourceBudget));
}
VulkanSharedContext::~VulkanSharedContext() {
if (fPipelineCache != VK_NULL_HANDLE) {
VULKAN_CALL(this->interface(),
DestroyPipelineCache(this->device(),
fPipelineCache,
nullptr));
fPipelineCache = VK_NULL_HANDLE;
}
fThreadSafeResourceProvider.reset();
// need to clear out resources before the allocator is removed
this->globalCache()->deleteResources();
}
VkPipelineCache VulkanSharedContext::createPipelineCache(
VkPhysicalDevice physDev,
PersistentPipelineStorage* persistentPipelineStorage) {
VkPipelineCacheCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
createInfo.initialDataSize = 0;
createInfo.pInitialData = nullptr;
if (persistentPipelineStorage) {
sk_sp<SkData> cachedData = persistentPipelineStorage->load();
// For version one of the header, the total header size is 16 bytes plus
// VK_UUID_SIZE bytes. See Section 9.6 (Pipeline Cache) in the vulkan spec to see
// the breakdown of these bytes.
static const int kV1HeaderSize = 4*sizeof(uint32_t) + VK_UUID_SIZE;
if (cachedData && cachedData->size() >= kV1HeaderSize) {
const uint32_t* cacheHeader = (const uint32_t*)cachedData->data();
if (cacheHeader[1] == VK_PIPELINE_CACHE_HEADER_VERSION_ONE) {
SkASSERT(cacheHeader[0] == kV1HeaderSize);
VkPhysicalDeviceProperties2 props;
props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
props.pNext = nullptr;
VULKAN_CALL(fInterface.get(), GetPhysicalDeviceProperties2(physDev, &props));
const VkPhysicalDeviceProperties& physDevProps = props.properties;
const uint8_t* supportedPipelineCacheUUID = physDevProps.pipelineCacheUUID;
if (cacheHeader[2] == physDevProps.vendorID &&
cacheHeader[3] == physDevProps.deviceID &&
memcmp(&cacheHeader[4], supportedPipelineCacheUUID, VK_UUID_SIZE) == 0) {
createInfo.initialDataSize = cachedData->size();
createInfo.pInitialData = cachedData->data();
fLastKnownPersistentPipelineStorageSize = createInfo.initialDataSize;
}
}
}
}
VkResult result;
VkPipelineCache pipelineCache = VK_NULL_HANDLE;
VULKAN_CALL_RESULT(this,
result,
CreatePipelineCache(this->device(),
&createInfo,
nullptr,
&pipelineCache));
if (VK_SUCCESS != result) {
SKGPU_LOG_W("CreatePipelineCache failed");
return VK_NULL_HANDLE;
}
return pipelineCache;
}
VulkanThreadSafeResourceProvider* VulkanSharedContext::threadSafeResourceProvider() const {
return static_cast<VulkanThreadSafeResourceProvider*>(fThreadSafeResourceProvider.get());
}
void VulkanSharedContext::syncPipelineData(PersistentPipelineStorage* persistentPipelineStorage,
size_t maxSize) {
SkASSERT(persistentPipelineStorage);
if (fPipelineCache == VK_NULL_HANDLE || !fHasNewVkPipelineCacheData) {
return; // ill-formed SharedContext or no new pipelines
}
size_t origDataSize = 0;
VkResult result;
VULKAN_CALL_RESULT(
this,
result,
GetPipelineCacheData(this->device(), fPipelineCache, &origDataSize, nullptr));
if (result != VK_SUCCESS) {
return;
}
if (!this->vulkanCaps().supportsPipelineCreationCacheControl()) {
// Since we don't have cache control 'fHasNewVkPipelineCacheData' can be wildly
// inaccurate. Attempt to compensate by comparing the uncapped data sizes.
if (fLastKnownPersistentPipelineStorageSize == origDataSize) {
return;
}
}
size_t cappedDataSize = std::min(origDataSize, maxSize);
std::unique_ptr<uint8_t[]> data(new uint8_t[cappedDataSize]);
VULKAN_CALL_RESULT_NOCHECK(
this->interface(),
result,
GetPipelineCacheData(this->device(), fPipelineCache, &cappedDataSize, (void*)data.get()));
this->checkVkResult(result);
if ((result != VK_SUCCESS) && (result != VK_INCOMPLETE)) {
return;
}
fLastKnownPersistentPipelineStorageSize = origDataSize;
fHasNewVkPipelineCacheData = false;
persistentPipelineStorage->store(*SkData::MakeWithoutCopy(data.get(), cappedDataSize));
}
std::unique_ptr<ResourceProvider> VulkanSharedContext::makeResourceProvider(
SingleOwner* singleOwner,
uint32_t recorderID,
size_t resourceBudget) {
return std::unique_ptr<ResourceProvider>(
new VulkanResourceProvider(this,
singleOwner,
recorderID,
resourceBudget));
}
sk_sp<GraphicsPipeline> VulkanSharedContext::createGraphicsPipeline(
const RuntimeEffectDictionary* runtimeDict,
const UniqueKey& pipelineKey,
const GraphicsPipelineDesc& pipelineDesc,
const RenderPassDesc& renderPassDesc,
SkEnumBitMask<PipelineCreationFlags> pipelineCreationFlags,
uint32_t compilationID) {
return VulkanGraphicsPipeline::Make(this,
runtimeDict,
pipelineKey,
pipelineDesc,
renderPassDesc,
pipelineCreationFlags,
compilationID);
}
bool VulkanSharedContext::checkVkResult(VkResult result) const {
switch (result) {
case VK_SUCCESS:
return true;
case VK_ERROR_DEVICE_LOST:
{
SkAutoMutexExclusive lock(fDeviceIsLostMutex);
if (fDeviceIsLost) {
return false;
}
fDeviceIsLost = true;
// Fall through to InvokeDeviceLostCallback (on first VK_ERROR_DEVICE_LOST) only afer
// releasing fDeviceIsLostMutex, otherwise clients might cause deadlock by checking
// isDeviceLost() from the callback.
}
skgpu::InvokeDeviceLostCallback(interface(),
device(),
fDeviceLostContext,
fDeviceLostProc,
vulkanCaps().supportsDeviceFaultInfo());
return false;
case VK_ERROR_OUT_OF_DEVICE_MEMORY:
case VK_ERROR_OUT_OF_HOST_MEMORY:
// TODO: determine how we'll track this in a thread-safe manner
//this->setOOMed();
return false;
default:
return false;
}
}
} // namespace skgpu::graphite