| /* | |
| * 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 |