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