/* * 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/VulkanResourceProvider.h" #include "include/core/SkSpan.h" #include "include/gpu/MutableTextureState.h" #include "include/gpu/graphite/BackendTexture.h" #include "include/gpu/graphite/vk/VulkanGraphiteTypes.h" #include "include/gpu/vk/VulkanMutableTextureState.h" #include "src/gpu/graphite/Buffer.h" #include "src/gpu/graphite/ComputePipeline.h" #include "src/gpu/graphite/GraphicsPipeline.h" #include "src/gpu/graphite/RenderPassDesc.h" #include "src/gpu/graphite/Sampler.h" #include "src/gpu/graphite/Texture.h" #include "src/gpu/graphite/vk/VulkanBuffer.h" #include "src/gpu/graphite/vk/VulkanCommandBuffer.h" #include "src/gpu/graphite/vk/VulkanDescriptorPool.h" #include "src/gpu/graphite/vk/VulkanDescriptorSet.h" #include "src/gpu/graphite/vk/VulkanFramebuffer.h" #include "src/gpu/graphite/vk/VulkanGraphicsPipeline.h" #include "src/gpu/graphite/vk/VulkanGraphiteTypesPriv.h" #include "src/gpu/graphite/vk/VulkanRenderPass.h" #include "src/gpu/graphite/vk/VulkanSampler.h" #include "src/gpu/graphite/vk/VulkanSharedContext.h" #include "src/gpu/graphite/vk/VulkanTexture.h" #include "src/gpu/graphite/vk/VulkanYcbcrConversion.h" #include "src/gpu/vk/VulkanMemory.h" #include "src/sksl/SkSLCompiler.h" #ifdef SK_BUILD_FOR_ANDROID #include "src/gpu/vk/VulkanUtilsPriv.h" #include #endif namespace skgpu::graphite { constexpr int kMaxNumberOfCachedBufferDescSets = 1024; VulkanResourceProvider::VulkanResourceProvider(SharedContext* sharedContext, SingleOwner* singleOwner, uint32_t recorderID, size_t resourceBudget, sk_sp intrinsicConstantUniformBuffer) : ResourceProvider(sharedContext, singleOwner, recorderID, resourceBudget) , fIntrinsicUniformBuffer(std::move(intrinsicConstantUniformBuffer)) , fUniformBufferDescSetCache(kMaxNumberOfCachedBufferDescSets) {} VulkanResourceProvider::~VulkanResourceProvider() { if (fPipelineCache != VK_NULL_HANDLE) { VULKAN_CALL(this->vulkanSharedContext()->interface(), DestroyPipelineCache(this->vulkanSharedContext()->device(), fPipelineCache, nullptr)); } if (fMSAALoadVertShaderModule != VK_NULL_HANDLE) { VULKAN_CALL(this->vulkanSharedContext()->interface(), DestroyShaderModule(this->vulkanSharedContext()->device(), fMSAALoadVertShaderModule, nullptr)); } if (fMSAALoadFragShaderModule != VK_NULL_HANDLE) { VULKAN_CALL(this->vulkanSharedContext()->interface(), DestroyShaderModule(this->vulkanSharedContext()->device(), fMSAALoadFragShaderModule, nullptr)); } if (fMSAALoadPipelineLayout != VK_NULL_HANDLE) { VULKAN_CALL(this->vulkanSharedContext()->interface(), DestroyPipelineLayout(this->vulkanSharedContext()->device(), fMSAALoadPipelineLayout, nullptr)); } } const VulkanSharedContext* VulkanResourceProvider::vulkanSharedContext() const { return static_cast(fSharedContext); } sk_sp VulkanResourceProvider::onCreateWrappedTexture(const BackendTexture& texture) { sk_sp ycbcrConversion; if (TextureInfos::GetVulkanYcbcrConversionInfo(texture.info()).isValid()) { ycbcrConversion = this->findOrCreateCompatibleYcbcrConversion( TextureInfos::GetVulkanYcbcrConversionInfo(texture.info())); if (!ycbcrConversion) { return nullptr; } } return VulkanTexture::MakeWrapped(this->vulkanSharedContext(), texture.dimensions(), texture.info(), BackendTextures::GetMutableState(texture), BackendTextures::GetVkImage(texture), /*alloc=*/{} /*Skia does not own wrapped texture memory*/, std::move(ycbcrConversion)); } sk_sp VulkanResourceProvider::refIntrinsicConstantBuffer() const { return fIntrinsicUniformBuffer; } sk_sp VulkanResourceProvider::createGraphicsPipeline( const RuntimeEffectDictionary* runtimeDict, const GraphicsPipelineDesc& pipelineDesc, const RenderPassDesc& renderPassDesc, SkEnumBitMask pipelineCreationFlags) { return VulkanGraphicsPipeline::Make(this, runtimeDict, pipelineDesc, renderPassDesc, pipelineCreationFlags); } sk_sp VulkanResourceProvider::createComputePipeline(const ComputePipelineDesc&) { return nullptr; } sk_sp VulkanResourceProvider::createTexture(SkISize size, const TextureInfo& info, skgpu::Budgeted budgeted) { sk_sp ycbcrConversion; if (TextureInfos::GetVulkanYcbcrConversionInfo(info).isValid()) { ycbcrConversion = this->findOrCreateCompatibleYcbcrConversion( TextureInfos::GetVulkanYcbcrConversionInfo(info)); if (!ycbcrConversion) { return nullptr; } } return VulkanTexture::Make(this->vulkanSharedContext(), size, info, budgeted, std::move(ycbcrConversion)); } sk_sp VulkanResourceProvider::createBuffer(size_t size, BufferType type, AccessPattern accessPattern) { return VulkanBuffer::Make(this->vulkanSharedContext(), size, type, accessPattern); } sk_sp VulkanResourceProvider::createSampler(const SamplerDesc& samplerDesc) { sk_sp ycbcrConversion = nullptr; // Non-zero conversion information means the sampler utilizes a ycbcr conversion. bool usesYcbcrConversion = (samplerDesc.desc() >> SamplerDesc::kImmutableSamplerInfoShift) != 0; if (usesYcbcrConversion) { GraphiteResourceKey ycbcrKey = VulkanYcbcrConversion::GetKeyFromSamplerDesc(samplerDesc); if (Resource* resource = fResourceCache->findAndRefResource(ycbcrKey, skgpu::Budgeted::kYes)) { ycbcrConversion = sk_sp(static_cast(resource)); } else { ycbcrConversion = VulkanYcbcrConversion::Make( this->vulkanSharedContext(), static_cast( samplerDesc.desc() >> SamplerDesc::kImmutableSamplerInfoShift), (uint64_t)(samplerDesc.externalFormatMSBs()) << 32 | samplerDesc.format()); SkASSERT(ycbcrConversion); ycbcrConversion->setKey(ycbcrKey); fResourceCache->insertResource(ycbcrConversion.get()); } } return VulkanSampler::Make(this->vulkanSharedContext(), samplerDesc, std::move(ycbcrConversion)); } BackendTexture VulkanResourceProvider::onCreateBackendTexture(SkISize dimensions, const TextureInfo& info) { VulkanTextureInfo vkTexInfo; if (!TextureInfos::GetVulkanTextureInfo(info, &vkTexInfo)) { return {}; } VulkanTexture::CreatedImageInfo createdTextureInfo; if (!VulkanTexture::MakeVkImage(this->vulkanSharedContext(), dimensions, info, &createdTextureInfo)) { return {}; } return BackendTextures::MakeVulkan( dimensions, vkTexInfo, skgpu::MutableTextureStates::GetVkImageLayout(createdTextureInfo.fMutableState.get()), skgpu::MutableTextureStates::GetVkQueueFamilyIndex( createdTextureInfo.fMutableState.get()), createdTextureInfo.fImage, createdTextureInfo.fMemoryAlloc); } namespace { GraphiteResourceKey build_desc_set_key(const SkSpan& requestedDescriptors) { static const ResourceType kType = GraphiteResourceKey::GenerateResourceType(); // The number of int32s needed for a key can depend on whether we use immutable samplers or not. // So, accumulte key data while passing through to check for that quantity and simply copy // into builder afterwards. skia_private::TArray keyData (requestedDescriptors.size() + 1); keyData.push_back(requestedDescriptors.size()); for (const DescriptorData& desc : requestedDescriptors) { keyData.push_back(static_cast(desc.fType) << 24 | desc.fBindingIndex << 16 | static_cast(desc.fCount)); if (desc.fImmutableSampler) { const VulkanSampler* sampler = static_cast(desc.fImmutableSampler); SkASSERT(sampler); keyData.push_back_n(sampler->samplerDesc().asSpan().size(), sampler->samplerDesc().asSpan().data()); } } GraphiteResourceKey key; GraphiteResourceKey::Builder builder(&key, kType, keyData.size(), Shareable::kNo); for (int i = 0; i < keyData.size(); i++) { builder[i] = keyData[i]; } builder.finish(); return key; } sk_sp add_new_desc_set_to_cache(const VulkanSharedContext* context, const sk_sp& pool, const GraphiteResourceKey& descSetKey, ResourceCache* resourceCache) { sk_sp descSet = VulkanDescriptorSet::Make(context, pool); if (!descSet) { return nullptr; } descSet->setKey(descSetKey); resourceCache->insertResource(descSet.get()); return descSet; } } // anonymous namespace sk_sp VulkanResourceProvider::findOrCreateDescriptorSet( SkSpan requestedDescriptors) { if (requestedDescriptors.empty()) { return nullptr; } // Search for available descriptor sets by assembling a key based upon the set's structure. GraphiteResourceKey key = build_desc_set_key(requestedDescriptors); if (auto descSet = fResourceCache->findAndRefResource(key, skgpu::Budgeted::kYes)) { // A non-null resource pointer indicates we have found an available descriptor set. return sk_sp(static_cast(descSet)); } // If we did not find an existing avilable desc set, allocate sets with the appropriate layout // and add them to the cache. VkDescriptorSetLayout layout; const VulkanSharedContext* context = this->vulkanSharedContext(); DescriptorDataToVkDescSetLayout(context, requestedDescriptors, &layout); if (!layout) { return nullptr; } auto pool = VulkanDescriptorPool::Make(context, requestedDescriptors, layout); if (!pool) { VULKAN_CALL(context->interface(), DestroyDescriptorSetLayout(context->device(), layout, nullptr)); return nullptr; } // Start with allocating one descriptor set. If one cannot be successfully created, then we can // return early before attempting to allocate more. Storing a ptr to the first set also // allows us to return that later without having to perform a find operation on the cache once // all the sets are added. auto firstDescSet = add_new_desc_set_to_cache(context, pool, key, fResourceCache.get()); if (!firstDescSet) { return nullptr; } // Continue to allocate & cache the maximum number of sets so they can be easily accessed as // they're needed. for (int i = 1; i < VulkanDescriptorPool::kMaxNumSets ; i++) { auto descSet = add_new_desc_set_to_cache(context, pool, key, fResourceCache.get()); if (!descSet) { SKGPU_LOG_W("Descriptor set allocation %d of %d was unsuccessful; no more sets will be" "allocated from this pool.", i, VulkanDescriptorPool::kMaxNumSets); break; } } return firstDescSet; } namespace { VulkanResourceProvider::UniformBindGroupKey make_ubo_bind_group_key( SkSpan requestedDescriptors, SkSpan bindUniformBufferInfo) { VulkanResourceProvider::UniformBindGroupKey uniqueKey; { // Each entry in the bind group needs 2 uint32_t in the key: // - buffer's unique ID: 32 bits. // - buffer's binding size: 32 bits. // We need total of 4 entries in the uniform buffer bind group. // Unused entries will be assigned zero values. VulkanResourceProvider::UniformBindGroupKey::Builder builder(&uniqueKey); for (uint32_t i = 0; i < VulkanGraphicsPipeline::kNumUniformBuffers; ++i) { builder[2 * i] = 0; builder[2 * i + 1] = 0; } for (uint32_t i = 0; i < requestedDescriptors.size(); ++i) { int descriptorBindingIndex = requestedDescriptors[i].fBindingIndex; SkASSERT(SkTo(descriptorBindingIndex) < bindUniformBufferInfo.size()); SkASSERT(SkTo(descriptorBindingIndex) < VulkanGraphicsPipeline::kNumUniformBuffers); const auto& bindInfo = bindUniformBufferInfo[descriptorBindingIndex]; const VulkanBuffer* boundBuffer = static_cast(bindInfo.fBuffer); SkASSERT(boundBuffer); builder[2 * descriptorBindingIndex] = boundBuffer->uniqueID().asUInt(); builder[2 * descriptorBindingIndex + 1] = bindInfo.fSize; } builder.finish(); } return uniqueKey; } void update_uniform_descriptor_set(SkSpan requestedDescriptors, SkSpan bindUniformBufferInfo, VkDescriptorSet descSet, const VulkanSharedContext* sharedContext) { for (size_t i = 0; i < requestedDescriptors.size(); i++) { int descriptorBindingIndex = requestedDescriptors[i].fBindingIndex; SkASSERT(SkTo(descriptorBindingIndex) < bindUniformBufferInfo.size()); const auto& bindInfo = bindUniformBufferInfo[descriptorBindingIndex]; if (bindInfo.fBuffer) { #if defined(SK_DEBUG) static uint64_t maxBufferRange = sharedContext->caps()->storageBufferSupport() ? sharedContext->vulkanCaps().maxStorageBufferRange() : sharedContext->vulkanCaps().maxUniformBufferRange(); SkASSERT(bindInfo.fSize <= maxBufferRange); #endif VkDescriptorBufferInfo bufferInfo; memset(&bufferInfo, 0, sizeof(VkDescriptorBufferInfo)); auto vulkanBuffer = static_cast(bindInfo.fBuffer); bufferInfo.buffer = vulkanBuffer->vkBuffer(); bufferInfo.offset = 0; // We always use dynamic ubos so we set the base offset to 0 bufferInfo.range = bindInfo.fSize; VkWriteDescriptorSet writeInfo; memset(&writeInfo, 0, sizeof(VkWriteDescriptorSet)); writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeInfo.pNext = nullptr; writeInfo.dstSet = descSet; writeInfo.dstBinding = descriptorBindingIndex; writeInfo.dstArrayElement = 0; writeInfo.descriptorCount = requestedDescriptors[i].fCount; writeInfo.descriptorType = DsTypeEnumToVkDs(requestedDescriptors[i].fType); writeInfo.pImageInfo = nullptr; writeInfo.pBufferInfo = &bufferInfo; writeInfo.pTexelBufferView = nullptr; // TODO(b/293925059): Migrate to updating all the uniform descriptors with one driver // call. Calling UpdateDescriptorSets once to encapsulate updates to all uniform // descriptors would be ideal, but that led to issues with draws where all the UBOs // within that set would unexpectedly be assigned the same offset. Updating them one at // a time within this loop works in the meantime but is suboptimal. VULKAN_CALL(sharedContext->interface(), UpdateDescriptorSets(sharedContext->device(), /*descriptorWriteCount=*/1, &writeInfo, /*descriptorCopyCount=*/0, /*pDescriptorCopies=*/nullptr)); } } } } // anonymous namespace sk_sp VulkanResourceProvider::findOrCreateUniformBuffersDescriptorSet( SkSpan requestedDescriptors, SkSpan bindUniformBufferInfo) { SkASSERT(requestedDescriptors.size() <= VulkanGraphicsPipeline::kNumUniformBuffers); auto key = make_ubo_bind_group_key(requestedDescriptors, bindUniformBufferInfo); auto* existingDescSet = fUniformBufferDescSetCache.find(key); if (existingDescSet) { return *existingDescSet; } sk_sp newDS = this->findOrCreateDescriptorSet(requestedDescriptors); if (!newDS) { return nullptr; } update_uniform_descriptor_set(requestedDescriptors, bindUniformBufferInfo, *newDS->descriptorSet(), this->vulkanSharedContext()); return *fUniformBufferDescSetCache.insert(key, newDS); } sk_sp VulkanResourceProvider::findOrCreateRenderPassWithKnownKey( const RenderPassDesc& renderPassDesc, bool compatibleOnly, const GraphiteResourceKey& rpKey) { if (Resource* resource = fResourceCache->findAndRefResource(rpKey, skgpu::Budgeted::kYes)) { return sk_sp(static_cast(resource)); } sk_sp renderPass = VulkanRenderPass::MakeRenderPass(this->vulkanSharedContext(), renderPassDesc, compatibleOnly); if (!renderPass) { return nullptr; } renderPass->setKey(rpKey); fResourceCache->insertResource(renderPass.get()); return renderPass; } sk_sp VulkanResourceProvider::findOrCreateRenderPass( const RenderPassDesc& renderPassDesc, bool compatibleOnly) { GraphiteResourceKey rpKey = VulkanRenderPass::MakeRenderPassKey(renderPassDesc, compatibleOnly); return this->findOrCreateRenderPassWithKnownKey(renderPassDesc, compatibleOnly, rpKey); } VkPipelineCache VulkanResourceProvider::pipelineCache() { if (fPipelineCache == VK_NULL_HANDLE) { VkPipelineCacheCreateInfo createInfo; memset(&createInfo, 0, sizeof(VkPipelineCacheCreateInfo)); createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; createInfo.pNext = nullptr; createInfo.flags = 0; createInfo.initialDataSize = 0; createInfo.pInitialData = nullptr; VkResult result; VULKAN_CALL_RESULT(this->vulkanSharedContext(), result, CreatePipelineCache(this->vulkanSharedContext()->device(), &createInfo, nullptr, &fPipelineCache)); if (VK_SUCCESS != result) { fPipelineCache = VK_NULL_HANDLE; } } return fPipelineCache; } sk_sp VulkanResourceProvider::createFramebuffer( const VulkanSharedContext* context, const skia_private::TArray& attachmentViews, const VulkanRenderPass& renderPass, const int width, const int height) { // TODO: Consider caching these in the future. If we pursue that, it may make more sense to // use a compatible renderpass rather than a full one to make each frame buffer more versatile. VkFramebufferCreateInfo framebufferInfo; memset(&framebufferInfo, 0, sizeof(VkFramebufferCreateInfo)); framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.pNext = nullptr; framebufferInfo.flags = 0; framebufferInfo.renderPass = renderPass.renderPass(); framebufferInfo.attachmentCount = attachmentViews.size(); framebufferInfo.pAttachments = attachmentViews.begin(); framebufferInfo.width = width; framebufferInfo.height = height; framebufferInfo.layers = 1; return VulkanFramebuffer::Make(context, framebufferInfo); } void VulkanResourceProvider::onDeleteBackendTexture(const BackendTexture& texture) { SkASSERT(texture.isValid()); SkASSERT(texture.backend() == BackendApi::kVulkan); VULKAN_CALL(this->vulkanSharedContext()->interface(), DestroyImage(this->vulkanSharedContext()->device(), BackendTextures::GetVkImage(texture), /*VkAllocationCallbacks=*/nullptr)); VulkanAlloc alloc = BackendTextures::GetMemoryAlloc(texture); // Free the image memory used for the BackendTexture's VkImage. // // How we do this is dependent upon on how the image was allocated (via the memory allocator or // with a direct call to the Vulkan driver) . If the VulkanAlloc's fBackendMemory is != 0, then // that means the allocator was used. Otherwise, a direct driver call was used and we should // free the VkDeviceMemory (fMemory). if (alloc.fBackendMemory) { skgpu::VulkanMemory::FreeImageMemory(this->vulkanSharedContext()->memoryAllocator(), alloc); } else { SkASSERT(alloc.fMemory != VK_NULL_HANDLE); VULKAN_CALL(this->vulkanSharedContext()->interface(), FreeMemory(this->vulkanSharedContext()->device(), alloc.fMemory, nullptr)); } } sk_sp VulkanResourceProvider::findOrCreateCompatibleYcbcrConversion( const VulkanYcbcrConversionInfo& ycbcrInfo) const { if (!ycbcrInfo.isValid()) { return nullptr; } GraphiteResourceKey ycbcrConversionKey = VulkanYcbcrConversion::MakeYcbcrConversionKey(this->vulkanSharedContext(), ycbcrInfo); if (Resource* resource = fResourceCache->findAndRefResource(ycbcrConversionKey, skgpu::Budgeted::kYes)) { return sk_sp(static_cast(resource)); } auto ycbcrConversion = VulkanYcbcrConversion::Make(this->vulkanSharedContext(), ycbcrInfo); if (!ycbcrConversion) { return nullptr; } ycbcrConversion->setKey(ycbcrConversionKey); fResourceCache->insertResource(ycbcrConversion.get()); return ycbcrConversion; } sk_sp VulkanResourceProvider::findOrCreateLoadMSAAPipeline( const RenderPassDesc& renderPassDesc) { if (!renderPassDesc.fColorResolveAttachment.fTextureInfo.isValid() || !renderPassDesc.fColorAttachment.fTextureInfo.isValid()) { SKGPU_LOG_E("Loading MSAA from resolve texture requires valid color & resolve attachment"); return nullptr; } // Check to see if we already have a suitable pipeline that we can use. GraphiteResourceKey renderPassKey = VulkanRenderPass::MakeRenderPassKey(renderPassDesc, /*compatibleOnly=*/true); for (int i = 0; i < fLoadMSAAPipelines.size(); i++) { if (renderPassKey == fLoadMSAAPipelines.at(i).first) { return fLoadMSAAPipelines.at(i).second; } } // If any of the load MSAA pipeline creation structures are null then we need to initialize // those before proceeding. If the creation of one of them fails, all are assigned to null, so // we only need to check one of the structures. if (fMSAALoadVertShaderModule == VK_NULL_HANDLE) { SkASSERT(fMSAALoadFragShaderModule == VK_NULL_HANDLE && fMSAALoadPipelineLayout == VK_NULL_HANDLE); if (!VulkanGraphicsPipeline::InitializeMSAALoadPipelineStructs( this->vulkanSharedContext(), &fMSAALoadVertShaderModule, &fMSAALoadFragShaderModule, &fMSAALoadShaderStageInfo[0], &fMSAALoadPipelineLayout)) { SKGPU_LOG_E("Failed to initialize MSAA load pipeline creation structure(s)"); return nullptr; } } sk_sp compatibleRenderPass = this->findOrCreateRenderPassWithKnownKey(renderPassDesc, /*compatibleOnly=*/true, renderPassKey); if (!compatibleRenderPass) { SKGPU_LOG_E("Failed to make compatible render pass for loading MSAA"); } sk_sp pipeline = VulkanGraphicsPipeline::MakeLoadMSAAPipeline( this->vulkanSharedContext(), fMSAALoadVertShaderModule, fMSAALoadFragShaderModule, &fMSAALoadShaderStageInfo[0], fMSAALoadPipelineLayout, compatibleRenderPass, this->pipelineCache(), renderPassDesc.fColorAttachment.fTextureInfo); if (!pipeline) { SKGPU_LOG_E("Failed to create MSAA load pipeline"); return nullptr; } fLoadMSAAPipelines.push_back(std::make_pair(renderPassKey, pipeline)); return pipeline; } #ifdef SK_BUILD_FOR_ANDROID BackendTexture VulkanResourceProvider::onCreateBackendTexture(AHardwareBuffer* hardwareBuffer, bool isRenderable, bool isProtectedContent, SkISize dimensions, bool fromAndroidWindow) const { const VulkanSharedContext* vkContext = this->vulkanSharedContext(); VkDevice device = vkContext->device(); const VulkanCaps& vkCaps = vkContext->vulkanCaps(); VkAndroidHardwareBufferFormatPropertiesANDROID hwbFormatProps; VkAndroidHardwareBufferPropertiesANDROID hwbProps; if (!skgpu::GetAHardwareBufferProperties( &hwbFormatProps, &hwbProps, vkContext->interface(), hardwareBuffer, device)) { return {}; } bool importAsExternalFormat = hwbFormatProps.format == VK_FORMAT_UNDEFINED; // Start to assemble VulkanTextureInfo which is needed later on to create the VkImage but can // sooner help us query VulkanCaps for certain format feature support. // TODO: Allow client to pass in tiling mode. For external formats, this is required to be // optimal. For AHB that have a known Vulkan format, we can query VulkanCaps to determine if // optimal is a valid decision given the format features. VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL; VkImageCreateFlags imgCreateflags = isProtectedContent ? VK_IMAGE_CREATE_PROTECTED_BIT : 0; VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_SAMPLED_BIT; // When importing as an external format the image usage can only be VK_IMAGE_USAGE_SAMPLED_BIT. if (!importAsExternalFormat) { usageFlags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; if (isRenderable) { // Renderable attachments can be used as input attachments if we are loading from MSAA. usageFlags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; } } VulkanTextureInfo vkTexInfo { VK_SAMPLE_COUNT_1_BIT, Mipmapped::kNo, imgCreateflags, hwbFormatProps.format, tiling, usageFlags, VK_SHARING_MODE_EXCLUSIVE, VK_IMAGE_ASPECT_COLOR_BIT, VulkanYcbcrConversionInfo() }; if (isRenderable && (importAsExternalFormat || !vkCaps.isRenderable(vkTexInfo))) { SKGPU_LOG_W("Renderable texture requested from an AHardwareBuffer which uses a VkFormat " "that Skia cannot render to (VkFormat: %d).\n", hwbFormatProps.format); return {}; } if (!importAsExternalFormat && (!vkCaps.isTransferSrc(vkTexInfo) || !vkCaps.isTransferDst(vkTexInfo) || !vkCaps.isTexturable(vkTexInfo))) { if (isRenderable) { SKGPU_LOG_W("VkFormat %d is either unfamiliar to Skia or doesn't support the necessary" " format features. Because a renerable texture was requested, we cannot " "fall back to importing with an external format.\n", hwbFormatProps.format); return {}; } // If the VkFormat does not support the features we need, then import as an external format. importAsExternalFormat = true; // If we use VkExternalFormatANDROID with an externalFormat != 0, then format must = // VK_FORMAT_UNDEFINED. vkTexInfo.fFormat = VK_FORMAT_UNDEFINED; vkTexInfo.fImageUsageFlags = VK_IMAGE_USAGE_SAMPLED_BIT; } VulkanYcbcrConversionInfo ycbcrInfo; VkExternalFormatANDROID externalFormat; externalFormat.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID; externalFormat.pNext = nullptr; externalFormat.externalFormat = 0; // If this is zero it is as if we aren't using this struct. if (importAsExternalFormat) { GetYcbcrConversionInfoFromFormatProps(&ycbcrInfo, hwbFormatProps); if (!ycbcrInfo.isValid()) { SKGPU_LOG_W("Failed to create valid YCbCr conversion information from hardware buffer" "format properties.\n"); return {}; } vkTexInfo.fYcbcrConversionInfo = ycbcrInfo; externalFormat.externalFormat = hwbFormatProps.externalFormat; } const VkExternalMemoryImageCreateInfo externalMemoryImageInfo{ VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, // sType &externalFormat, // pNext VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID, // handleTypes }; SkASSERT(!(vkTexInfo.fFlags & VK_IMAGE_CREATE_PROTECTED_BIT) || fSharedContext->isProtected() == Protected::kYes); const VkImageCreateInfo imageCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // sType &externalMemoryImageInfo, // pNext vkTexInfo.fFlags, // VkImageCreateFlags VK_IMAGE_TYPE_2D, // VkImageType vkTexInfo.fFormat, // VkFormat { (uint32_t)dimensions.fWidth, (uint32_t)dimensions.fHeight, 1 }, // VkExtent3D 1, // mipLevels 1, // arrayLayers VK_SAMPLE_COUNT_1_BIT, // samples vkTexInfo.fImageTiling, // VkImageTiling vkTexInfo.fImageUsageFlags, // VkImageUsageFlags vkTexInfo.fSharingMode, // VkSharingMode 0, // queueFamilyCount nullptr, // pQueueFamilyIndices VK_IMAGE_LAYOUT_UNDEFINED, // initialLayout }; VkResult result; VkImage image; result = VULKAN_CALL(vkContext->interface(), CreateImage(device, &imageCreateInfo, nullptr, &image)); if (result != VK_SUCCESS) { return {}; } const VkPhysicalDeviceMemoryProperties2& phyDevMemProps = vkContext->vulkanCaps().physicalDeviceMemoryProperties2(); VulkanAlloc alloc; if (!AllocateAndBindImageMemory(&alloc, image, phyDevMemProps, hwbProps, hardwareBuffer, vkContext->interface(), device)) { VULKAN_CALL(vkContext->interface(), DestroyImage(device, image, nullptr)); return {}; } return BackendTextures::MakeVulkan(dimensions, vkTexInfo, VK_IMAGE_LAYOUT_UNDEFINED, VK_QUEUE_FAMILY_FOREIGN_EXT, image, alloc); } #endif // SK_BUILD_FOR_ANDROID } // namespace skgpu::graphite